/* ==========================================================================
   Okaybabs Studio — site.css
   Palette rationale: the works are natural soil (browns, ochres, terracotta)
   and raffia (pale beige) on jute burlap. A warm linen ground keeps the works
   reading as objects on a wall; a dark ground would compress the brown-on-brown
   range and make the soil read as mud.
   ========================================================================== */

:root {
  /* --- ground & ink --- */
  --linen:        #f6f1e8;   /* page ground, warm off-white */
  --linen-deep:   #ede5d8;   /* alternating band / card ground */
  --burlap:       #e0d5c2;   /* hairlines, rules */
  --ink:          #2b211a;   /* body text — 12.4:1 on --linen */
  --ink-soft:     #5b4c3f;   /* secondary text — 6.6:1 on --linen */
  --ink-faint:    #7a6857;   /* meta text — 4.6:1 on --linen */

  /* --- accents drawn from the works --- */
  --terracotta:   #93482a;   /* primary accent — 6.5:1 on --linen */
  --terracotta-d: #7a3a20;   /* hover / pressed */
  --ochre:        #a9752c;
  --soil:         #3d2c20;   /* deepest brown, viewer matte */

  /* --- type --- */
  --font-display: "Cormorant Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* --- rhythm --- */
  --gutter: 20px;
  --wrap: 1180px;
  --header-h: 64px;
  --radius: 2px;
  --shadow-lift: 0 1px 2px rgba(43,33,26,.06), 0 8px 24px rgba(43,33,26,.09);
}

@media (min-width: 700px) { :root { --gutter: 32px; } }
@media (min-width: 1100px) { :root { --gutter: 48px; } }

/* --------------------------------------------------------------------------
   Reset / base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is position:sticky, so an in-page jump to #contact (from the
     header CTA, the footer, or a work page's enquire button) would land the
     heading underneath it. scroll-padding-top on the scroll container offsets
     every anchor jump by the header's height at once, rather than needing a
     scroll-margin on each target. */
  scroll-padding-top: calc(var(--header-h) + 1rem);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  /* Belt and braces against horizontal scroll: no element may widen the page. */
  overflow-x: hidden;
  background: var(--linen);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3, h4 { margin: 0; font-weight: 500; line-height: 1.18; }
p { margin: 0 0 1.1em; }
ul { margin: 0; padding: 0; }
li { list-style: none; }

a { color: var(--terracotta); text-decoration-thickness: 1px; text-underline-offset: .18em; }
a:hover { color: var(--terracotta-d); }

:focus-visible {
  outline: 2px solid var(--terracotta);
  outline-offset: 3px;
  border-radius: var(--radius);
}

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

.skip-link {
  position: absolute; left: 50%; top: 0;
  transform: translate(-50%, -120%);
  z-index: 200;
  background: var(--ink); color: var(--linen);
  padding: .6rem 1.1rem; font-size: .875rem;
  text-decoration: none;
}
.skip-link:focus { transform: translate(-50%, 0); color: var(--linen); }

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.site-header {
  position: sticky; top: 0; z-index: 100;
  background: rgba(246,241,232,.94);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--burlap);
  transition: background .35s ease, border-color .35s ease, color .35s ease;
}

/* On the home page the header floats over the hero artwork until the hero has
   scrolled past. `.has-hero` is set by the generator, so the transparent state
   never applies to a page without a hero behind it.

   The transparent treatment is gated on `.js` as well: without JavaScript
   nothing can ever add `.is-solid`, so the header would stay transparent over
   light artwork further down the page. No JS therefore means the solid,
   always-legible default. */
.has-hero.js .site-header {
  background: transparent;
  border-bottom-color: transparent;
  backdrop-filter: none;
}
.has-hero.js .site-header .brand,
.has-hero.js .site-header .brand-name,
.has-hero.js .site-header .site-nav a { color: var(--linen); }
.has-hero.js .site-header .brand-sub { color: rgba(246,241,232,.7); }
.has-hero.js .site-header .nav-toggle { border-color: rgba(246,241,232,.5); }
.has-hero.js .site-header .nav-toggle-bars,
.has-hero.js .site-header .nav-toggle-bars::before,
.has-hero.js .site-header .nav-toggle-bars::after { background: var(--linen); }

/* Scrolled past the hero: revert to the solid treatment. */
.has-hero.js .site-header.is-solid {
  background: rgba(246,241,232,.94);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom-color: var(--burlap);
}
.has-hero.js .site-header.is-solid .brand,
.has-hero.js .site-header.is-solid .brand-name,
.has-hero.js .site-header.is-solid .site-nav a { color: var(--ink); }
.has-hero.js .site-header.is-solid .brand-sub { color: var(--ink-faint); }
.has-hero.js .site-header.is-solid .nav-toggle { border-color: var(--burlap); }
.has-hero.js .site-header.is-solid .nav-toggle-bars,
.has-hero.js .site-header.is-solid .nav-toggle-bars::before,
.has-hero.js .site-header.is-solid .nav-toggle-bars::after { background: var(--ink); }
/* The Contact button keeps its filled terracotta in both header states. */
.has-hero.js .site-header .nav-cta,
.has-hero.js .site-header.is-solid .nav-cta { color: var(--linen); }

.header-inner {
  display: flex; align-items: center; gap: 1rem;
  min-height: 64px;
  padding-block: .5rem;
}

.brand {
  display: flex; align-items: center; gap: .65rem;
  text-decoration: none; color: var(--ink);
  margin-right: auto;
}
.brand-mark { width: 38px; height: 38px; border-radius: 50%; }
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }
.brand-name {
  font-family: var(--font-display);
  font-size: 1.35rem; letter-spacing: .01em;
}
.brand-sub {
  font-size: .62rem; letter-spacing: .22em;
  text-transform: uppercase; color: var(--ink-faint);
}

.nav-toggle {
  display: none;
  width: 42px; height: 42px;
  padding: 0; border: 1px solid var(--burlap);
  border-radius: var(--radius);
  background: transparent; cursor: pointer;
  align-items: center; justify-content: center;
}
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  display: block; width: 18px; height: 1.5px;
  background: var(--ink); content: ""; position: relative;
  transition: transform .2s ease, opacity .2s ease;
}
.nav-toggle-bars::before { position: absolute; top: -6px; }
.nav-toggle-bars::after  { position: absolute; top: 6px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after  { top: 0; transform: rotate(-45deg); }

.site-nav ul { display: flex; align-items: center; gap: 1.15rem; }
@media (min-width: 1000px) { .site-nav ul { gap: 1.6rem; } }
.site-nav a {
  color: var(--ink); text-decoration: none;
  font-size: .82rem; letter-spacing: .12em; text-transform: uppercase;
  padding-block: .35rem;
  border-bottom: 1px solid transparent;
}
.site-nav a:hover { border-bottom-color: var(--terracotta); color: var(--terracotta); }
.site-nav a[aria-current="page"] { border-bottom-color: var(--ink); }
/* Contact button — the one filled control in the header. The Shopify shop is
   deliberately not linked from this site (client, 2026-09-17), so the primary
   action is the site's own contact form. */
.site-nav .nav-cta {
  background: var(--terracotta);
  border: 1px solid var(--terracotta);
  color: var(--linen);
  padding: .45rem 1.05rem;
  border-radius: var(--radius);
  white-space: nowrap;
}
.site-nav .nav-cta:hover {
  background: var(--terracotta-d); border-color: var(--terracotta-d);
  color: var(--linen); border-bottom-color: var(--terracotta-d);
}

@media (max-width: 719px) {
  .nav-toggle { display: inline-flex; }
  .site-nav {
    position: absolute; left: 0; right: 0; top: 100%;
    background: var(--linen);
    border-bottom: 1px solid var(--burlap);
    display: none;
  }
  .site-nav.is-open { display: block; }
  .site-nav ul {
    flex-direction: column; align-items: stretch;
    gap: 0; padding: .5rem var(--gutter) 1.1rem;
  }
  .site-nav li { border-top: 1px solid var(--burlap); }
  .site-nav a { display: block; padding: .9rem 0; border-bottom: 0; }
  .site-nav .nav-cta { text-align: center; margin-top: .9rem; padding: .8rem; }
  .site-nav li:has(.nav-cta) { border-top: 0; }
  .site-nav li:has(.nav-cta) { margin-top: -.35rem; }
}

/* --------------------------------------------------------------------------
   Full-bleed helper

   The page has a centred max-width wrapper, but editorial sections need to
   reach the viewport edges. `100vw` includes the scrollbar and would cause a
   horizontal scroll, so width is taken from the body box instead.
   -------------------------------------------------------------------------- */

.bleed {
  width: 100%;
  margin-inline: 0;
}

/* --------------------------------------------------------------------------
   Reveal on scroll (JS adds .is-visible; without JS the fallback below shows
   everything, so content is never hidden behind a script.)
   -------------------------------------------------------------------------- */

.reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity .75s ease, transform .75s cubic-bezier(.22,.61,.36,1);
    will-change: opacity, transform;
  }
  .js .reveal.is-visible { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   Hero — full viewport, artwork close-up behind the artist's name
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: end;
  overflow: hidden;
  background: var(--soil);
}

/* Pull the hero up under the header only when the header is actually
   transparent (i.e. JS is running); otherwise the solid bar would crop it. */
.js .hero { margin-top: calc(-1 * var(--header-h)); }

.hero-media { position: absolute; inset: 0; }
.hero-media picture, .hero-media img {
  width: 100%; height: 100%;
  display: block;
}
.hero-media img {
  object-fit: cover;
  /* Atmosphere layer: the crop may soften as it scales. The deep-zoom viewer
     on each work page is where true resolution is presented. */
  object-position: 18% 62%;
  filter: saturate(1.02) contrast(1.02);
  transform: scale(1.04);
}

/* Scrim: dark enough that the display type clears 4.5:1 over any part of the
   artwork, weighted to the bottom where the text sits. */
/* Measured, not guessed: the artwork's ochre passages peak around L*=170, so
   an unaided image leaves the linen display type at ~2.1:1. Two stacked layers
   bring the worst-case pixel behind the text under L*=80, which clears 4.5:1
   for the name, tagline and CTA while the artwork still reads as artwork. */
.hero-scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top, rgba(20,13,9,.94) 0%, rgba(20,13,9,.88) 34%,
                    rgba(20,13,9,.66) 62%, rgba(20,13,9,.38) 100%),
    /* 2026-10-05 client: show a little more of the artwork on the right.
       The text column (left ~45%) keeps its original darkness; only the
       right side is lightened. */
    linear-gradient(to right, rgba(20,13,9,.72) 0%, rgba(20,13,9,.44) 45%,
                    rgba(20,13,9,.06) 100%);
}

.hero-content {
  position: relative;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: clamp(3rem, 12vh, 8rem);
  color: var(--linen);
}

.hero-eyebrow {
  margin: 0 0 1.4rem;
  font-size: .7rem; letter-spacing: .26em; text-transform: uppercase;
  color: rgba(246,241,232,.82);
  max-width: 30ch;
}

.hero-name {
  font-family: var(--font-display);
  font-size: clamp(3rem, 8vw, 7rem);
  line-height: .95;
  letter-spacing: -.02em;
  color: var(--linen);
  margin: 0;
}

.hero-realname {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.05rem, 2.4vw, 1.6rem);
  color: rgba(246,241,232,.86);
  margin: .7rem 0 0;
}

.hero-tagline {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.5vw, 1.8rem);
  line-height: 1.35;
  color: rgba(246,241,232,.94);
  max-width: 26ch;
  margin: 1.8rem 0 2.2rem;
}

.hero-cta {
  display: inline-block;
  font-size: .76rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--linen);
  text-decoration: none;
  padding: .95rem 1.7rem;
  border: 1px solid rgba(246,241,232,.55);
  border-radius: var(--radius);
  transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.hero-cta:hover {
  background: var(--linen); border-color: var(--linen); color: var(--soil);
}

/* Marks the point at which the header turns solid. */
.hero-sentinel {
  position: absolute; left: 0; bottom: 0;
  width: 1px; height: 1px;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Works — editorial, asymmetric, full-bleed
   -------------------------------------------------------------------------- */

.works {
  padding-block: clamp(4rem, 10vh, 9rem) 0;
  background: var(--linen);
}

.w-title {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.6rem);
  line-height: 1.1;
  margin: 0;
  color: var(--ink);
}

.w-yoruba {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1rem, 2vw, 1.25rem);
  color: var(--terracotta);
  margin: .2rem 0 0;
}

.w-meta {
  font-size: .84rem;
  color: var(--ink-faint);
  margin: .7rem 0 0;
}
.w-meta span + span::before { content: " · "; }

.w-zoom {
  display: inline-block;
  margin-top: .9rem;
  font-size: .68rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--terracotta);
  border-bottom: 1px solid currentColor;
  padding-bottom: .15rem;
}

.works a { text-decoration: none; color: inherit; display: block; }

/* --- Full-bleed panorama band --- */

.w-band { margin-block: clamp(3rem, 8vh, 7rem); }

.w-band-media {
  width: 100%;
  overflow: hidden;
  background: var(--linen-deep);
}
.w-band-media img {
  width: 100%; height: auto;
  display: block;
  transition: transform .9s cubic-bezier(.22,.61,.36,1);
}
.w-band a:hover .w-band-media img { transform: scale(1.015); }

/* On a phone a 3.5:1 strip is only ~107px tall; crop to a readable band and
   let the work page's viewer carry the full sweep. */
@media (max-width: 719px) {
  .w-band-media { aspect-ratio: 16 / 10; }
  .w-band-media img { height: 100%; object-fit: cover; object-position: center; }
}

.w-band .w-caption {
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 1.4rem var(--gutter) 0;
}

/* --- Asymmetric pairs --- */

.w-pair {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: clamp(2rem, 6vh, 5rem);
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2.5rem, 6vw, 4rem);
  align-items: start;
}

@media (min-width: 900px) {
  /* Both panes are placed in the shared 12-column field, and both are biased
     to the SAME side, alternating per pair. Two panes on opposite edges — the
     previous 7fr/5fr treatment — cancel each other out: the measured centroid
     of every pair sat within 2% of the axis however unequal the columns were.
     Biased together, the pair genuinely leans, and the stagger comes from the
     size difference and the vertical drop rather than from the split. */
  .w-pair { grid-template-columns: repeat(12, 1fr); }
  .w-pair .is-lead  { grid-column: 1 / span 8; }
  .w-pair .is-trail { grid-column: 4 / span 4; }
  .w-pair:nth-of-type(even) .is-lead  { grid-column: 5 / span 8; }
  .w-pair:nth-of-type(even) .is-trail { grid-column: 9 / span 4; }
  .w-pair.is-single .is-lead { grid-column: 1 / span 8; }
  .w-pair:nth-of-type(even).is-single .is-lead { grid-column: 5 / span 8; }
  /* Vertical offset — the small pane drops, so the row staggers. */
  .w-pair .is-trail { margin-top: clamp(4rem, 12vh, 9rem); }
}

/* Phone keeps the swing: a single column of equal cards is exactly the "one
   long strip of identical widths" the brief rules out. The small pane is
   inset from alternating sides, so the eye still tracks left and right. */
@media (max-width: 899px) {
  /* Neither pane may be full width: a full-width block sits on the axis by
     definition, and a column of them is exactly the "one long strip of
     identical cards" the brief rules out. The lead pane takes 10 of 12
     columns from one side, the small pane 7 from the other, and the sides
     swap on every other pair. */
  .w-pair { grid-template-columns: repeat(12, 1fr); gap: clamp(1.6rem, 5vw, 3rem); }
  .w-pair .is-lead  { grid-column: 1 / span 10; }
  .w-pair .is-trail { grid-column: 2 / span 7; }
  .w-pair:nth-of-type(even) .is-lead  { grid-column: 3 / span 10; }
  .w-pair:nth-of-type(even) .is-trail { grid-column: 6 / span 7; }
  .w-pair.is-single .is-lead { grid-column: 1 / span 10; }
  .w-pair:nth-of-type(even).is-single .is-lead { grid-column: 3 / span 10; }
}

.w-pane-media {
  overflow: hidden;
  background: var(--linen-deep);
}
.w-pane-media img {
  width: 100%; height: auto;
  display: block;
  transition: transform .9s cubic-bezier(.22,.61,.36,1);
}
.w-pane a:hover .w-pane-media img { transform: scale(1.02); }
.w-pane .w-caption { padding-top: 1.2rem; }

/* --- Texture band: a close-up used as a divider --- */

.texture-band {
  position: relative;
  width: 100%;
  height: clamp(40vh, 50vh, 60vh);
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--soil);
  margin-block: clamp(3rem, 8vh, 7rem);
}

.texture-media { position: absolute; inset: 0; }
.texture-media picture, .texture-media img { width: 100%; height: 100%; display: block; }
.texture-media img { object-fit: cover; transform: scale(1.06); }

/* Same reasoning as the hero scrim: the caption is linen display type sitting
   directly on soil-and-ochre texture, so the veil has to be heavy enough to
   clear 4.5:1 against the brightest passage, not merely to "look moody". */
.texture-band::after {
  content: "";
  position: absolute; inset: 0;
  background: rgba(20,13,9,.74);
}

.texture-line {
  position: relative;
  z-index: 1;
  margin: 0;
  max-width: 30ch;
  padding-inline: var(--gutter);
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3.2vw, 2.2rem);
  line-height: 1.3;
  color: var(--linen);
}

/* --------------------------------------------------------------------------
   Dark band — About teaser
   -------------------------------------------------------------------------- */

.band-dark {
  background: #2a1f1a;
  color: rgba(246,241,232,.84);
  padding-block: clamp(4rem, 12vh, 9rem);
}
.band-dark p { color: rgba(246,241,232,.84); }
.band-dark a { color: var(--linen); }

.teaser-grid {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
@media (min-width: 860px) {
  /* The portrait moves to the right and the prose column widens, so this
     section's weight lands left (measured 45.0% at 1366px) — the opposite
     side from the "See every work" hand-off above it (66.3%) and from
     Contact below it (61.4%). Source order is unchanged (portrait first in
     the markup, which is also the reading order on a phone); only the
     placement swaps. */
  .teaser-grid { grid-template-columns: minmax(0, 7fr) minmax(0, 2fr); gap: clamp(2rem, 5vw, 3rem); }
  .teaser-portrait { grid-column: 2; grid-row: 1; }
  .teaser-body { grid-column: 1; grid-row: 1; }
}

/* Phone: the portrait is inset from the right so the column is not a stack of
   full-width blocks. */
@media (max-width: 859px) {
  /* Portrait reduced and pushed right, prose held off the right edge: the
     block leans the same way it does at desktop width instead of collapsing
     into a centred stack of full-width blocks. */
  .teaser-portrait { margin-inline-start: 42%; }
  .teaser-body > * { max-width: 76%; }
}

.teaser-portrait img {
  width: 100%; height: auto;
  display: block;
  filter: saturate(.96);
}

.teaser-quote {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.6vw, 2.5rem);
  line-height: 1.28;
  color: var(--linen);
  margin: 0 0 1.8rem;
  padding: 0;
  border: 0;
  quotes: "\201C" "\201D";
}
.teaser-quote::before { content: open-quote; }
.teaser-quote::after  { content: close-quote; }

.teaser-body p { max-width: 54ch; font-size: .98rem; }

/* --------------------------------------------------------------------------
   Home contact
   -------------------------------------------------------------------------- */

.home-contact {
  background: var(--linen-deep);
  padding-block: clamp(3.5rem, 10vh, 7rem);
}

.contact-inner {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The last swing of the page.

   The "More works" collage that used to sit above About is gone (the site now
   holds six works, so a collage previewing a big catalogue had nothing to
   show). Its right beat is carried by the new "See every work" hand-off, and
   the section-head sequence down the page is now

       pair-0 left / pair-1 right / "See every work" right /
       About left / Contact right

   measured at 1366px as 38.1 / 65.9 / 66.3 / 45.0 / 61.4 % — a change of side
   every one to two sections, and no two adjacent sections sharing a side.

   Contact used to hold the left with a narrow single column. It cannot any
   more: it now carries a form, a form needs two columns and a message box
   wide enough to write in, and a wide two-column block anchored left measures
   within a couple of points of the About band above it — the same side at the
   same weight, which is a repeat rather than a swing. So Contact takes the
   right instead, which is also the side the About band's portrait already
   points to. The heading keeps its own narrower measure so the section does
   not read as one solid right-hand slab.
*/
.home-contact .contact-inner > .sig-h {
  max-width: 34rem;
  margin-inline-start: auto;
  text-align: right;
}
.home-contact .contact-inner > .sig-h::after { margin-inline-start: auto; }
.home-contact .contact-split { max-width: 56rem; margin-inline-start: auto; }
@media (max-width: 899px) {
  /* Held off the left edge so the closing section still has a side, rather
     than collapsing to a centred full-width block. */
  .home-contact .contact-inner > .sig-h { max-width: 74%; }
  .home-contact .contact-split { max-width: 90%; }
  .home-contact .contact-large { grid-template-columns: 1fr; }
}

/* The two halves of a contact section: details + commission note on one side,
   the form on the other. One rule set serves the home page and the About
   page, since the generator emits the same markup in both. */
.contact-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 3.2rem);
  align-items: start;
}
@media (min-width: 860px) {
  /* Details left, form right. The form gets the wider column because a
     300px message box is not a usable message box; the section keeps its
     left lean from the split's own left anchoring, not from the column
     split. */
  .contact-split { grid-template-columns: minmax(0, 4fr) minmax(0, 5fr); }
}
.contact-aside > :last-child { margin-bottom: 0; }

.commission-note {
  margin-top: 1.6rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--burlap);
}
.commission-note p { max-width: 34ch; color: var(--ink-soft); margin: 0 0 1.1rem; }
.commission-note p:last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Contact form
   -------------------------------------------------------------------------- */

.contact-form { margin: 0; }

.form-intro {
  font-size: .78rem;
  color: var(--ink-faint);
  margin: 0 0 1.2rem;
}

.form-field { margin: 0 0 1.1rem; }  /* not `.field`: that name is the 12-column layout grid below */

.field-label {
  display: block;
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .35rem;
}
.field-req { color: var(--terracotta); margin-left: .25em; }

.field-input {
  font: inherit;
  font-size: 1rem;
  /* A percentage width plus a border overflows a grid column in every browser
     that does not inherit border-box here; the global reset does set it, and
     100% is then exactly the column. */
  width: 100%;
  max-width: 100%;
  color: var(--ink);
  background: var(--linen);
  border: 1px solid var(--burlap);
  border-radius: var(--radius);
  padding: .7rem .8rem;
  transition: border-color .18s ease, box-shadow .18s ease;
}
textarea.field-input { resize: vertical; min-height: 7.5rem; line-height: 1.5; }
.field-input:hover { border-color: var(--ink-faint); }
.field-input:focus-visible {
  outline: none;
  border-color: var(--terracotta);
  box-shadow: 0 0 0 3px rgba(147,72,42,.18);
}

.field-hint { font-size: .76rem; color: var(--ink-faint); margin: .35rem 0 0; }

/* Honeypot. Hidden from sight and from assistive technology, out of the tab
   order, and never autofilled — a human has no way to fill it, so anything
   in it is a bot. `display: none` is avoided: some bots skip those fields,
   which would defeat the trap. */
.field-decoy {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
  width: 1px; height: 1px;
  overflow: hidden;
}

.field-subject {
  border: 0;
  margin: 0 0 1.2rem;
  padding: 0;
}
.field-subject .field-label { margin-bottom: .45rem; padding: 0; }
/* The two options sit side by side where there is room and stack where there
   is not. Each label is an inline-flex box whose text cannot break, so on a
   390px phone (a ~315px measure) the pair measures within a few pixels of the
   column and one wider glyph run would push the row past it. A flex container
   with wrap makes that impossible: the second option drops to its own line
   instead of overflowing. */
.field-subject .subject-list {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.3rem;
}
.subject-opt {
  display: inline-flex;
  align-items: flex-start;
  gap: .45rem;
  font-size: .92rem;
  line-height: 1.35;
  color: var(--ink);
}
.subject-opt input { accent-color: var(--terracotta); margin-top: .18em; flex: 0 0 auto; }

.form-actions { margin: 0; }

.form-work-note {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--terracotta);
  background: var(--linen);
  border-left: 2px solid var(--terracotta);
  padding: .6rem .8rem;
  margin: 0 0 1.2rem;
  overflow-wrap: anywhere;
}

.form-status {
  margin: 1rem 0 0;
  font-size: .92rem;
  color: var(--ink-soft);
  min-height: 1.2em;
}
.form-status:empty { margin: 0; min-height: 0; }
.form-status.is-ok { color: #2f5d3a; }        /* 5.9:1 on --linen-deep */
.form-status.is-error { color: var(--terracotta-d); }

.form-alt {
  margin: 1.2rem 0 0;
  font-size: .82rem;
  color: var(--ink-faint);
}
.form-alt a { color: var(--terracotta); }

.display-h {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3.4rem);
  line-height: 1.05;
  margin: 0 0 2rem;
  color: var(--ink);
}

.contact-large {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.3rem 3rem;
  margin: 0 0 2.4rem;
  max-width: 54rem;
}
@media (min-width: 700px) { .contact-large { grid-template-columns: repeat(2, 1fr); } }

.contact-large dt {
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .25rem;
}
.contact-large dd {
  margin: 0 0 .4rem;
  font-size: 1.02rem;
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block;
  /* A button label must be able to wrap rather than push the page wider: the
     longest one on the site ("Start a commission enquiry", uppercase with
     .13em tracking) measures within a few pixels of a 390px phone's contact
     column. max-width keeps it inside its container and the wrap makes the
     label take a second line instead of overflowing. */
  max-width: 100%;
  white-space: normal;
  text-align: center;
  font-size: .8rem; letter-spacing: .13em; text-transform: uppercase;
  padding: .85rem 1.5rem;
  border-radius: var(--radius);
  text-decoration: none;
  border: 1px solid var(--terracotta);
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.btn-solid { background: var(--terracotta); color: var(--linen); }
.btn-solid:hover { background: var(--terracotta-d); border-color: var(--terracotta-d); color: var(--linen); }
.btn-ghost { background: transparent; color: var(--terracotta); }
.btn-ghost:hover { background: var(--terracotta); color: var(--linen); }

/* The ghost button on a dark band. This has to sit after .btn: the base rule
   sets `border` as a shorthand, so a variant that only sets border-color has
   to come later in the sheet to win. */
.btn-ghost-light {
  background: transparent;
  color: var(--linen);
  border-color: rgba(246,241,232,.5);
}
.btn-ghost-light:hover { background: var(--linen); color: var(--soil); border-color: var(--linen); }

/* --------------------------------------------------------------------------
   Work page — viewer
   -------------------------------------------------------------------------- */

.viewer-section {
  background: var(--soil);
  border-bottom: 1px solid var(--burlap);
}

.viewer-shell {
  position: relative;
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
}

/* A 1:2 upright fitted into a 1600px-wide frame leaves ~74% of the row empty
   at desktop width. Narrow the shell for uprights only; panoramas and
   standard ratios keep the full width. */
@media (min-width: 1020px) {
  .viewer-section.is-upright .viewer-shell { max-width: 900px; }
}

.viewer-frame {
  width: 100%;
  /* Default (upright / squarish works): tall but never taller than the screen. */
  height: min(74vh, 720px);
  min-height: 340px;
  background: var(--soil);
}

/* Panoramas: a 3.5:1 work fitted to width in a tall box wastes the frame.
   Keep the frame shorter so the strip fills it, and let the user pan. */
.viewer-section.is-panorama .viewer-frame {
  height: min(58vh, 560px);
  min-height: 260px;
}

/* Upright 1:2 works benefit from all the height we can give them. */
.viewer-section.is-upright .viewer-frame {
  height: min(80vh, 780px);
}

@media (max-width: 719px) {
  .viewer-frame { height: min(62vh, 480px); min-height: 280px; }
  .viewer-section.is-panorama .viewer-frame { height: min(46vh, 340px); min-height: 210px; }
  .viewer-section.is-upright .viewer-frame { height: min(70vh, 540px); }
}

/* OpenSeadragon paints into this element; keep its canvas from overflowing. */
.viewer-frame canvas, .viewer-frame .openseadragon-canvas { outline: none; }

/* `display: grid` would otherwise defeat the `hidden` attribute that keeps
   this block out of the page while the zoom viewer is working. */
.viewer-fallback[hidden] { display: none !important; }

.viewer-fallback {
  display: grid; place-items: center;
  padding: clamp(1rem, 4vw, 2.5rem);
  background: var(--soil);
}
.viewer-fallback img {
  max-height: 70vh; width: auto; max-width: 100%;
  box-shadow: 0 10px 40px rgba(0,0,0,.35);
}
.viewer-fallback-img {
  max-height: 70vh; width: auto; max-width: 100%;
  margin-inline: auto;
}
noscript .viewer-fallback-img { padding: 1.5rem; }

.viewer-hint {
  margin: 0;
  padding: .7rem var(--gutter);
  text-align: center;
  font-size: .74rem; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(246,241,232,.72);
  background: var(--soil);
}

/* Zoom controls. OpenSeadragon's own toolbar is switched off (see viewer.js)
   and these are bound to it by element id, so they carry the site's small-caps
   button language instead of the vendor's grey icons. They sit on the soil
   ground under the frame, on the same row as the hint. */
.viewer-controls {
  display: flex;
  gap: .5rem;
  justify-content: center;
  padding: 0 var(--gutter) .9rem;
  background: var(--soil);
}

.viewer-btn {
  min-width: 2.6rem;
  padding: .5rem .8rem;
  font-family: var(--font-ui);
  font-size: .8rem; letter-spacing: .12em; text-transform: uppercase;
  line-height: 1;
  color: var(--linen);
  background: transparent;
  border: 1px solid rgba(246,241,232,.45);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.viewer-btn-wide { min-width: 5rem; }
.viewer-btn:hover,
.viewer-btn:focus-visible {
  background: var(--linen); color: var(--soil); border-color: var(--linen);
}

/* OSD's own control buttons sit on the dark matte. */
.viewer-frame .openseadragon-container + div { z-index: 5; }

/* --------------------------------------------------------------------------
   Work page — body
   -------------------------------------------------------------------------- */

.work-body {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 3.5rem);
  padding-block: clamp(2.25rem, 6vw, 4rem);
  align-items: start;
}
@media (min-width: 900px) {
  .work-body { grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); }
}

.work-head { margin-bottom: 1.9rem; }

.work-eyebrow {
  font-size: .72rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .7rem;
}

.work-title {
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 5.6vw, 3.2rem);
  letter-spacing: -.01em;
}

.work-yoruba {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.15rem, 3vw, 1.5rem);
  color: var(--terracotta);
  margin: .25rem 0 0;
}

.work-subtitle {
  font-size: .95rem;
  color: var(--ink-soft);
  margin: .8rem 0 0;
  max-width: 46ch;
}

.work-main .prose-p,
.prose p {
  max-width: 68ch;
  color: var(--ink-soft);
}

.work-main .prose-p:first-of-type {
  font-size: 1.075rem;
  color: var(--ink);
}

.work-medium-note {
  margin-top: 2.4rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--burlap);
}
.work-medium-note p {
  max-width: 68ch;
  font-size: .93rem;
  color: var(--ink-faint);
  margin: 0;
}

/* Aside */

.work-aside {
  background: var(--linen-deep);
  border: 1px solid var(--burlap);
  border-radius: var(--radius);
  padding: clamp(1.2rem, 3vw, 1.75rem);
}
@media (min-width: 900px) {
  .work-aside { position: sticky; top: 88px; }
}

.spec {
  margin: 0 0 1.5rem;
  display: grid;
  grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
  gap: .1rem 1rem;
}
.spec dt {
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-faint);
  padding-block: .55rem;
  border-top: 1px solid var(--burlap);
}
.spec dd {
  margin: 0;
  font-size: .93rem;
  padding-block: .55rem;
  border-top: 1px solid var(--burlap);
}
.spec dt:first-of-type, .spec dd:nth-of-type(1) { border-top: 0; }

.spec-price {
  font-family: var(--font-display);
  font-size: 1.2rem !important;
  color: var(--ink);
}

.variants {
  margin: 0 0 1.5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--burlap);
}
.variant {
  margin-bottom: 1.1rem;
}
.variant:last-child { margin-bottom: 0; }
.variant-label {
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .25rem;
}
.variant-line {
  margin: 0;
  font-size: .93rem;
}
.variant-price {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--ink);
}
.variant-note {
  margin: .15rem 0 0;
  font-size: .84rem;
  color: var(--ink-faint);
}
.variant-media { margin-top: .8rem; }
.variant-media img {
  width: 100%; height: auto;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

.work-aside .btn { display: block; text-align: center; }

.aside-note {
  margin: .85rem 0 0;
  font-size: .8rem;
  color: var(--ink-faint);
}

/* Work prev/next */

.work-nav {
  border-top: 1px solid var(--burlap);
  background: var(--linen-deep);
}
.work-nav-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding-block: 1.5rem;
  flex-wrap: wrap;
}
/* Desktop: three real columns so "All works" is centred and can never collide
   with the prev/next labels, whatever the work titles are. A missing prev or
   next still leaves its column, keeping the centre in the centre. */
@media (min-width: 700px) {
  .work-nav-inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
}
.work-nav a { text-decoration: none; }
.work-nav-dir {
  display: block;
  font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .12rem;
}
.work-nav-name {
  font-family: var(--font-display);
  font-size: 1.2rem;
  color: var(--ink);
}
.work-nav-prev { text-align: left; }
.work-nav-next { text-align: right; margin-left: auto; }
@media (min-width: 700px) {
  .work-nav-prev { grid-column: 1; }
  .work-nav-next { grid-column: 3; margin-left: 0; }
}
.work-nav a:hover .work-nav-name { color: var(--terracotta); }
.work-nav-all {
  font-size: .72rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-faint);
  order: 3; width: 100%; text-align: center;
  padding-top: .9rem;
  border-top: 1px solid var(--burlap);
}
@media (min-width: 700px) {
  .work-nav-all { order: 0; width: auto; padding-top: 0; border-top: 0; grid-column: 2; grid-row: 1; }
}
.work-nav-all:hover { color: var(--terracotta); }

/* --------------------------------------------------------------------------
   About page
   -------------------------------------------------------------------------- */

.about-hero {
  padding-block: clamp(2.5rem, 7vw, 5rem) clamp(2rem, 5vw, 3.5rem);
  border-bottom: 1px solid var(--burlap);
}

/* The About intro reuses the home hero's type classes (.hero-eyebrow,
   .hero-tagline) so the two pages speak the same language, but those classes
   are defined in near-white for the home page's dark scrim. On this linen
   ground they were invisible (measured 1.04:1 and 1.05:1). Restate the colour
   for the light ground here, using the same ink tokens the rest of the page
   uses, rather than forking the type scale. */
.about-hero .hero-eyebrow { color: var(--ink-soft); }
.about-hero .hero-tagline { color: var(--ink); }
/* The client asked for the signing name to lead: "About: under my name, make
   Okaybabs to be bigger" (2026-09-17). The legal name becomes the smaller
   line above and "Okaybabs" the display line under it, at 1.85x the name's
   size at every breakpoint:
       name  clamp(1.15rem, 2.7vw, 1.85rem)
       sig   clamp(2.15rem, 5.0vw, 3.45rem)   -> 1.87x / 1.85x / 1.86x
   so the ratio holds on a phone as well as on a desktop rather than only at
   one width. */
.about-hero .hero-title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.7vw, 1.85rem);
  line-height: 1.15;
  letter-spacing: .01em;
  margin: 0;
  color: var(--ink-soft);
}
.about-hero .hero-sig {
  font-family: var(--font-display);
  font-size: clamp(2.15rem, 5vw, 3.45rem);
  line-height: 1.02;
  color: var(--terracotta);
  margin: .1rem 0 0;
  overflow-wrap: anywhere;
}
.about-hero-inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}
@media (min-width: 760px) {
  .about-hero-inner { grid-template-columns: minmax(0, 280px) minmax(0, 1fr); }
}
.about-portrait { max-width: 320px; }
.about-portrait img {
  width: 100%; height: auto;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

.about-body { padding-block: clamp(2.25rem, 6vw, 4rem); }
.about-body > section { margin-bottom: clamp(2.5rem, 6vw, 4rem); }
.about-body > section:last-child { margin-bottom: 0; }

.prose p { max-width: 68ch; color: var(--ink-soft); }

.testimony-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}
@media (min-width: 700px) { .testimony-list { grid-template-columns: repeat(2, 1fr); } }

.testimony-list li {
  font-family: var(--font-display);
  font-size: 1.2rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--linen-deep);
  border-left: 2px solid var(--terracotta);
  padding: 1.1rem 1.3rem;
  border-radius: var(--radius);
}

.statement { background: var(--linen-deep); border-radius: var(--radius); padding: clamp(1.4rem, 4vw, 2.4rem); }
.statement .section-h { margin-top: 0; }
.statement-sign {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.4rem;
  color: var(--terracotta);
  margin: 0;
}

.contact-spec { max-width: 40rem; margin-bottom: 1.6rem; }
@media (max-width: 520px) {
  .spec { grid-template-columns: 1fr; gap: 0; }
  .spec dd { border-top: 0; padding-top: 0; padding-bottom: .7rem; }
  .spec dt { padding-bottom: .1rem; }
  .spec dt:first-of-type { border-top: 0; }
  .spec dd:nth-of-type(1) { border-top: 0; }
}

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

.site-footer {
  background: var(--soil);
  color: rgba(246,241,232,.82);
  margin-top: auto;
  padding-top: clamp(2.5rem, 6vw, 4rem);
}
.site-footer a { color: rgba(246,241,232,.95); text-decoration: none; border-bottom: 1px solid rgba(246,241,232,.28); }
.site-footer a:hover { color: #fff; border-bottom-color: rgba(246,241,232,.8); }

.footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  padding-bottom: 2.5rem;
}
@media (min-width: 760px) {
  .footer-grid { grid-template-columns: 2fr 1fr 1fr; gap: 3rem; }
}

.footer-name {
  font-family: var(--font-display);
  font-size: 1.6rem;
  color: var(--linen);
  margin: 0 0 .5rem;
}
.footer-tag {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  max-width: 32ch;
  margin: 0;
  color: rgba(246,241,232,.72);
}

.footer-h {
  font-size: .68rem; letter-spacing: .2em; text-transform: uppercase;
  color: rgba(246,241,232,.55);
  margin: 0 0 .85rem;
  font-weight: 500;
}
.footer-list li { margin-bottom: .55rem; font-size: .92rem; line-height: 1.5; }

/* Contact block: label above / inline with value, mirroring the format the
   client supplied (ADDRESS / PHONE / HOURS / E-MAIL). */
.contact-lines li { margin-bottom: .7rem; }
.contact-label {
  display: block;
  font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  color: rgba(246,241,232,.5);
}

.footer-base {
  border-top: 1px solid rgba(246,241,232,.16);
  padding-block: 1.4rem;
}
.footer-base p {
  margin: 0;
  font-size: .78rem;
  color: rgba(246,241,232,.5);
}

/* --------------------------------------------------------------------------
   Layout spine — sticky footer
   -------------------------------------------------------------------------- */

body { display: flex; flex-direction: column; min-height: 100vh; }
main { flex: 1 0 auto; }

/* Any table / pre that might appear stays inside its own scroller. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Hero eyebrow: movement on line one, the school on a quieter second line. */
.eyebrow-sub {
  display: block;
  margin-top: .3rem;
  color: rgba(246,241,232,.62);
  letter-spacing: .18em;
}

/* ==========================================================================
   B2 — full catalogue
   --------------------------------------------------------------------------
   One rhythm language for the whole site. Every section places its content in
   the same 12-column field and picks from a small fixed set of spans, so the
   page swings left and right on a regular beat instead of inventing a new
   layout each time. Nothing is centred by default and nothing is uniformly
   left-aligned: weight alternates, and full-bleed bands act as the centred
   punctuation between swings.
   ========================================================================== */

:root {
  --beat: clamp(3rem, 9vh, 7rem);   /* vertical rhythm between sections */
}

/* The shared 12-column field. */
.field {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(1rem, 2.4vw, 2rem);
}

/* The fixed span set — the only widths any section may use. */
.span-left   { grid-column: 1 / span 7; }
.span-right  { grid-column: 6 / span 7; }
.span-narrow-left  { grid-column: 1 / span 5; }
.span-narrow-right { grid-column: 8 / span 5; }
.span-full   { grid-column: 1 / span 12; }

@media (max-width: 899px) {
  .span-left, .span-right, .span-narrow-left,
  .span-narrow-right, .span-full { grid-column: 1 / span 12; }
}

/* --------------------------------------------------------------------------
   Signature-style headings

   The artist signs every canvas "okaybabs" in a loose hand. Section titles
   pick that up: an oversized italic serif with a hand-drawn rule beneath,
   set against very small caps labels for deliberate scale contrast.
   -------------------------------------------------------------------------- */

.sig-h {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(2.2rem, 6vw, 4.5rem);
  line-height: 1.02;
  letter-spacing: -.02em;
  margin: 0 0 .5rem;
}

.sig-h::after {
  content: "";
  display: block;
  width: clamp(64px, 12vw, 140px);
  height: 2px;
  margin-top: .7rem;
  background: var(--terracotta);
  /* A slightly uneven rule, closer to a drawn stroke than a border. */
  clip-path: polygon(0 40%, 12% 0, 34% 55%, 61% 8%, 84% 62%, 100% 18%, 100% 100%, 0 100%);
}

.label-sm {
  font-size: .66rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .9rem;
}

/* --------------------------------------------------------------------------
   Works index
   -------------------------------------------------------------------------- */

.notfound-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1.6rem; }

.index-head { padding-block: calc(var(--beat) * .9) calc(var(--beat) * .4); }
/* Anchored left, with the lede indented under it — the first swing of the
   left/right rhythm the rest of the page continues. */
.index-head-inner { max-width: 46rem; }
.index-head .sig-h { margin-left: -.04em; }
.index-lede {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 2.2vw, 1.45rem);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 34ch;
  margin: 1rem 0 0;
  padding-left: clamp(0px, 4vw, 3.5rem);
}

/* Index grid — staggered, not a flat matrix.

   A dense auto-flow grid with per-card spans keeps the eye moving: wide works
   take six columns, tall works take three and drop, and every third card is
   nudged down so rows never line up into a rigid ledger. */

.index-grid-wrap { padding-block: calc(var(--beat) * .6) var(--beat); }

.index-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(1.6rem, 3.2vw, 3rem) clamp(1rem, 2.4vw, 2rem);
  grid-auto-flow: dense;
}

.index-card { grid-column: span 4; }
.index-card.is-wide { grid-column: 1 / span 8; }
.index-card.is-tall { grid-column: span 3; }

/* Positional beats (see INDEX_RHYTHM in build-pages.py). Proportion still
   decides .is-wide / .is-tall; these carry the list when the catalogue runs a
   hundred cards of the same shape. The wide beats start from alternating
   columns so the extra width swings left and right instead of always opening
   from the same edge. */
.index-card.beat-wide-l { grid-column: 1 / span 8; }
.index-card.beat-wide-r { grid-column: 5 / span 8; }
.index-card.beat-tall-l { grid-column: 1 / span 3; }
.index-card.beat-tall-r { grid-column: 10 / span 3; }

/* Which edge a wide card opens from, assigned by wide_side_map() in
   build-pages.py so consecutive wide cards alternate. These must come after
   .is-wide / .beat-wide-* to win: a 3.5:1 panorama is .is-wide by proportion
   and the side class is what decides whether it opens from column 1 or 5. */
.index-card.is-wide-l { grid-column: 1 / span 8; }
.index-card.is-wide-r { grid-column: 5 / span 8; }

/* The swing: alternate cards ride lower, so the grid reads as a hang. */
.index-card:nth-child(3n + 2) { margin-top: clamp(1rem, 4vh, 3.2rem); }
.index-card:nth-child(4n + 4) { margin-top: clamp(.5rem, 2vh, 1.6rem); }

@media (max-width: 1019px) {
  .index-card { grid-column: span 6; }
  .index-card.is-wide { grid-column: 1 / span 12; }
  .index-card.is-tall { grid-column: span 6; }
  .index-card.beat-wide-l { grid-column: 1 / span 8; }
  .index-card.beat-wide-r { grid-column: 5 / span 8; }
  .index-card.beat-tall-l { grid-column: 1 / span 5; }
  .index-card.beat-tall-r { grid-column: 8 / span 5; }
  .index-card.is-wide-l { grid-column: 1 / span 9; }
  .index-card.is-wide-r { grid-column: 4 / span 9; }
}

@media (max-width: 619px) {
  /* Phone keeps the rhythm. The old rule set every card to span 12, which
     made `margin-left: auto` on the offset cards a no-op — a full-width card
     has nowhere to move — so the list flattened into one column of identical
     widths. Cards now take 10 of 12 columns and alternate which side they
     start from, and the wide beats take the full 12 as the punctuation. */
  .index-grid { gap: 1.8rem 1rem; }
  .index-card { grid-column: 1 / span 10; }
  .index-card.is-tall { grid-column: 1 / span 7; }
  .index-card.beat-tall-l { grid-column: 1 / span 7; }
  .index-card.beat-tall-r { grid-column: 6 / span 7; }
  .index-card.is-wide,
  .index-card.beat-wide-l,
  .index-card.beat-wide-r { grid-column: 1 / span 12; }
  /* Three of the six works land in the wide slot (two are real 3.5:1
     panoramas), and at full width they would all sit dead centre — three
     identical 50% blocks in a row. The side classes inset them from
     alternating edges so the phone column swings like the desktop grid, while
     still reading much wider than the tall cards next to them. */
  .index-card.is-wide-l { grid-column: 1 / span 11; }
  .index-card.is-wide-r { grid-column: 2 / span 11; }
  .index-card:nth-child(3n + 2) { margin-top: .4rem; grid-column: 3 / span 10; }
  .index-card:nth-child(3n + 2).is-tall { grid-column: 6 / span 7; }
  .index-card:nth-child(3n + 2).beat-tall-l { grid-column: 4 / span 7; }
  .index-card:nth-child(3n + 2).beat-tall-r { grid-column: 6 / span 7; }
  .index-card:nth-child(3n + 2).is-wide,
  .index-card:nth-child(3n + 2).beat-wide-l,
  .index-card:nth-child(3n + 2).beat-wide-r { grid-column: 1 / span 12; }
  .index-card:nth-child(3n + 2).is-wide-l { grid-column: 1 / span 11; }
  .index-card:nth-child(3n + 2).is-wide-r { grid-column: 2 / span 11; }
  .index-card:nth-child(4n + 4) { margin-top: .2rem; }
}

.index-card a { display: block; text-decoration: none; color: inherit; }

.index-media {
  position: relative;
  overflow: hidden;
  background: var(--linen-deep);
}
.index-media img {
  width: 100%; height: auto; display: block;
  transition: transform .8s cubic-bezier(.22,.61,.36,1);
}
.index-card a:hover .index-media img { transform: scale(1.03); }

.index-zoom-badge {
  position: absolute; left: 10px; bottom: 10px;
  background: rgba(20,13,9,.78);
  color: var(--linen);
  font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
  padding: .28rem .55rem;
}

/* A single unbroken word (a long Yoruba title, a pasted URL) must not push a
   card or the work page wider than its column. */
.work-title, .index-title, .w-title { overflow-wrap: anywhere; }

.index-title {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1.9vw, 1.5rem);
  line-height: 1.15;
  margin: .75rem 0 0;
}
.index-yoruba {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--terracotta);
  font-size: .98rem;
  margin: .1rem 0 0;
}
.index-meta {
  font-size: .78rem;
  color: var(--ink-faint);
  margin: .3rem 0 0;
}

/* --------------------------------------------------------------------------
   Home — "See every work" hand-off to the index

   This replaces the "More works" collage. The collage existed to show a slice
   of a 144-work catalogue; with six works on the site there is nothing left
   to preview, so the section is a right-swung head and one link. It keeps the
   right beat the collage held, so the page still alternates
   left / right / right / left down its section heads.
   -------------------------------------------------------------------------- */

.works-cta {
  padding-block: calc(var(--beat) * .7);
  background: var(--linen);
}
.works-cta-inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.2rem, 3vh, 2rem);
}
.works-cta-head {
  max-width: 40rem;
  margin-inline-start: auto;
  text-align: right;
}
.works-cta-head .sig-h::after { margin-inline-start: auto; }
.works-cta-actions { margin: 0; text-align: right; }
@media (max-width: 699px) {
  .works-cta-head { max-width: 82%; }
}

/* --------------------------------------------------------------------------
   Work page — tags, view strip
   -------------------------------------------------------------------------- */

.work-tags {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin: 1rem 0 0;
}
.tag {
  font-size: .64rem; letter-spacing: .16em; text-transform: uppercase;
  padding: .3rem .7rem;
  border: 1px solid var(--burlap);
  border-radius: 999px;
  color: var(--ink-faint);
}
.tag-series { border-color: var(--terracotta); color: var(--terracotta); }

.prose-credit {
  margin-top: 1.2rem;
  font-size: .82rem;
  color: var(--ink-faint);
  font-style: italic;
}

/* View strip under the viewer */

.view-strip {
  display: flex;
  gap: .6rem;
  padding: .8rem var(--gutter);
  background: var(--soil);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.view-thumb {
  flex: 0 0 auto;
  /* Wide enough for the longest caption the generator can emit ("Full view",
     "Detail 1") at .58rem/.12em tracking without wrapping or clipping. */
  width: 86px;
  padding: 0 0 .1rem;
  border: 1px solid rgba(246,241,232,.25);
  background: none;
  cursor: pointer;
  /* line-height stays normal for the caption; the image is display:block, so
     it does not pick up a text baseline gap. */
  line-height: 1.2;
  transition: border-color .2s ease, opacity .2s ease;
  opacity: .72;
}
.view-thumb img { width: 100%; height: 56px; object-fit: cover; display: block; }

/* Caption under each thumbnail ("Full view", "Framed", "Detail 1"...). The
   client asked for close-ups on four works; unlabelled 56px crops of the same
   painting are indistinguishable, so each view says what it is. */
.view-label {
  display: block;
  font-family: var(--font-ui);
  font-size: .56rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(246,241,232,.72);
  padding: .3rem .15rem .1rem;
  text-align: center;
  /* A client-supplied label could be long; clip it rather than let it stretch
     the strip or wrap to three lines. The full text is in the aria-label. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.view-thumb.is-current .view-label { color: var(--linen); }
.view-thumb:hover { opacity: 1; border-color: rgba(246,241,232,.6); }
.view-thumb.is-current { opacity: 1; border-color: var(--linen); }

/* --------------------------------------------------------------------------
   Heading utilities still referenced by the templates.

   These were lost when the work-grid section was rewritten as the editorial
   layout; restored here so `.minor-h` ("On the materials", "Available from
   the studio") and `.section-h` render as the small-caps labels they are
   meant to be rather than as body text.
   -------------------------------------------------------------------------- */

.minor-h {
  font-size: .68rem;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .7rem;
  line-height: 1.4;
}

.section-h {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.4vw, 2.05rem);
  margin: 0 0 1.4rem;
  padding-bottom: .55rem;
  border-bottom: 1px solid var(--burlap);
}

.display-h {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3.4rem);
  line-height: 1.05;
  margin: 0 0 2rem;
  color: var(--ink);
}

/* Buttons must never inherit the global link underline. */
.btn, .hero-cta, .work-nav a, .index-card a {
  text-decoration: none;
}
