/* ==========================================================================
   Components: buttons, forms, cards, header, nav, mobile menu, footer.
   ========================================================================== */

/* ---- Buttons -------------------------------------------------------------- */
.button {
  --button-bg: transparent;
  --button-fg: var(--accent);
  --button-border: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 2.75rem; /* 44px touch target. */
  padding: 0.6em 1.4em;
  border: 1.5px solid var(--button-border);
  border-radius: var(--radius-m);
  background: var(--button-bg);
  color: var(--button-fg);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  transition: background-color var(--dur-fast) ease, color var(--dur-fast) ease,
    border-color var(--dur-fast) ease, transform var(--dur-fast) ease, translate var(--dur-med) var(--ease-out);
}

.button:hover {
  --button-fg: var(--accent-hover);
  --button-border: var(--accent-hover);
  text-decoration: none;
}

.button:active {
  transform: translateY(1px);
}

/* Primary: copper. One per view. */
.button--primary {
  --button-bg: var(--spark);
  --button-fg: var(--color-on-spark);
  --button-border: var(--spark);
}

.button--primary:hover {
  --button-bg: var(--spark-hover);
  --button-fg: var(--color-on-spark);
  --button-border: var(--spark-hover);
}

.button--primary:active {
  --button-bg: var(--spark-pressed);
  --button-border: var(--spark-pressed);
}

/* Secondary: sage fill. */
.button--secondary {
  --button-bg: var(--accent);
  --button-fg: var(--color-on-accent);
  --button-border: var(--accent);
}

.button--secondary:hover {
  --button-bg: var(--accent-hover);
  --button-fg: var(--color-on-accent);
  --button-border: var(--accent-hover);
}

.button--secondary:active {
  --button-bg: var(--accent-pressed);
  --button-border: var(--accent-pressed);
}

/* Disabled: dashed border and muted fill, so it reads as off without relying on low contrast. */
.button:disabled,
.button[aria-disabled="true"] {
  --button-bg: var(--color-disabled-bg);
  --button-fg: var(--color-disabled-text);
  --button-border: var(--color-disabled-text);
  border-style: dashed;
  cursor: not-allowed;
  transform: none;
}

/* Text-style action with an arrow, for "View score →" and similar. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-weight: 600;
  text-decoration: none;
}

.link-arrow::after {
  content: "→";
  transition: transform var(--dur-fast) ease;
}

.link-arrow:hover::after {
  transform: translateX(3px);
}

/* ---- Forms ---------------------------------------------------------------- */
.field {
  display: grid;
  gap: var(--space-3xs);
}

.field > label,
.field legend {
  font-weight: 600;
}

.field .required,
.field .optional {
  font-weight: 400;
  color: var(--text-muted);
}

.field .hint {
  font-size: var(--step--1);
  color: var(--text-muted);
}

.input,
.select,
.textarea {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.6em 0.8em;
  background: var(--color-surface-2);
  color: var(--color-text);
  border: 1.5px solid var(--color-border-control);
  border-radius: var(--radius-s);
  transition: border-color var(--dur-fast) ease;
}

.textarea {
  min-height: 8rem;
  resize: vertical;
}

.input:hover,
.select:hover,
.textarea:hover {
  border-color: var(--accent);
}

.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  border-color: var(--accent);
}

.input::placeholder,
.textarea::placeholder {
  color: var(--text-subtle);
}

.select {
  appearance: none;
  padding-inline-end: 2.5em;
  background-image: linear-gradient(45deg, transparent 50%, var(--accent) 50%),
    linear-gradient(135deg, var(--accent) 50%, transparent 50%);
  background-position: calc(100% - 1.2em) 55%, calc(100% - 0.85em) 55%;
  background-size: 0.35em 0.35em;
  background-repeat: no-repeat;
}

.field--error .input,
.field--error .select,
.field--error .textarea,
[aria-invalid="true"] {
  border-color: var(--danger);
}

.error-message {
  font-size: var(--step--1);
  color: var(--danger);
}

/* "Error:" prefix so the state does not rely on color alone. */
.error-message::before {
  content: "Error: ";
  font-weight: 600;
}

.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2xs);
}

.choice input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin-block-start: 0.2em;
  accent-color: var(--accent);
}

fieldset {
  border: 0;
  padding: 0;
}

.status {
  padding: var(--space-s);
  border-radius: var(--radius-m);
  border-inline-start: 3px solid currentColor;
  background: var(--color-surface);
}

.status--success {
  color: var(--success);
}

.status--error {
  color: var(--danger);
}

/* ---- Cards ---------------------------------------------------------------- */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-m);
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-m);
  transition: border-color var(--dur-med) ease, transform var(--dur-med) var(--ease-out),
    box-shadow var(--dur-med) ease;
}

.card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

.card :is(h2, h3) a {
  color: var(--color-heading);
  text-decoration: none;
}

/* Whole card clickable via the heading link. */
.card :is(h2, h3) a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.card :is(h2, h3) a:focus-visible {
  outline: none;
}

.card:has(:is(h2, h3) a:focus-visible) {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}

.card__media {
  margin: calc(var(--space-m) * -1) calc(var(--space-m) * -1) var(--space-2xs);
  border-radius: var(--radius-m) var(--radius-m) 0 0;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--ground-800);
}

/* Website screenshots keep the browser shape (16:10). */
.card__media--site {
  aspect-ratio: 16 / 10;
}

.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Moss feature panel for teasers (Commissions, bio). */
.panel {
  padding: var(--space-xl) var(--space-l);
  background: var(--color-fill);
  border-radius: var(--radius-l);
}

.panel .eyebrow,
.panel a:not(.button) {
  color: var(--accent);
}

.tag {
  display: inline-block;
  padding: 0.15em 0.65em;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--text-muted);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-pill);
}

/* ---- Header --------------------------------------------------------------- */
.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border-subtle);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  min-height: 4.5rem;
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark:hover {
  color: var(--text);
  text-decoration: none;
}

.nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  list-style: none;
  padding: 0;
}

.nav__list a {
  white-space: nowrap;
  color: var(--text-muted);
  font-weight: 500;
  text-decoration: none;
  padding-block: var(--space-3xs);
}

.nav__list a:hover,
.nav__list a[aria-current] {
  color: var(--text);
}

.nav__list a[aria-current] {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.45em;
}

/* Service link, set apart from the music nav. */
/* Store: the one highlighted item, a small copper button set apart from the links. */
.nav__store {
  padding-inline-start: var(--space-m);
  border-inline-start: 1px solid var(--color-border-subtle);
}

.nav__list .nav__store a {
  display: inline-flex;
  align-items: center;
  min-height: 2.25rem;
  padding: 0.3em 1em;
  border-radius: var(--radius-m);
  background: var(--spark);
  color: var(--color-on-spark);
  font-weight: 600;
  text-decoration: none;
  transition: background-color var(--dur-fast) ease;
}

.nav__list .nav__store a:hover {
  background: var(--spark-hover);
  color: var(--color-on-spark);
}

.nav__list .nav__store a::after {
  display: none;
}

.menu-toggle {
  display: none;
  align-items: center;
  gap: var(--space-2xs);
  min-height: 2.75rem;
  padding: 0 var(--space-xs);
  background: none;
  border: 1.5px solid var(--color-border-control);
  border-radius: var(--radius-m);
  color: var(--text);
  font-weight: 600;
}

@media (max-width: 66rem) {
  .menu-toggle {
    display: inline-flex;
  }

  /* Without JS the nav stays visible and wraps below the wordmark. */
  .js .nav {
    display: none;
  }

  .no-js .site-header__inner {
    flex-wrap: wrap;
    padding-block: var(--space-xs);
  }

  .no-js .menu-toggle {
    display: none;
  }

  .no-js .nav__list {
    flex-wrap: wrap;
    gap: var(--space-xs) var(--space-s);
  }
}

/* ---- Mobile menu (full screen) ------------------------------------------- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  padding: var(--space-s) var(--gutter) var(--space-xl);
  background: var(--ground-950);
  overflow-y: auto;
}

.mobile-menu[hidden] {
  display: none;
}

.mobile-menu__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 3.5rem;
}

.mobile-menu__list {
  list-style: none;
  padding: 0;
  margin-block-start: var(--space-xl);
  display: grid;
  gap: var(--space-xs);
}

.mobile-menu__list a {
  font-family: var(--font-display);
  font-size: var(--step-3);
  color: var(--text);
  text-decoration: none;
}

.mobile-menu__list a[aria-current] {
  color: var(--accent);
}

.mobile-menu__store {
  margin-block-start: var(--space-m);
  padding-block-start: var(--space-m);
  border-top: 1px solid var(--color-border-subtle);
}

.mobile-menu__list .mobile-menu__store a {
  display: inline-flex;
  padding: 0.35em 1.2em;
  border-radius: var(--radius-m);
  background: var(--spark);
  color: var(--color-on-spark);
  font-family: var(--font-body);
  font-size: var(--step-1);
  font-weight: 600;
}

/* ---- Footer --------------------------------------------------------------- */
.site-footer {
  margin-block-start: var(--space-3xl);
  padding-block: var(--space-xl) var(--space-l);
  background: var(--ground-950);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--step--1);
  color: var(--text-muted);
}

.site-footer__grid {
  display: grid;
  gap: var(--space-l);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}

.site-footer ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-3xs);
}

.site-footer a {
  color: var(--text-muted);
}

.site-footer a:hover {
  color: var(--text);
}

.site-footer ul.cluster {
  display: flex;
  align-content: start;
}

.site-footer__legal {
  margin-block-start: var(--space-xl);
  padding-block-start: var(--space-m);
  border-top: 1px solid var(--color-border-subtle);
}

/* Quiet way into the private Studio page. */
.site-footer__studio {
  margin-inline-start: var(--space-s);
  color: inherit;
}

.social-icon {
  width: 1.25rem;
  height: 1.25rem;
  fill: currentColor;
}

/* ---- Photos (one tonal treatment across the site) ------------------------ */
.photo {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-l);
  background: var(--ground-800);
}

.photo picture {
  display: block;
  height: 100%;
}

.photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.04) brightness(0.96);
}

/* Moss wash ties every photo to the palette. */
.photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--panel);
  mix-blend-mode: soft-light;
  opacity: 0.4;
  pointer-events: none;
}

.photo figcaption,
figure > figcaption {
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* Score pages are shown as paper: no wash, no filter. */
.score-sample {
  border-radius: var(--radius-m);
  overflow: hidden;
  box-shadow: var(--shadow-3);
}

/* ---- Section heading with a trailing link -------------------------------- */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-s) var(--space-l);
  margin-block-end: var(--space-l);
}

/* ---- Breadcrumb ----------------------------------------------------------- */
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3xs);
  list-style: none;
  padding: 0;
  font-size: var(--step--1);
  color: var(--text-muted);
}

.breadcrumb li + li::before {
  content: "/";
  margin-inline-end: var(--space-3xs);
  color: var(--text-subtle);
}

/* ---- Definition list of facts (composition details) ---------------------- */
.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2xs) var(--space-m);
}

.facts dt {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--accent);
  padding-block-start: 0.2em;
}

.facts dd {
  margin: 0;
}

/* ---- Event list ----------------------------------------------------------- */
.events {
  list-style: none;
  padding: 0;
  border-top: 1px solid var(--color-border-subtle);
}

.events li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: var(--space-s);
  padding-block: var(--space-s);
  border-bottom: 1px solid var(--color-border-subtle);
}

.events time {
  font-weight: 600;
  color: var(--accent);
}

/* ---- Long-form text (policies, bios) -------------------------------------- */
.prose {
  max-width: var(--measure);
}

.prose > * + * {
  margin-block-start: var(--space-s);
}

.prose > h2 {
  margin-block-start: var(--space-xl);
  font-size: var(--step-2);
}

.prose > h3 {
  margin-block-start: var(--space-l);
}

.prose ul,
.prose ol {
  padding-inline-start: 1.25em;
}

.prose li + li {
  margin-block-start: var(--space-3xs);
}

/* ---- Page header for inner pages ----------------------------------------- */
.page-head {
  padding-block: var(--space-2xl) var(--space-xl);
}

.page-head > * + * {
  margin-block-start: var(--space-s);
}

/* ---- Photo hero: a photo behind the page heading (Compositions, Performance) ----
   Fills the first screen below the header at any size; hero-scroll.js then glides down to
   the content. The photo is muted, and a forest gradient sits over it (heaviest behind the
   text, fading into the page below), so the heading and lead stay well above 4.5:1.
   Per page: --hero-focus (which part of the photo to keep on wide screens),
   --hero-focus-narrow (phones and upright tablets), --hero-shade (darkening on those). */
.photo-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-items: center;
  min-height: calc(100svh - 4.5rem);
  margin-bottom: var(--space-l);
}

/* On wide screens the photo sits to the right and fades in from the left, clear of the text. */
.photo-hero__photo {
  position: absolute;
  inset: 0 0 0 auto;
  width: min(100%, 78%);
  z-index: -2;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 40%);
  mask-image: linear-gradient(to right, transparent 0%, #000 40%);
}

.photo-hero__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-focus, 50% 50%);
  filter: grayscale(0.25) brightness(var(--hero-brightness, 0.92));
}

.photo-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to bottom, transparent 60%, var(--ground) 100%),
    linear-gradient(90deg, rgb(16 27 23 / 0.9) 0%, rgb(16 27 23 / 0.6) 40%, rgb(16 27 23 / 0.15) 70%, transparent 100%);
}

.photo-hero .page-head {
  padding-block: var(--space-2xl);
}

@media (max-width: 47.99rem), (orientation: portrait) {
  /* The text covers most of the photo here: show it full width and darken it evenly. */
  .photo-hero__photo {
    width: 100%;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .photo-hero__photo img {
    object-position: var(--hero-focus-narrow, 50% 50%);
  }

  .photo-hero::after {
    background:
      linear-gradient(to bottom, transparent 55%, var(--ground) 100%),
      rgb(16 27 23 / var(--hero-shade, 0.62));
  }
}

.photo-hero--piano {
  --hero-focus: 30% 55%;
  --hero-focus-narrow: 35% 50%;
}

/* Bright church walls: darker, focused on Matthew. */
.photo-hero--choir {
  --hero-focus: 30% 55%;
  --hero-focus-narrow: 37% 50%;
  --hero-brightness: 0.8;
  --hero-shade: 0.7;
}

/* On phones and upright tablets the text sits low, so Matthew stays visible above it. */
@media (max-width: 47.99rem), (orientation: portrait) {
  .photo-hero--choir {
    align-items: end;
    --hero-focus-narrow: 37% 30%;
  }

  .photo-hero--choir::after {
    background:
      linear-gradient(to bottom, rgb(16 27 23 / 0.35) 0%, rgb(16 27 23 / 0.5) 35%, rgb(16 27 23 / 0.9) 65%, var(--ground) 100%);
  }
}

/* ---- Visible placeholder (previews only; blocked on production by check:todo) ---- */
.todo {
  padding: var(--space-s) var(--space-m);
  border: 2px dashed var(--danger);
  border-radius: var(--radius-m);
  color: var(--danger);
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: var(--step--1);
}

/* Spam trap: off-screen for people, still in the form for bots. */
.honeypot {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Heading level and visual size kept separate. */
.h3 {
  font-size: var(--step-2);
  line-height: var(--leading-snug);
}

/* Button that looks like a link. */
.text-button {
  padding: 0;
  background: none;
  border: 0;
  color: var(--color-link);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Heading styled one step down, when the document outline needs h2 but the design needs smaller. */
.h4 {
  font-size: var(--step-1);
}

.buy form .button {
  width: 100%;
}

/* Composition card: score page as the image, cropped to the title area. */
.work-card .card__media {
  aspect-ratio: 16 / 9;
  background: var(--text);
}

.work-card .card__media img {
  object-position: top;
}

/* Photo collage (About, Performing). Every photo keeps its own shape: each figure
   carries its width / height as data-r, which gallery.js copies to --r. gallery.js deals the photos
   into columns of different widths (shortest column first), so sizes vary but there
   are no holes inside; only the bottom edge is ragged. Without JS: plain columns. */
.gallery {
  column-gap: var(--space-s);
}

@media (min-width: 36rem) {
  .gallery {
    columns: 2;
  }
}

h2 + .gallery {
  margin-top: var(--space-m);
}

.gallery .photo {
  break-inside: avoid;
  display: block;
  aspect-ratio: var(--r, auto);
  margin: 0 0 var(--space-s);
  border-radius: var(--radius-m);
}

.gallery--columns {
  display: grid;
  gap: var(--space-s);
  align-items: start;
  columns: auto;
}

.gallery__column {
  display: grid;
  gap: var(--space-s);
}

.gallery__column .photo {
  margin: 0;
}

/* The middle column starts a little lower, so the top edge is uneven too. */
.gallery--columns > .gallery__column:nth-child(2):not(:last-child) {
  margin-block-start: var(--space-xl);
}

/* Two columns: text and a photo. */
.split {
  display: grid;
  gap: var(--space-xl);
  align-items: start;
}

@media (min-width: 52rem) {
  .split {
    grid-template-columns: 1.2fr 0.8fr;
  }

  .split--reverse {
    grid-template-columns: 0.8fr 1.2fr;
  }
}

.split .photo {
  aspect-ratio: 4 / 5;
}

/* Plain list of roles: name on one line, role underneath. */
.roles {
  display: grid;
  gap: var(--space-s);
}

.roles li {
  padding-block-end: var(--space-s);
  border-bottom: 1px solid var(--color-border-subtle);
}

.roles strong {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 500;
}

/* ==== Motion ================================================================
   Everything here is off with reduced motion (see base.css and the media
   queries below). Content never depends on it: without JS it shows at once.

   Scroll reveal: blocks start lowered and transparent, and /js/motion.js adds
   .is-in as each one scrolls into view, then .reveal-done once it has settled
   so hover effects work normally. Keep this selector list in sync with
   REVEAL in /js/motion.js. */
@media (prefers-reduced-motion: no-preference) {
  .js :is(
    main .page-head > *, main .section-head, main .section > h2, main .section > .stack,
    main .grid > *, main .gallery .photo, main .roles > li, main .process > li, main .faq__item,
    main .events > li, main .split > *, main .panel, main .prose > *, main .bio-teaser > *,
    main .contact-cta > *, main .marquee, main .explore > li, main .work__aside, main .facts,
    main .score-sample, main .work__section, main .contact-layout > *, main .request-form,
    main .stats__list > li, main .strip > li, main .arch
  ):not(.is-in) {
    opacity: 0;
    transform: translateY(var(--reveal-distance));
  }

  .js .is-in:not(.reveal-done) {
    transition: opacity var(--dur-reveal) ease-out, transform var(--dur-reveal) var(--ease-out);
    transition-delay: calc(var(--reveal-i, 0) * var(--reveal-stagger));
  }

  /* Failsafe: if motion.js never runs, show everything after a moment. */
  .js:not(.motion-ready) main :not(.hero, .hero *, .marquee *) {
    animation: reveal-failsafe 0s 2.5s forwards;
  }
}

@keyframes reveal-failsafe {
  to {
    opacity: 1;
    transform: none;
  }
}

/* Page transitions between pages (Chrome, Edge, Safari; others just load).
   The header stays put while the page underneath cross-fades. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }

  .site-header {
    view-transition-name: site-header;
  }

  /* Changing page: the old page sinks back, softening out of focus, while the new one is
     drawn down over it from the top like a sheet. The header stays put throughout. */
  ::view-transition-old(root) {
    animation: page-out 420ms var(--ease-in-out) both;
  }

  ::view-transition-new(root) {
    animation: page-in 780ms cubic-bezier(0.65, 0, 0.2, 1) both;
  }
}

@keyframes page-out {
  to {
    opacity: 0;
    scale: 0.985;
    filter: blur(4px);
  }
}

@keyframes page-in {
  from {
    clip-path: inset(0 0 100% 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

/* Nav: underline grows from the left on hover and marks the current page. */
.nav__list a {
  position: relative;
  transition: color var(--dur-med) ease;
}

.nav__list a[aria-current] {
  text-decoration: none;
}

.nav__list a::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -2px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-med) var(--ease-out);
}

.nav__list a:hover::after,
.nav__list a[aria-current]::after {
  transform: scaleX(1);
}

/* Buttons: a small lift on hover, a press on click. */
.button {
  transition: background-color var(--dur-med) ease, color var(--dur-med) ease,
    border-color var(--dur-med) ease, transform var(--dur-med) var(--ease-out),
    box-shadow var(--dur-med) ease;
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

.button:active {
  transform: translateY(0) scale(0.98);
  box-shadow: none;
  transition-duration: 80ms;
}

/* Cards and photos: images ease in a little closer on hover. */
.card {
  transition: border-color var(--dur-med) ease, transform var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) ease;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-3);
}

.card__media img,
.gallery .photo img,
.split .photo img,
.bio-teaser .photo img {
  transition: transform 1.2s var(--ease-out), object-position 1.6s var(--ease-out);
}

.card:hover .card__media img,
.gallery .photo:hover img,
.split .photo:hover img,
.bio-teaser .photo:hover img {
  transform: scale(1.04);
}

/* Score cards: the page scrolls down a little, as if reading on. */
.work-card:hover .card__media img {
  object-position: center 30%;
}

.link-arrow::after {
  transition: transform var(--dur-med) var(--ease-out);
}

/* FAQ answers and form results ease in. */
@media (prefers-reduced-motion: no-preference) {
  details[open] > :not(summary) {
    animation: rise-in var(--dur-slow) var(--ease-out);
  }

  .status--success,
  .error-message {
    animation: rise-in var(--dur-med) var(--ease-out);
  }

  /* Mobile menu: fades in, links rise one after another. */
  .mobile-menu:not([hidden]) {
    animation: fade-in var(--dur-med) ease-out;
  }

  .mobile-menu:not([hidden]) li {
    animation: rise-in var(--dur-slow) var(--ease-out) both;
  }

  .mobile-menu:not([hidden]) li:nth-child(2) { animation-delay: 40ms; }
  .mobile-menu:not([hidden]) li:nth-child(3) { animation-delay: 80ms; }
  .mobile-menu:not([hidden]) li:nth-child(4) { animation-delay: 120ms; }
  .mobile-menu:not([hidden]) li:nth-child(5) { animation-delay: 160ms; }
  .mobile-menu:not([hidden]) li:nth-child(6) { animation-delay: 200ms; }
  .mobile-menu:not([hidden]) li:nth-child(7) { animation-delay: 240ms; }
  .mobile-menu:not([hidden]) li:nth-child(n + 8) { animation-delay: 280ms; }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem);
  }
}

/* Header gains a shadow once the page has scrolled (class set by motion.js). */
.site-header {
  transition: box-shadow var(--dur-slow) ease, background-color var(--dur-slow) ease;
}

.is-scrolled .site-header {
  box-shadow: var(--shadow-2);
}

/* Reading progress: a thin sage line along the foot of the header (motion.js sets --progress). */
.site-header::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -1px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: left;
  pointer-events: none;
}

/* Pointer light: a soft sage glow under the pointer on the hero, cards, and panels.
   Mouse only; motion.js adds .has-light and sets --px / --py. */
.has-light {
  position: relative;
  isolation: isolate;
}

.has-light::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(24rem circle at var(--px, 50%) var(--py, 50%), color-mix(in srgb, var(--accent) 11%, transparent), transparent 70%);
  opacity: 0;
  transition: opacity var(--dur-slow) ease;
}

.has-light:hover::before {
  opacity: 1;
}

/* Section headings rise word by word (motion.js wraps each word). */
.words .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding: 0.08em 0.12em 0.14em;
  margin: -0.08em -0.12em -0.14em;
}

.words .word > span {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 900ms var(--ease-out) calc(var(--word-i, 0) * 70ms + 120ms);
}

.words.words-in .word > span {
  transform: none;
}

/* ---- Custom cursor (cursor.js adds .has-cursor once a mouse moves) ----------- */
.has-cursor,
.has-cursor * {
  cursor: none;
}

.has-cursor :is(input, textarea, [contenteditable="true"]) {
  cursor: text;
}

.has-cursor select {
  cursor: default;
}

.cursor {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 1000;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) ease;
  will-change: transform;
}

/* The visible shape is a child-free pseudo element, so its size can change without
   moving the point it is centred on. */
.cursor::before {
  content: "";
  position: absolute;
  width: 0.5rem;
  height: 0.5rem;
  translate: -50% -50%;
  border: 1.5px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  transition: width 300ms var(--ease-out), height 300ms var(--ease-out), background-color 300ms ease,
    scale 150ms ease;
}

.cursor.is-on {
  opacity: 1;
}

.cursor.is-link::before {
  width: 2.5rem;
  height: 2.5rem;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.cursor.is-drag::before {
  width: 4.5rem;
  height: 1.75rem;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

.cursor.is-down::before {
  scale: 0.8;
}

.cursor.is-field {
  opacity: 0;
}

/* ==== Signature details ==================================================== */

/* Film grain over the whole page, like a printed score under warm light.
   An SVG noise tile; pointer-events: none so it never blocks clicks. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 900;
  pointer-events: none;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px;
}

/* Portrait frame: a single upright photo. */
.arch {
  position: relative;
  width: min(100%, 26rem);
  justify-self: center;
}

.arch .photo {
  aspect-ratio: 4 / 5;
  box-shadow: var(--shadow-3);
}
