/* Val Davis: page layout. Tokens and primitives live in components.css. */

.section {
  max-width: var(--max);
  margin: 0 auto;
  padding: var(--section) var(--gutter) 0;
}
.section.narrow {
  max-width: 760px;
  padding-bottom: var(--section);
}
.section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px 32px;
  margin-bottom: clamp(32px, 4vw, 56px);
}
.section h2,
.sequence__about h2 {
  font-size: clamp(2rem, 3.6vw, 3.25rem);
}
[data-identity="3"] .section h2,
[data-identity="3"] .sequence__about h2 {
  font-size: clamp(1.75rem, 3vw, 2.75rem);
}
.lede {
  font-size: clamp(1.0625rem, 1.3vw, 1.25rem);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 38em;
}

/* ---- Header ---------------------------------------------------------------------------------- */

/* Sticky and always visible (Tyler, 2026-10-08): same header on every page, never hidden on scroll.
   A hairline appears once the page has scrolled (components.js adds .is-stuck). */
.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s;
}
.site-header.is-stuck {
  border-bottom-color: var(--hairline);
}
html {
  scroll-padding-top: calc(var(--header-h) + 16px);
}
:root {
  --header-h: 72px;
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--gutter);
  min-height: var(--header-h);
}
/* Phones: the primary shop action stays in the bar beside the menu. */
.site-header__quick {
  display: none;
  align-items: center;
  min-height: 44px;
  margin-left: auto;
  padding: 0 4px;
  font-size: 0.9375rem;
  font-weight: 500;
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: var(--stone);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
}
.site-header__quick[aria-current] {
  text-decoration-color: currentColor;
}
.site-header__brand {
  display: inline-flex;
  align-items: center;
  flex: none;
  min-height: 44px;
  padding: 10px 0;
  color: var(--ink);
}
.wordmark {
  height: 30px;
  width: auto;
}
.wordmark--2 {
  height: 30px;
}
.wordmark--3 {
  height: 22px;
}
/* Every nav item sits in one group against the right edge (Tyler, 2026-10-08): a fixed, compact gap,
   so the group never spreads toward the centre as the window widens. */
.site-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
.site-nav__list {
  display: flex;
}
/* Equal 16px gaps between the words: each link carries 8px of padding either side (which also keeps
   short labels like "Work" at a 44px target), and the underline sits under the text only. */
.site-nav__list a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 8px;
  font-size: 0.9375rem;
  font-weight: 500;
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.45em;
}
.site-nav__list li:last-child a {
  margin-right: -8px;
}
.site-nav__list a:hover,
.site-nav__list a[aria-current] {
  text-decoration-color: currentColor;
}
.socials {
  display: flex;
  gap: 4px;
}
.icon-link {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
}
.icon-link svg {
  width: 22px;
  height: 22px;
}
.site-header__menu {
  display: none;
}
.sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 68px;
  margin-bottom: 24px;
}
.sheet__brand .wordmark {
  height: 24px;
}
.sheet__brand .wordmark--3 {
  height: 18px;
}
.sheet__nav {
  display: grid;
  gap: 4px;
}
.sheet__nav a {
  display: flex;
  align-items: center;
  min-height: 56px;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-variation-settings: var(--display-settings);
  letter-spacing: var(--display-tracking);
  font-size: 2rem;
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
}
.sheet__nav a[aria-current] {
  text-decoration: underline;
  text-decoration-thickness: 1px;
}
.sheet__socials {
  margin-top: 24px;
  /* The 22px glyph sits centred in its 44px target: pull it back so it lines up with the nav text. */
  margin-left: -11px;
}

/* Phones and small tablets: menu in a Sheet. Without JS the nav stays inline and wraps. */
@media (max-width: 860px) {
  :root {
    --header-h: 60px;
  }
  .site-header__inner {
    gap: 12px;
  }
  .js .site-header__quick {
    display: inline-flex;
  }
  .wordmark {
    height: 24px;
  }
  .wordmark--3 {
    height: 18px;
  }
  .js .site-nav {
    display: none;
  }
  .js .site-header__menu {
    display: inline-flex;
    margin-right: -10px;
  }
  /* Without JS the nav wraps under the wordmark instead of widening the page. */
  .no-js .site-header__inner {
    flex-wrap: wrap;
  }
  .no-js .site-nav__list {
    flex-wrap: wrap;
    gap: 0 20px;
  }
}
@media (max-width: 359px) {
  .site-header__inner {
    gap: 6px;
  }
  .site-header__quick {
    font-size: 0.875rem;
  }
  .wordmark {
    height: 20px;
  }
  .wordmark--3 {
    height: 15px;
  }
}

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

.site-footer {
  margin-top: var(--section);
  background: var(--porcelain);
  border-top: 1px solid var(--hairline);
}
/* Brand on the left; contact details and links in one group against the right edge (Tyler, 2026-10-08). */
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 40px;
  max-width: var(--max);
  margin: 0 auto;
  padding: 64px var(--gutter) 40px;
  font-size: 0.9375rem;
}
.site-footer__brand {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.site-footer__mark {
  height: 40px;
  width: auto;
  flex: none;
}
.site-footer__end {
  display: flex;
  gap: 32px 56px;
  margin-left: auto;
}
.site-footer__contact p a,
.site-footer__nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
  text-decoration: none;
}
.site-footer__contact a:hover,
.site-footer__nav a:hover {
  text-decoration: underline;
}
.site-footer__socials {
  margin-left: -12px;
}
.site-footer__legal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: var(--max);
  margin: 0 auto;
  padding: 20px var(--gutter) 32px;
  border-top: 1px solid var(--hairline);
  font-size: 0.8125rem;
  color: var(--muted);
}
/* Val's way into the studio: a small dot that blends into the footer until hovered or focused.
   Not a secret (Cloudflare Access protects the studio); just out of visitors' way. */
.studio-mark {
  display: inline-grid;
  flex: none; /* the © line beside it must not squeeze the 44px target at 320 */
  place-items: center;
  width: 44px;
  height: 44px;
  margin-right: -14px;
  border-radius: var(--radius);
}
/* The studio entry: identity 1's VD monogram, small and quiet (1.65:1 on porcelain, deliberately
   discreet and decorative, Tyler 2026-10-09), full ink on hover and focus. Not a second logo: a third
   of the footer brand mark's size, no label, beside the © line. */
.studio-mark__mono {
  width: auto;
  height: 15px;
  color: #c4bcb0;
  transition: color 0.2s;
}
.studio-mark:hover .studio-mark__mono,
.studio-mark:focus-visible .studio-mark__mono {
  color: var(--ink);
}
/* Phones: everything stacks, left-aligned. */
@media (max-width: 720px) {
  .site-footer__inner {
    flex-direction: column;
    gap: 32px;
  }
  .site-footer__end {
    flex-direction: column;
    gap: 24px;
    margin-left: 0;
  }
  /* Keep the focus ring inside the 16px gutter. */
  .studio-mark {
    margin-right: -6px;
  }
}

/* ---- Painting presentation ------------------------------------------------------------------- */

/* The "wall": porcelain field, painting uncropped and centred, a soft hang shadow. */
.wall {
  /* Size containment: the frame's size comes from its width and aspect ratio alone, so the
     painting can be fitted inside it without ever stretching the frame or being cropped. */
  container-type: size;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 5;
  background: var(--porcelain);
  overflow: clip;
}
.wall img {
  width: auto;
  height: auto;
  max-width: 76cqw;
  max-height: calc(100cqh - 24cqw);

  box-shadow: 0 1px 2px rgb(29 29 27 / 0.14);
  transition: transform 0.6s var(--ease-out);
}
/* With the painting's ratio known (--ar from the server), its fitted width is set outright, so the box
   has its final size before the file loads; the height follows from the width/height attributes. */
.wall[style*="--ar"] img {
  width: min(76cqw, calc((100cqh - 24cqw) * var(--ar)));
  max-width: none;
  max-height: none;
}
a:hover .wall img {
  transform: translateY(-3px);
}

.label__title {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-variation-settings: var(--display-settings);
  letter-spacing: var(--display-tracking);
  font-size: 1.25rem;
  line-height: 1.2;
}
[data-identity="3"] .label__title {
  font-size: 1.0625rem;
  font-weight: 500;
}
.label__meta {
  display: block;
  margin-top: 2px;
  font-size: 0.875rem;
  color: var(--muted);
}

/* ---- Hero shared ----------------------------------------------------------------------------- */

.hero {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(16px, 3vw, 40px) var(--gutter) 0;
}
.hero__title {
  font-size: clamp(2.75rem, 5.6vw, 5.25rem);
  line-height: 1;
  max-width: 9.5ch;
  margin-bottom: clamp(28px, 3vw, 40px);
}
[data-identity="2"] .hero__title {
  max-width: 10ch;
  line-height: 1.02;
}
[data-identity="3"] .hero__title {
  font-size: clamp(2.375rem, 4.6vw, 4.25rem);
  line-height: 1.04;
  max-width: 10ch;
}
.hero__caption {
  margin-top: 14px;
}
.hero__caption a {
  text-decoration-color: var(--stone);
}

/* A: the painting dominates; the text sits low on the left. */
.hero--a {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: end;
}
.hero--a .hero__text {
  padding-bottom: 56px;
}
.hero--a .hero__art {
  margin: 0;
  width: 100%;
}

/* Hero A: one still painting in a fixed frame, fitted uncropped and anchored bottom-right, so it
   never moves the page as it loads. */
.hero__frame {
  position: relative;
  height: min(72vh, 700px);
}
.hero__link {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
}
.hero__link picture {
  display: contents;
}
/* The width it takes once loaded, set before it loads (an unloaded img with auto size is 0 x 0, so it
   grew in on a slow first visit): the frame, the frame's height times --ar, or the img's intrinsic
   width (its `sizes`, 62vw / 92vw in home.ts), whichever is smallest. The height follows the img's
   width and height attributes. */
.hero__link img {
  max-width: 100%;
  max-height: 100%;
  width: min(100%, 62vw, calc(min(72vh, 700px) * var(--ar)));
  height: auto;
  box-shadow: 0 1px 2px rgb(29 29 27 / 0.14);
}
.hero__bar {
  margin-top: 14px;
  text-align: right;
}

/* B: Val first. */
.hero--b {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: stretch;
}
.hero--b .hero__portrait {
  margin: 0;
  overflow: clip;
}
.hero--b .hero__portrait img {
  width: 100%;
  height: 100%;
  max-height: 82vh;
  object-fit: cover;
  object-position: 50% 30%;
}
.hero--b .hero__side {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 48px;
  padding-top: clamp(24px, 6vw, 96px);
}
.hero--b .hero__work {
  margin: 0;
  justify-self: end;
  width: min(100%, 340px);
}
.hero--b .hero__work img {
  box-shadow: 0 1px 2px rgb(29 29 27 / 0.14);
}

/* C: type first, then a detail of the painting. */
.hero--c .hero__mark {
  color: var(--ink);
  padding-top: clamp(8px, 2vw, 24px);
}
.hero--c .hero__mark svg {
  width: auto;
  height: min(26vh, 22vw);
  max-width: 100%;
}
.hero--c .hero__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px 48px;
  margin: clamp(28px, 4vw, 56px) 0 clamp(28px, 3vw, 40px);
}
.hero--c .hero__title {
  font-size: clamp(1.75rem, 2.6vw, 2.5rem);
  max-width: 16ch;
  margin: 0;
}
.hero--c .hero__detail {
  margin: 0;
  overflow: clip;
}
.hero--c .hero__crop {
  display: block;
  overflow: hidden;
  aspect-ratio: 21 / 8;
}
.hero--c .hero__crop-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 62%;
}
.hero--c .hero__caption {
  font-size: 0.875rem;
  color: var(--muted);
}
/* Over the hero C detail, the header stays on paper above it. */

@media (max-width: 899px) {
  .hero--a,
  .hero--b {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
  }
  .hero--a .hero__art {
    order: -1;
    justify-self: stretch;
  }
  .hero__frame {
    height: auto;
    aspect-ratio: var(--ar, 1);
    max-height: min(64vh, 112vw);
  }
  .hero__link {
    justify-content: center;
  }
  .hero__link img {
    width: min(100%, 92vw, calc(min(64vh, 112vw) * var(--ar)));
  }
  .hero__bar {
    text-align: left;
  }
  .hero--a .hero__text {
    padding-bottom: 0;
  }
  .hero--b .hero__portrait {
    margin: 0 calc(var(--gutter) * -1);
  }
  .hero--b .hero__portrait img {
    aspect-ratio: 4 / 5;
    max-height: 72vh;
  }
  .hero--b .hero__side {
    padding-top: 0;
    gap: 40px;
  }
  .hero--b .hero__work {
    justify-self: start;
    width: min(70%, 300px);
  }
  .hero--c .hero__detail {
    margin: 0 calc(var(--gutter) * -1);
  }
  .hero--c .hero__caption {
    padding: 0 var(--gutter);
  }
  .hero--c .hero__crop {
    aspect-ratio: 4 / 5;
  }
  .hero--c .hero__row {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ---- Collection accents -------------------------------------------------------------------- */

[data-accent="water"] {
  --accent: var(--water);
}
[data-accent="earth"] {
  --accent: var(--earth);
}
[data-accent="colour"] {
  --accent: var(--colour);
}

/* ---- Artwork cards --------------------------------------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(32px, 3vw, 40px) clamp(16px, 2vw, 28px);
}
.card__link {
  display: block;
  text-decoration: none;
}
.card__title {
  margin-top: 16px;
  font-size: 1.375rem;
  line-height: 1.2;
}
[data-identity="3"] .card__title {
  font-size: 1.125rem;
  font-weight: 500;
}
.card__meta {
  margin-top: 4px;
  font-size: 0.875rem;
  color: var(--muted);
}
.card__action {
  margin-top: 14px;
}
.card__status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
  font-size: 0.9375rem;
  font-weight: 500;
}
@media (max-width: 1000px) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 420px) {
  .cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Latest works: one calm row of four; phones scroll it sideways (snapping card by card). */
.latest__row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 899px) {
  .latest__row {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: min(62vw, 300px);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding: 0 var(--gutter) 8px;
    scrollbar-width: none;
  }
  .latest__row > li {
    scroll-snap-align: start;
  }
}

/* ---- Scroll sequence: paintings into About the artist ------------------------------------------
   Static by default (no JS, reduced motion): a row of paintings, then About. components.js adds
   .is-live: the stage pins under the header and the track travels sideways with scroll progress
   (transform only), then the About panel settles. The section's height is set from the track. */

.sequence {
  max-width: var(--max);
  margin: 0 auto;
  padding: var(--section) var(--gutter) 0;
}
.sequence__works {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 32px clamp(16px, 2.4vw, 32px);
}
.sequence__item a {
  display: block;
  text-decoration: none;
}
.sequence__img {
  display: block;
  height: clamp(160px, 20vw, 300px);
  width: auto;
  max-width: 100%;
  box-shadow: 0 1px 2px rgb(29 29 27 / 0.14);
}
.sequence__item picture {
  display: contents;
}
.sequence__title {
  display: block;
  margin-top: 12px;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-variation-settings: var(--display-settings);
  letter-spacing: var(--display-tracking);
  font-size: 1.25rem;
  line-height: 1.2;
}
.sequence__about {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: center;
  margin-top: var(--section);
}
.sequence__portrait {
  margin: 0;
}
.sequence__portrait img {
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 30%;
  width: 100%;
}
.sequence__text h2 {
  margin-bottom: 20px;
}
.sequence__text .lede {
  margin-bottom: 28px;
}
/* Moved below the stage by components.js when it can't fit inside it (phones, short windows). */
.sequence + .sequence__about {
  max-width: var(--max);
  margin: var(--section) auto 0;
  padding: 0 var(--gutter);
}
@media (max-width: 760px) {
  /* Phones show eight paintings in the moving sequence (Tyler, 2026-10-12: "do like 8"; it was four).
     The server sends at most eight, so this only matters if that grows. The static row keeps them all. */
  .is-live .sequence__item:nth-child(n + 9) {
    display: none;
  }
  .sequence__works {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: end;
  }
  .sequence__img {
    height: auto;
    width: 100%;
  }
  .sequence__about {
    grid-template-columns: minmax(0, 1fr);
  }
  .sequence__portrait {
    max-width: 320px;
  }
}

.sequence.is-live {
  max-width: none;
  padding: 0;
  margin-top: var(--section);
}
.is-live .sequence__stage {
  position: sticky;
  top: var(--header-h);
  height: calc(100svh - var(--header-h));
  display: flex;
  align-items: center;
  /* clip, not hidden: focusing a painting must not scroll the stage itself (the transform does that). */
  overflow: hidden;
  overflow: clip;
}
.is-live .sequence__track {
  display: flex;
  align-items: center;
  gap: clamp(48px, 6vw, 112px);
  padding: 0 max(var(--gutter), calc((100vw - var(--max)) / 2 + var(--gutter)));
  will-change: transform;
}
.is-live .sequence__works {
  display: flex;
  flex-wrap: nowrap;
  flex: none;
  gap: clamp(32px, 4vw, 72px);
}
/* Big paintings (Tyler, 2026-10-09): one common height of about three quarters of the stage,
   lowered only where a wide painting would not fit the screen whole, and never above half the
   stored image's height (--max-h), so every painting has at least 2x its pixels on screen.
   --ar (width / height) and --max-h are set by components.js from the img attributes. */
.is-live .sequence__img {
  --stage-h: calc(100svh - var(--header-h));
  --cap: calc(var(--stage-h) * 0.76);
  height: min(var(--cap), calc((100vw - 2 * var(--gutter)) / var(--ar, 1)), var(--max-h, 100vh));
  width: auto;
  max-width: none;
  /* A fixed box from the img attributes (--ar): a lazy image's own ratio can differ by a pixel, and
     the track then changed width mid-scroll as paintings loaded, which re-laid out the sequence. */
  aspect-ratio: var(--ar);
  object-fit: cover;
}
.is-live .sequence__track > .sequence__about {
  flex: none;
  width: min(1040px, calc(100vw - 2 * var(--gutter)));
  margin: 0;
  opacity: calc(0.2 + 0.8 * var(--settle, 1));
  transform: translateY(calc((1 - var(--settle, 1)) * 28px));
}
/* Beside the last painting, the portrait has that painting's height (--about-h, set by components.js)
   so their top and bottom edges line up; the column takes the portrait's width (same 4:5 framing). */
.is-live .sequence__track > .sequence__about {
  grid-template-columns: max-content minmax(0, 1fr);
  position: relative;
  top: var(--about-dy, 0px);
}
.is-live .sequence__track > .sequence__about .sequence__portrait img {
  height: var(--about-h, auto);
  width: auto;
}
/* Never faint while anything in it has keyboard focus (coordinator ruling, 2026-10-08). */
.is-live .sequence__track > .sequence__about:focus-within {
  opacity: 1;
  transform: none;
}
@media (max-width: 760px) {
  /* Phones: as large as the stage allows, leaving room for the title below. */
  .is-live .sequence__img {
    --cap: calc(var(--stage-h) - 112px);
  }
}
@media (max-width: 760px) {
  /* Tyler, 2026-10-12: below the row, Val's portrait is as wide as the painting before it
     (--about-w, set by components.js), its height following the 4:5 framing. */
  .sequence + .sequence__about .sequence__portrait {
    max-width: none;
    width: var(--about-w, auto);
  }
}
/* Phones, where the browser has scroll-driven animations (components.js adds .is-timeline): the track
   is moved by the scroll itself, not by script (Builder 34, 2026-10-12; Tyler on his iPhone: "too
   choppy"). iOS scrolls the page on its own thread, and a recording of his phone showed the scripted
   track updating only every 2 to 6 frames behind it; WebKit runs these animations on the compositor
   from Safari 26.4. The range is the pin: from the section's top reaching the header (the timeline's
   inset) to --pin px later. --pin and --overflow come from layout(); nothing is written per frame. */
.sequence.is-timeline {
  view-timeline: --sequence block;
  view-timeline-inset: var(--header-h) 0px;
}
.is-timeline .sequence__track {
  animation: sequence-track linear both;
  animation-timeline: --sequence;
  animation-range: exit-crossing 0px exit-crossing var(--pin, 0px);
}
@keyframes sequence-track {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(calc(-1 * var(--overflow, 0px)), 0, 0);
  }
}
/* About settles over the last 28% of the pin, as the script's --settle does (smoothstep, sampled),
   done 2px early: the About stop can rest a fraction of a pixel short of the pin's end. */
.is-timeline .sequence__track > .sequence__about {
  animation: sequence-settle linear both;
  animation-timeline: --sequence;
  animation-range: exit-crossing calc(var(--pin, 0px) * 0.72) exit-crossing calc(var(--pin, 0px) - 2px);
}
.is-timeline .sequence__track > .sequence__about:focus-within {
  animation: none;
}
@keyframes sequence-settle {
  0% {
    opacity: 0.2;
    transform: translateY(28px);
  }
  25% {
    opacity: 0.325;
    transform: translateY(23.63px);
  }
  50% {
    opacity: 0.6;
    transform: translateY(14px);
  }
  75% {
    opacity: 0.875;
    transform: translateY(4.38px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---- Enquiry invitation ---------------------------------------------------------------------- */

.invite {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 28px 48px;
}
.invite h2 {
  max-width: 14ch;
}
.invite__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
}

/* ---- Motion -----------------------------------------------------------------------------------
   One signature moment: the hero painting is laid in like a wash, bottom to top, then stays still.
   Below the hero: blur-to-focus plus a small rise, once per block; artwork cards wash in. Both vanish under reduced motion,
   and nothing is hidden unless JS is running (no-JS shows everything). */

@media (prefers-reduced-motion: no-preference) {
  .wash {
    animation: wash-in 1.5s var(--ease-in-out) 0.15s backwards;
  }
  .wash img {
    animation: wash-settle 2.2s var(--ease-out) 0.15s backwards;
  }
  .reveal-text {
    animation: focus-in 1s var(--ease-out) 0.55s backwards;
  }
  .js .reveal {
    opacity: 0;
    filter: blur(6px);
    transform: translateY(14px);
    transition:
      opacity 0.8s var(--ease-out),
      filter 0.8s var(--ease-out),
      transform 0.8s var(--ease-out);
  }
  .js .reveal.is-in {
    opacity: 1;
    filter: none;
    transform: none;
  }
  /* Artwork cards: the painting washes up inside its wall (the hero's wash, quicker) with a small
     rise, and the label follows. Clip-path, transform and opacity only; at most 650ms; once.
     --i staggers cards that arrive together (components.js). */
  .js .reveal-art .wall img {
    clip-path: inset(100% 0 0 0);
    transform: translateY(12px);
    transition:
      clip-path 0.65s var(--ease-out) calc(var(--i, 0) * 70ms),
      transform 0.65s var(--ease-out) calc(var(--i, 0) * 70ms);
  }
  .js .reveal-art > :not(.card__link),
  .js .reveal-art .card__title {
    opacity: 0;
    transform: translateY(8px);
    transition:
      opacity 0.5s var(--ease-out) calc(120ms + var(--i, 0) * 70ms),
      transform 0.5s var(--ease-out) calc(120ms + var(--i, 0) * 70ms);
  }
  .js .reveal-art.is-in .wall img {
    clip-path: inset(0);
    transform: none;
  }
  .js .reveal-art.is-in > :not(.card__link),
  .js .reveal-art.is-in .card__title {
    opacity: 1;
    transform: none;
  }
  .js .reveal-art.is-in a:hover .wall img {
    transform: translateY(-3px);
  }
}
@keyframes wash-in {
  from {
    clip-path: inset(100% 0 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}
@keyframes wash-settle {
  from {
    transform: scale(1.045) translateY(1.5%);
  }
  to {
    transform: none;
  }
}
@keyframes focus-in {
  from {
    opacity: 0;
    filter: blur(8px);
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    filter: none;
    transform: none;
  }
}

/* ---- Inner pages ----------------------------------------------------------------------------- */

.page-head {
  padding-top: clamp(32px, 6vw, 88px);
}
.page-head h1 {
  font-size: clamp(2.75rem, 6vw, 5rem);
  margin-bottom: 20px;
}
.page-head .lede {
  margin-bottom: 8px;
}
/* A collection's facts line (count and size range): plain, under the name. */
.page-head__facts {
  margin: -8px 0 8px;
}
/* 404: the page-head type scale, then the two ways back. */
.notfound {
  min-height: 52svh;
}
.notfound__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
  margin-top: 32px;
}
.order-success {
  min-height: 52svh;
}
.order-success .lede {
  max-width: 48rem;
  margin-bottom: 32px;
}
.order-summary {
  max-width: 40rem;
  margin: 0 0 32px;
  border-top: 1px solid var(--hairline);
}
.order-summary div {
  display: grid;
  grid-template-columns: minmax(7rem, 1fr) 3fr;
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
}
.order-summary dt {
  color: var(--muted);
}
.order-summary dd {
  margin: 0;
}
.filter {
  margin-top: 24px;
  border-bottom: 1px solid var(--hairline);
}
/* Collection labels are display type, 26px on phones to 36px on desktop: large text (3:1 is enough for
   the painting texture inside the letters, Tyler 2026-10-08) and big enough for the texture to read. */
.filter a {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-variation-settings: var(--display-settings);
  letter-spacing: var(--display-tracking);
  font-size: clamp(1.625rem, 1.1rem + 1.6vw, 2.25rem);
  line-height: 1.15;
}
@media (max-width: 420px) {
  .filter {
    column-gap: 16px;
  }
}
.filter a[aria-current] {
  border-bottom-color: var(--accent, var(--ink));
  border-bottom-width: 2px;
}
.section--tight {
  padding-top: clamp(32px, 4vw, 56px);
}
/* "More from <collection>": fewer works than columns sit centred at the usual card width (Tyler,
   2026-10-10), never stretched. auto-fit drops the empty tracks, so centring moves the rest. */
@media (min-width: 1001px) {
  .cards--related {
    grid-template-columns: repeat(auto-fit, calc((100% - 3 * clamp(16px, 2vw, 28px)) / 4));
    justify-content: center;
  }
}
.cards--work {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(40px, 4vw, 64px) clamp(16px, 2.4vw, 32px);
}
@media (max-width: 1000px) {
  .cards--work {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 420px) {
  .cards--work {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Artwork: never cropped. */
.art {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
  /* The first screen: the painting fits below the sticky header with a calm margin; svh so phone
     browser bars never cut it. --art-chrome is the room for the row(s) under it. */
  --art-top: clamp(24px, 4vw, 56px);
  --art-chrome: 104px;
  --art-screen: calc(100vh - var(--header-h) - var(--art-top));
  padding-top: var(--art-top);
}
@supports (height: 100svh) {
  .art {
    --art-screen: calc(100svh - var(--header-h) - var(--art-top));
  }
}
/* Painting, cue and previous / next fill exactly the first screen, so nothing else shows until the
   visitor scrolls. */
.art__first {
  display: flex;
  flex-direction: column;
  min-height: var(--art-screen);
  gap: 12px;
}
.art__first > .art__figure {
  flex: 1;
}
/* The cue under the first screen says there is more below: "Details" on phones, "See it on a wall"
   from 600px when the painting has a wall photo, centred in the previous / next row. A real link (works
   without JS); one slow nudge of the chevron after a second, never under reduced motion. */
.art__cue {
  display: none;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  border-radius: var(--radius);
  transition:
    opacity 0.3s,
    visibility 0.3s;
}
.art__cue svg {
  width: 18px;
  height: 18px;
  flex: none;
}
.art__cue:hover {
  background: rgb(29 29 27 / 0.06);
}
.art__cue:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.art__cue.is-gone {
  opacity: 0;
  visibility: hidden;
}
@media (prefers-reduced-motion: no-preference) {
  .art__cue svg {
    animation: art-cue-nudge 1.4s ease-in-out 1s 1 both;
  }
}
@keyframes art-cue-nudge {
  0%,
  100% {
    transform: translateY(0);
  }
  45% {
    transform: translateY(5px);
  }
}
/* The row under the painting: previous / next at the edges, the wall cue in the middle. */
/* No rule above it (Tyler, 2026-10-10): the row is held by spacing alone. */
.art__bar {
  display: grid;
  align-items: center;
  padding-top: 8px;
}
.art__bar > * {
  grid-area: 1 / 1;
}
.art__cue--wall {
  justify-self: center;
}
@media (min-width: 600px) {
  .art__cue--wall {
    display: inline-flex;
  }
  .art__first--wall > .art__cue--details {
    display: none;
  }
  /* Long titles wrap rather than run under the cue, so leave room for a two-line row. */
  .art__first--wall .art-nav__link {
    max-width: calc(50% - 86px);
  }
  .art:has(.art__first--wall) {
    --art-chrome: 132px;
  }
}
@media (min-width: 600px) and (max-width: 999px) {
  .art:has(.art__first--wall) {
    --art-chrome: 160px;
  }
}
.art__info:focus {
  outline: none;
}
/* Just the painting: no panel or border around it (Tyler, 2026-10-10). */
.art__figure {
  margin: 0;
  display: grid;
  place-items: center;
}
.art__zoom {
  position: relative;
  display: block;
  cursor: zoom-in;
}
.art__figure img {
  --fit-h: max(240px, calc(var(--art-screen) - var(--art-chrome)));
  max-height: var(--fit-h);
  width: auto;
  box-shadow: 0 1px 2px rgb(29 29 27 / 0.14);
}
.art__zoom-hint {
  position: absolute;
  right: 10px;
  bottom: 10px;
  padding: 6px 10px;
  background: rgb(251 250 246 / 0.92);
  border-radius: var(--radius);
  font-size: 0.8125rem;
  font-weight: 500;
  opacity: 0;
  transition: opacity 0.2s;
}
.art__zoom:hover .art__zoom-hint,
.art__zoom:focus-visible .art__zoom-hint {
  opacity: 1;
}
.art__info {
  position: sticky;
  top: calc(var(--header-h) + 24px);
  padding-top: 8px;
}
.art__collection a {
  display: inline-flex;
  min-height: 44px;
  min-width: 44px;
  align-items: center;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 2px solid var(--accent, var(--ink));
}
.art__title {
  margin: 16px 0 20px;
  font-size: clamp(2.5rem, 4.4vw, 4rem);
}
.art__price {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 1.25rem;
  font-weight: 500;
}
/* Buy (solid) over Enquire (outlined), or Enquire alone as the solid one: one column, the same
   height, and both as wide as the longer label, at every width (Tyler, 2026-10-10). */
.art__actions {
  display: grid;
  grid-template-columns: minmax(0, max-content);
  gap: 12px;
  margin: 28px 0 36px;
}
.art__actions .btn {
  width: 100%;
}
.buy {
  margin: 0;
}
.art__details {
  margin: 0;
  border-top: 1px solid var(--hairline);
}
.art__details div {
  display: grid;
  grid-template-columns: 7.5em 1fr;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.9375rem;
}
.art__details dt {
  color: var(--muted);
}
.art__details dd {
  margin: 0;
}
.art__story {
  margin-top: 28px;
  font-size: 1.0625rem;
  color: var(--ink-2);
  max-width: 34em;
}
@media (max-width: 999px) {
  .art {
    grid-template-columns: minmax(0, 1fr);
  }
  .art {
    --art-chrome: 160px;
  }
  .art__cue--details {
    display: inline-flex;
    align-self: center;
  }
  .art__info {
    position: static;
  }
}

/* The painting, its other photos, then previous / next painting in the collection's order: a labelled
   row under everything ("Previous painting" + the title), never over a photo, so it can't read as a
   carousel of this painting's photos (Tyler, 2026-10-10). */
.art__stage {
  display: grid;
  gap: clamp(16px, 2.4vw, 32px);
}
.art-nav {
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  gap: 16px;
}
.art-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 44px;
  min-height: 56px;
  padding: 6px 12px 6px 4px;
  color: var(--ink);
  text-decoration: none;
  border-radius: var(--radius);
}
.art-nav__link--next {
  padding: 6px 4px 6px 12px;
  text-align: right;
}
.art-nav__link svg {
  width: 24px;
  height: 24px;
  flex: none;
}
.art-nav__text {
  display: grid;
  gap: 2px;
}
.art-nav__word {
  font-size: 0.8125rem;
  color: var(--muted);
  white-space: nowrap;
}
@media (max-width: 560px) {
  .art-nav__link {
    flex: 0 1 50%;
    gap: 4px;
    padding: 6px 4px 6px 0;
  }
  .art-nav__link--next {
    justify-content: flex-end;
    padding: 6px 0 6px 4px;
  }
  .art-nav__link svg {
    width: 20px;
    height: 20px;
  }
  .art-nav__word {
    font-size: 0.75rem;
  }
  /* The first and last paintings have one neighbour: its link takes the whole row (Tyler,
     2026-10-10), so the title doesn't wrap for want of room; next stays at the right edge. */
  .art-nav__link--none {
    display: none;
  }
  .art-nav:has(> .art-nav__link--none) > .art-nav__link {
    flex: 0 1 auto;
  }
  .art-nav:has(> .art-nav__link--none) > .art-nav__link--next {
    margin-left: auto;
  }
}
.art-nav__title {
  font-weight: 500;
  text-wrap: balance; /* a long neighbour title never ends on one word ("From Chaos / to Calm") */
}
.art-nav__link--none {
  visibility: hidden;
}
.art-nav__link:hover {
  background: rgb(29 29 27 / 0.06);
}
.art-nav__link:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
/* In the lightbox: the same labelled links along the bottom, at every width. */
.lightbox .art-nav {
  position: absolute;
  inset: auto 8px 8px;
}
.lightbox .art-nav__link {
  border: 1px solid var(--hairline);
  background: rgb(251 250 246 / 0.94);
}
.lightbox:has(.art-nav) {
  padding-bottom: 88px;
}
.lightbox:has(.art-nav) img {
  --fit-h: calc(100vh - 160px);
}

/* Extra photos (details, on a wall, in progress): below the painting, uncropped, in Val's order. The
   wall photo spans the column like the painting; other extras sit two across. No panels. */
.art__more {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 2vw, 20px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.art__more li {
  display: grid;
  place-items: center;
}
.art__more img {
  --fit-h: 48vh;
  max-height: var(--fit-h);
  width: auto;
}
.art__more .art__more-wide {
  grid-column: 1 / -1;
}
.art__more-wide img {
  --fit-h: 78vh;
}
@media (max-width: 999px) {
  .art__more-wide img {
    --fit-h: 70vh;
  }
}
@media (max-width: 560px) {
  .art__more {
    gap: 8px;
  }
}

.lightbox:target {
  display: grid;
}
.lightbox__img,
.lightbox picture {
  max-width: 100%;
  max-height: calc(100vh - 80px);
}
.lightbox img {
  --fit-h: calc(100vh - 80px);
  max-height: var(--fit-h);
  width: auto;
  object-fit: contain;
}
/* With the photo's ratio known (--ar from the server), the width is set outright: as wide as the
   height limit allows, never wider than the column (max-width: 100%); the height follows from the
   width and height attributes. So the box is reserved before the photo loads, with no layout shift. */
.art__zoom[style*="--ar"] img,
.lightbox[style*="--ar"] img {
  width: calc(var(--fit-h) * var(--ar));
}

/* About */
.about {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: start;
  padding-top: clamp(24px, 4vw, 56px);
}
.about__portrait {
  margin: 0;
}
.about__portrait img {
  width: 100%;
}
.about__text h1 {
  font-size: clamp(2.75rem, 6vw, 5rem);
  margin-bottom: 28px;
}
.about__text .lede {
  margin-bottom: 20px;
}
/* Where to go next: each collection, the available works and Enquire, as plain links. */
.about__links {
  margin-top: 8px;
  border-top: 1px solid var(--hairline);
  padding-top: 12px;
}
.about__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
@media (max-width: 860px) {
  .about {
    grid-template-columns: minmax(0, 1fr);
  }
  /* The source is 960px wide (1.78x at 540 CSS px, accepted by Tyler); 540px also keeps the heading on the first screen. */
  .about__portrait {
    max-width: 540px;
  }
}

/* Contact */
.contact {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: start;
  padding-top: clamp(24px, 4vw, 56px);
}
.contact__intro h1 {
  font-size: clamp(2.75rem, 6vw, 5rem);
  margin-bottom: 20px;
}
.form {
  display: grid;
  gap: 22px;
}
/* "Prefer to call?" + an outlined "Book a call" (only when contact.bookingUrl is set). The solid
   "Send enquiry" stays the main action; the button is sized to its label, not stretched. */
.contact__booking,
.form__booking {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  color: var(--ink-2);
}
.contact__booking {
  margin-top: 20px;
}
.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
}
/* The painting an enquiry is about, shown in the form (only when it comes from a painting). */
.enquiry-about {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px 10px 10px;
  background: var(--porcelain);
  border-radius: var(--radius);
}
.enquiry-about__thumb {
  flex: none;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
}
.enquiry-about__img {
  max-width: 56px;
  max-height: 56px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.enquiry-about__text {
  margin: 0;
  flex: 1;
  min-width: 0;
}
.enquiry-about__label {
  color: var(--muted);
}
.enquiry-about__remove {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  color: var(--ink-2);
}
/* "Interested in more than one painting?": optional ticks of available paintings, each a row with a
   thumbnail, title and size. The current painting is first and ticked; the rest sit in a disclosure. */
.pick {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
.pick > .label {
  padding: 0;
}
.pick > .hint {
  margin: -4px 0 2px;
}
.pick__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pick__item {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 64px;
  padding: 8px 12px 8px 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}
.pick__item:has(:checked) {
  background: var(--porcelain);
  border-color: var(--stone);
}
.pick__item:has(:focus-visible) {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}
.pick__check {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--ink);
}
.pick__check:focus-visible {
  outline: none;
}
.pick__thumb {
  flex: none;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
}
.pick__img {
  max-width: 48px;
  max-height: 48px;
  width: auto;
  height: auto;
  object-fit: contain;
}
.pick__text {
  display: grid;
  min-width: 0;
}
.pick__size {
  color: var(--muted);
  font-size: 0.875rem;
}
.pick__more > .pick__list {
  margin-top: 8px;
}
.pick__summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  width: fit-content;
  cursor: pointer;
  list-style: none;
}
.pick__summary::-webkit-details-marker {
  display: none;
}
/* A small chevron: points down when closed, up when open. */
.pick__summary::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin: 0 6px 4px 2px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}
.pick__more[open] > .pick__summary::before {
  margin-bottom: -2px;
  transform: rotate(-135deg);
}
.pick__summary-text {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.pick__label {
  min-width: 0;
}
.art__enquire {
  margin: 8px 0 40px;
  padding-top: 28px;
  border-top: 1px solid var(--hairline);
}
.art__enquire[hidden],
.alert[hidden] {
  display: none;
}
.art__enquire-title {
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
  margin: 0 0 20px;
}
.art__enquire-title:focus {
  outline: none;
}
.art__notice {
  margin-top: 20px;
}
.hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
@media (max-width: 860px) {
  .contact {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Prose */
.prose h1 {
  font-size: clamp(2.5rem, 5vw, 4rem);
  margin-bottom: 28px;
}
.prose h2 {
  font-size: 1.75rem;
  margin: 40px 0 12px;
}
.prose p {
  margin-bottom: 14px;
  color: var(--ink-2);
}

/* Collection names. Plain words by default. The per-letter concept (C) adds aria-hidden letters and
   keeps the word as visually hidden text, so it is read, copied and indexed exactly once. These rules
   live here, not in collection-names.css, so the markup is right even if that file doesn't load. */
.cname__word {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.cname--letters {
  position: relative;
}
