/* ============================================================================
   Katharina Hindes — Atelier
   Palette sampled from the paintings themselves: the deep teal-greens of
   "Justitia", gold leaf, the coral and magenta blooms, the near-black of
   "Eternity". No colour here is invented; every value has a canvas behind it.
   ========================================================================== */

/* ---------- Schriften — selbst ausgeliefert ----------
   Both families are served from assets/fonts/, NOT from Google's CDN. Loading
   them from fonts.gstatic.com transmits every visitor's IP address to Google,
   which without consent is a data-protection problem in Germany (LG München,
   Az. 3 O 17493/20). Self-hosting removes the request entirely.

   Cormorant Garamond and Inter are both SIL OFL 1.1; the licences travel with
   the files (assets/fonts/OFL-*.txt), as that licence requires.

   These are the variable fonts Google serves, so ONE file covers a whole
   weight axis: `font-weight: 300 700` tells the browser it may interpolate any
   weight in that range from this single file. The stylesheet uses 300 and 400
   of Cormorant and 400 and 600 of Inter — all four come from these files.

   Only the `latin` and `latin-ext` subsets are shipped. Google also offers
   cyrillic, greek and vietnamese; all 92 distinct characters on this site were
   checked against latin+latin-ext and every one is covered, so the other
   subsets would be dead weight. If a future text needs Greek or Cyrillic
   (a title, a quotation), the missing subset has to be added here.
   ------------------------------------------------------------------------- */

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-italic-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-italic-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/inter-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Ground — the dark, wet teal her canvases are built on */
  --abyss: #0b1416;
  --deep: #12262a;
  --teal: #1c3d42;
  --teal-mid: #2f5c60;
  --patina: #6d9490;

  /* Light — aged canvas, not white */
  --linen: #f4efe6;
  --linen-dim: #d9d1c4;
  --mist: #a8b8b4;

  /* Accents — gold leaf, the blooms */
  --gold: #c9a227;
  --gold-soft: #e0c264;
  --coral: #d96e4a;
  --magenta: #b8446b;

  /* Type — a serif with weight for her voice, clean sans for structure */
  --display: "Cormorant Garamond", "Times New Roman", ui-serif, Georgia, serif;
  --body:
    "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Scale — 1.333 (perfect fourth): dramatic, suits a gallery */
  --s-0: 1rem;
  --s-1: 1.333rem;
  --s-2: 1.777rem;
  --s-3: 2.369rem;
  --s-4: 3.157rem;
  --s-5: 4.209rem;
  --s-6: 5.61rem;

  /* Small type — the 1.333 scale has no step below 1rem, so every small
     label had been improvised (five different ad-hoc sizes). Two steps down
     from --s-0 replace them. */
  --s-sm: 0.875rem;
  --s-xs: 0.75rem;

  /* Space — 8px base. The two largest steps are fluid: at a fixed 9rem the
     desktop rhythm reached the phone unchanged and produced ~290px of dead
     air between sections on a 390px screen. */
  --sp-1: 0.5rem;
  --sp-2: 1rem;
  --sp-3: 1.5rem;
  --sp-4: 2.5rem;
  --sp-5: 4rem;
  --sp-6: clamp(3rem, 9vw, 6rem);
  --sp-7: clamp(4rem, 12vw, 9rem);

  /* 1ch is the width of "0", far wider than the mean lowercase glyph, so
     62ch measured ~81 real characters — above the comfortable 45–75 range,
     and German compounds make it worse. 48ch lands at ~63. */
  --measure: 48ch;
  --edge: clamp(1.5rem, 5vw, 5rem);
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* Height reserved for the fixed header, so anchor jumps do not hide the
     heading they targeted. */
  --header-h: 5.5rem;
}

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

html {
  scroll-behavior: smooth;
  /* Without this every in-page jump lands under the fixed header. */
  scroll-padding-top: var(--header-h);
}

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

body {
  margin: 0;
  background: var(--abyss);
  color: var(--linen);
  font-family: var(--body);
  font-size: var(--s-0);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

/* ---------- Accessibility ---------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--gold);
  color: var(--abyss);
  padding: var(--sp-1) var(--sp-2);
  font-weight: 600;
}

.skip-link:focus {
  left: var(--sp-2);
  top: var(--sp-2);
}

:focus-visible {
  outline: 2px solid var(--gold-soft);
  outline-offset: 4px;
  border-radius: 2px;
}

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

/* ---------- Reveal on scroll (opt-in, degrades to visible) ---------- */

/* Hidden only once JS confirms it can reveal them again. Without the .js-on
   guard a visitor with JavaScript disabled (or a failed script load) sees an
   empty page — the artwork must never depend on the animation. */
.js-on .reveal {
  opacity: 0;
  transform: translateY(1.75rem);
  transition:
    opacity 1s var(--ease),
    transform 1s var(--ease);
}

.js-on .reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js-on .reveal {
    opacity: 1;
    transform: none;
  }
}

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

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--edge);
  transition:
    background 0.5s var(--ease),
    padding 0.5s var(--ease);
}

.site-header.is-scrolled {
  background: rgba(11, 20, 22, 0.88);
  backdrop-filter: blur(14px);
  padding-block: var(--sp-2);
  border-bottom: 1px solid rgba(201, 162, 39, 0.18);
}

.wordmark {
  /* The home link is a primary control; give it the 44px tap minimum.
     `gap` restores the word space that flex layout collapses between the
     text node and the <span>. */
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  min-height: 2.75rem;
  font-family: var(--display);
  font-size: var(--s-1);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--linen);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark span {
  color: var(--gold);
}

.site-nav ul {
  display: flex;
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav a {
  display: inline-flex;
  align-items: center;
  /* 44px minimum applies at every viewport, not only on phones. */
  min-height: 2.75rem;
  color: var(--linen-dim);
  text-decoration: none;
  font-size: var(--s-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  position: relative;
}

.site-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  /* The link is now 44px tall for tapping; keep the rule close to the text
     rather than at the bottom of the tap area. */
  bottom: 0.75rem;
  width: 0;
  height: 1px;
  background: var(--gold);
  transition: width 0.4s var(--ease);
}

.site-nav a:hover::after,
.site-nav a[aria-current="page"]::after {
  width: 100%;
}

.site-nav a[aria-current="page"] {
  color: var(--linen);
}

/* ---------- Mobile navigation ----------
   Five items need ~390px of text; a 390px phone offers ~330px beside the
   wordmark, so the row was clipped (body's overflow-x: hidden hid it rather
   than fixing it). Below 48rem the nav becomes a disclosure panel driven by a
   checkbox — no JavaScript, so it works with scripts disabled. */

.nav-toggle,
.nav-toggle-label {
  display: none;
}

@media (max-width: 48rem) {
  .site-header {
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
    /* The panel must not push page content down, and the bar needs its own
       ground once it can open over the page. */
    background: rgba(11, 20, 22, 0.92);
    backdrop-filter: blur(14px);
    padding-block: var(--sp-2);
    /* Row gap only while the panel is open. As a plain `gap` it also applied to
       the collapsed nav row, so the shut bar carried 16px of dead space under
       the wordmark: 16 + 44 + 16 + 16 = 92px for a 44px control. */
    row-gap: 0;
  }

  .nav-toggle:checked ~ .site-nav {
    margin-top: var(--sp-2);
  }

  /* .is-scrolled exists so the desktop bar gains a ground once it leaves the
     painting. Here the bar already has one, so the scrolled state only did harm:
     it *lowered* the opacity 0.92 -> 0.88 and added a 1px border that grew the
     fixed header 101px -> 102px mid-scroll, dragging the whole page by a pixel.
     Needs the .is-scrolled specificity (0,2,0) to win — the bare .site-header
     above cannot override it at any position in the file. */
  .site-header.is-scrolled {
    background: rgba(11, 20, 22, 0.92);
    border-bottom: 0;
  }

  .wordmark {
    font-size: var(--s-0);
  }

  .nav-toggle-label {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    /* 44px minimum tap target */
    min-height: 2.75rem;
    padding: 0 0.25rem;
    margin-left: auto;
    color: var(--linen-dim);
    font-size: var(--s-xs);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    cursor: pointer;
  }

  .nav-toggle-bars {
    position: relative;
    width: 1.375rem;
    height: 1px;
    background: var(--gold);
    box-shadow:
      0 -6px 0 var(--gold),
      0 6px 0 var(--gold);
  }

  .nav-toggle:checked + .nav-toggle-label .nav-toggle-bars {
    background: var(--gold-soft);
    box-shadow: none;
  }

  .nav-toggle:focus-visible + .nav-toggle-label {
    outline: 2px solid var(--gold-soft);
    outline-offset: 4px;
  }

  .site-nav {
    flex-basis: 100%;
    display: grid;
    grid-template-rows: 0fr;
    overflow: hidden;
    transition: grid-template-rows 0.4s var(--ease);
  }

  .nav-toggle:checked ~ .site-nav {
    grid-template-rows: 1fr;
  }

  .site-nav ul {
    display: block;
    min-height: 0;
    /* Opaque: the panel opens over page content, which otherwise reads
       through the list. */
    background: var(--abyss);
    /* No border-top or padding here. `grid-template-rows: 0fr` collapses the
       row, but a border and padding on the row's own box are not overflow, so
       `overflow: hidden` cannot clip them: the shut menu kept a 9px gold strip
       inside the fixed header, which rode along on scroll and read as a stray
       rule over the page. Both move onto the items, which the 0fr row does
       collapse. */
  }

  /* A rule per item read as a dense table. One hairline separates the panel
     from the bar; the items are then told apart by space, which is what the
     rest of the site does. The rule sits on the first item rather than on the
     <ul>, because a collapsed 0fr row cannot clip its own border. */
  .site-nav li:first-child {
    border-top: 1px solid rgba(201, 162, 39, 0.16);
    margin-bottom: var(--sp-1);
  }

  .site-nav li + li {
    margin-bottom: var(--sp-1);
  }

  .site-nav a {
    display: block;
    /* 44px minimum tap target; was 28px */
    padding-block: 0.8125rem;
    font-size: var(--s-sm);
  }

  /* The underline affordance makes no sense on a stacked list. */
  .site-nav a::after {
    display: none;
  }

  .site-nav a[aria-current="page"] {
    color: var(--gold-soft);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav {
    transition: none;
  }
}

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

.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-items: center;
  justify-items: start;
  overflow: hidden;
  isolation: isolate;
}

.hero-canvas {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.hero-canvas img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 24%;
  filter: saturate(1.05);
}

/* Vignette — lets her colour through while keeping text legible.
   Measured: the darkest overlay stop yields 8.9:1 for --linen text. */
.hero-canvas::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to right,
      rgba(11, 20, 22, 0.92) 0%,
      rgba(11, 20, 22, 0.72) 42%,
      rgba(11, 20, 22, 0.28) 72%,
      rgba(11, 20, 22, 0.15) 100%
    ),
    linear-gradient(to bottom, rgba(11, 20, 22, 0.6) 0%, transparent 22%),
    linear-gradient(to top, rgba(11, 20, 22, 0.8) 0%, transparent 26%);
}

/* Text on the left over the darkened side; the painting reads on the right.
   Centering it forced a heavy all-over scrim that turned her canvas into
   texture — the whole point is that visitors see the work immediately. */
.hero-inner {
  text-align: left;
  padding: var(--sp-7) var(--edge) var(--sp-6);
  max-width: 34rem;
}

/* At mobile the text no longer sits on the painting. The band occupies the top
   of the hero at full colour, the words sit below it on solid --abyss, and the
   scrim is gone: a gradient strong enough to carry the header in the 48-52rem
   gap (where .site-header has no background of its own) measured 11rem at 0.97
   alpha, which is not a vignette any more. Reserving header height on .hero
   instead puts the header over solid ground — 16.29:1 for --linen, flat from
   360px to 832px. */
@media (max-width: 52rem) {
  .hero {
    /* Fallback first: without svh the rule is dropped and the hero collapses. */
    min-height: 100vh;
    min-height: 100svh;
    /* The text row sizes itself and the band takes the remainder. A fixed
       fraction cannot work: the stack measures 458px on a 667px phone, so any
       band over ~31% pushes the second button below the fold. */
    grid-template-rows: minmax(0, 1fr) auto;
    align-items: stretch;
    justify-items: stretch;
    /* 80px — the real header with the hamburger is 76px. --header-h (88px) is a
       scroll-anchor reservation, not a measurement, and would overshoot.
       Between 48rem and 52rem the header is the DESKTOP one (92px, and with no
       background of its own) while the hero is still the mobile one, so the
       reservation grows to match — see the 48rem override below. */
    padding-top: 5rem;
  }

  /* In flow now, but the <img> stays absolute: left in flow at height:100% its
     intrinsic 1600x1000 box becomes the row's min-content height and the band
     refuses to shrink below 234px (measured). Absolute takes it out of flow so
     minmax(0, 1fr) can actually bind. */
  .hero-canvas {
    position: relative;
  }

  .hero-canvas img {
    position: absolute;
    inset: 0;
    /* 62% slides right and clips the gold scales (at 28% x) once the band is
       taller than ~400px; 50% keeps scales, blindfold and the near poppies.
       34% centres the blindfold register on short phones, where the crop is
       vertical rather than horizontal. */
    object-position: 50% 34%;
  }

  /* No scrim: nothing overlaps the painting any more. */
  .hero-canvas::after {
    background: none;
  }

  .hero-inner {
    text-align: center;
    max-width: 40rem;
    margin-inline: auto;
    /* Was calc(var(--header-h) + var(--sp-4)); the hero reserves the header
       now, and those 104px are the fold budget the two buttons need. */
    padding-block: var(--sp-3) var(--sp-4);
  }

  .hero-actions {
    justify-content: center;
  }
}

.hero-eyebrow {
  font-size: 0.8125rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold-soft);
  margin: 0 0 var(--sp-3);
}

.hero h1 {
  font-family: var(--display);
  font-size: clamp(2rem, 4.4vw, 3.6rem);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: -0.015em;
  margin: 0 0 var(--sp-3);
  text-wrap: balance;
}

.hero h1 em {
  font-style: italic;
  color: var(--gold-soft);
}

.hero-lede {
  font-family: var(--display);
  font-size: clamp(1.125rem, 2.4vw, var(--s-2));
  font-weight: 300;
  line-height: 1.55;
  color: var(--linen-dim);
  max-width: 40ch;
  margin: 0 0 var(--sp-4);
  font-style: italic;
  text-wrap: pretty;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.scroll-cue {
  position: absolute;
  bottom: var(--sp-4);
  left: 50%;
  translate: -50% 0;
  color: var(--mist);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  text-decoration: none;
  display: grid;
  justify-items: center;
  gap: var(--sp-1);
}

/* The rest of the mobile hero. These live AFTER the base .hero-eyebrow / h1 /
   .hero-lede / .scroll-cue rules on purpose: every selector here has the same
   specificity as its base rule, so an override placed in the earlier @media
   block loses on cascade order and silently does nothing. */
@media (max-width: 52rem) {
  /* The base `margin` shorthand resets margin-inline, so the lede would
     otherwise hug the left edge of the centred column. */
  .hero-lede {
    margin-inline: auto;
    /* --sp-4 -> --sp-3: 16px toward keeping button two above the fold. */
    margin-bottom: var(--sp-3);
  }

  /* Absolutely positioned at the hero's foot, it would land on top of button
     two — and "there is more below" is redundant once the text is already on
     solid ground under the painting. */
  .scroll-cue {
    display: none;
  }
}

/* The 48-52rem gap. The nav collapses at 48rem but the hero splits at 52rem, so
   between 769px and 832px the header is the desktop one — 92px tall AND fully
   transparent — while the hero is already the mobile stack. Reserving only the
   76px hamburger height there put the header's own text on raw painting:
   measured 1.00:1. Reserve the real 92px instead and it is back on solid
   ground. Below 48rem the narrower rule above applies and wins on order. */
@media (min-width: 48.0625rem) and (max-width: 52rem) {
  .hero {
    padding-top: 6rem;
  }
}

/* Short phones. The natural text stack is a hard 474px floor, so at 640-667px
   the band was left with only 62-105px: a letterbox strip with her face cut off
   at the eyes, worse than the scrim it replaced. Tightening the type on short
   viewports only gives the band a real 176-200px (28-30% of the viewport) while
   keeping BOTH buttons above the fold — a 40svh floor on the band instead
   pushed the primary CTA off-screen entirely. Most of the saving is free: the
   eyebrow drops from two lines to one. Above 700px tall none of this applies. */
@media (max-width: 52rem) and (max-height: 43.75rem) {
  .hero {
    grid-template-rows: minmax(11rem, 1fr) auto;
  }

  .hero-eyebrow {
    font-size: 0.75rem;
    letter-spacing: 0.22em;
    margin-bottom: var(--sp-2);
  }

  .hero h1 {
    font-size: 1.75rem;
    margin-bottom: var(--sp-2);
  }

  .hero-lede {
    font-size: 1rem;
    margin-bottom: var(--sp-2);
  }

  .hero-inner {
    /* End padding is --sp-3 (24px) minus 3px: at 360x640 the 11rem band floor
       plus the text stack overran the viewport by exactly 3px, and a 3px sliver
       of a clipped button is worse than 3px less breathing room under it. */
    padding-block: var(--sp-2) 1.3125rem;
  }

  .hero-actions {
    gap: 0.75rem;
  }
}

.scroll-cue::after {
  content: "";
  width: 1px;
  height: 3rem;
  background: linear-gradient(to bottom, var(--gold), transparent);
  animation: cue 2.6s var(--ease) infinite;
}

@keyframes cue {
  0%,
  100% {
    opacity: 0.35;
    transform: scaleY(0.7);
    transform-origin: top;
  }
  50% {
    opacity: 1;
    transform: scaleY(1);
    transform-origin: top;
  }
}

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

.btn {
  display: inline-block;
  padding: 0.875rem 2rem;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--gold);
  color: var(--gold-soft);
  background: transparent;
  transition:
    background 0.45s var(--ease),
    color 0.45s var(--ease);
}

.btn:hover {
  background: var(--gold);
  color: var(--abyss);
}

.btn--solid {
  background: var(--gold);
  color: var(--abyss);
  font-weight: 600;
}

.btn--solid:hover {
  background: var(--gold-soft);
}

/* ---------- Sections ---------- */

.section {
  padding: var(--sp-7) var(--edge);
}

.section--tight {
  padding-block: var(--sp-6);
}

/* First section on a page without a hero: clears the fixed header. Replaces
   the inline padding-top patches that four pages carried. */
.section--lede {
  padding-top: calc(var(--header-h) + var(--sp-5));
  padding-bottom: 0;
}

/* A lede's bottom padding and the next section's top padding stacked into a
   289px void between the page title and its first heading. The lede owns no
   bottom space; the following section keeps its own. */
.section--lede + .section {
  padding-top: var(--sp-5);
}

.section--lede .section-head {
  margin-bottom: 0;
}

/* A button following a paragraph. Was six inline margin-top declarations. */
.btn-row {
  margin-top: var(--sp-3);
}

.btn-row--center {
  margin-top: var(--sp-5);
  text-align: center;
}

.section-head {
  max-width: var(--measure);
  margin: 0 auto var(--sp-5);
  text-align: center;
}

.section-label {
  font-size: 0.75rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 var(--sp-2);
}

.section-head h2,
.page-head h1 {
  font-family: var(--display);
  font-size: clamp(1.875rem, 4.5vw, var(--s-4));
  font-weight: 300;
  line-height: 1.15;
  margin: 0 0 var(--sp-2);
  text-wrap: balance;
}

.section-head p,
.page-head p {
  color: var(--linen-dim);
  margin: 0;
  text-wrap: pretty;
}

.rule {
  width: 3.5rem;
  height: 1px;
  background: var(--gold);
  margin: var(--sp-3) auto;
  border: 0;
}

/* ---------- Manifesto ---------- */

.manifesto {
  background: var(--deep);
  position: relative;
}

.manifesto blockquote {
  font-family: var(--display);
  font-size: clamp(1.375rem, 3.4vw, var(--s-3));
  font-weight: 300;
  font-style: italic;
  line-height: 1.45;
  max-width: 40ch;
  margin: 0 auto;
  text-align: center;
  text-wrap: pretty;
}

.manifesto cite {
  display: block;
  margin-top: var(--sp-3);
  font-family: var(--body);
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--gold);
}

/* ---------- Gallery ---------- */

/* Column flow, not a fixed grid: portraits, squares and landscapes each keep
   their true proportions and the columns stay visually balanced. */
.gallery {
  max-width: 78rem;
  margin: 0 auto;
  columns: 1;
  column-gap: var(--sp-4);
}

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

@media (min-width: 68rem) {
  .gallery {
    columns: 3;
    column-gap: var(--sp-5);
  }
}

.piece {
  break-inside: avoid;
  margin: 0 0 var(--sp-5);
}

.piece a {
  display: block;
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  background: var(--deep);
}

/* Never crop a painting. Each image keeps its own aspect ratio; the grid uses
   masonry-ish column flow so mixed portrait/landscape/square works sit together
   without forcing any of them into a uniform box. Cropping is acceptable for
   stock photography — it is not acceptable for someone's canvas. */
.piece img {
  width: 100%;
  height: auto;
  transition:
    transform 1.1s var(--ease),
    filter 1.1s var(--ease);
}

.piece a:hover img {
  transform: scale(1.035);
  filter: brightness(1.06);
}

/* Caption as a stack, not a two-slot row: the row wrapped at every mobile
   width and could not hold dimensions as well as the title. */
.piece figcaption {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px solid rgba(201, 162, 39, 0.22);
}

.piece-title {
  font-family: var(--display);
  font-size: var(--s-1);
  font-weight: 400;
  line-height: 1.2;
  margin: 0;
  text-wrap: balance;
}

.piece-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem var(--sp-2);
  margin: var(--sp-1) 0 0;
  font-size: var(--s-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--patina);
}

.piece-dims {
  color: var(--mist);
}

/* Her text lives in the document so it is readable without JavaScript; the
   viewer lifts it out on click. Visually it is folded away behind a cue so
   the gallery stays a gallery. */
.piece-detail {
  margin-top: var(--sp-2);
  font-size: var(--s-sm);
  color: var(--linen-dim);
}

.js-on .piece-detail {
  display: none;
}

.piece-detail p {
  margin: 0 0 var(--sp-2);
}

.piece-verse {
  font-family: var(--display);
  font-size: var(--s-1);
  line-height: 1.5;
  color: var(--linen);
}

.piece-status {
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.piece-status.is-sold {
  color: var(--coral);
}

.piece-status.is-available {
  color: var(--patina);
}

.piece-prov {
  font-style: italic;
  color: var(--mist);
}

.piece-price {
  display: block;
  margin-bottom: var(--sp-2);
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}

.piece-ask {
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid rgba(201, 162, 39, 0.22);
}

.piece-open {
  display: block;
}

.piece-tech {
  flex: 1 1 auto;
}

/* Touch has no hover, so the only signal that a painting opens was invisible
   on a phone. This cue is always present. */
.piece-cue {
  position: absolute;
  right: var(--sp-2);
  bottom: var(--sp-2);
  padding: 0.4rem 0.7rem;
  background: rgba(11, 20, 22, 0.72);
  border: 1px solid rgba(201, 162, 39, 0.5);
  color: var(--gold-soft);
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  backdrop-filter: blur(6px);
  transition: opacity 0.4s var(--ease);
}

.piece a {
  position: relative;
}

@media (hover: hover) {
  .piece-cue {
    opacity: 0;
  }
  .piece a:hover .piece-cue,
  .piece a:focus-visible .piece-cue {
    opacity: 1;
  }
}

/* Keyboard focus deserves the same response as hover. */
.piece a:focus-visible img {
  transform: scale(1.035);
  filter: brightness(1.06);
}

.piece a:focus-visible {
  outline-offset: -2px;
}

/* Her further photographs of a work — detail shots of the paint surface,
   atelier views, the work against a wall. With JavaScript the viewer lifts
   them into its own strip; without it they stay here as a small row of links
   to the full photo. Same mechanism as .piece-detail above: the content is
   always in the document, and .js-on only folds it away.
   Must follow `.piece img { width: 100% }` — equal specificity, later wins. */
.piece-shots {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin: var(--sp-2) 0 0;
  padding: 0;
  list-style: none;
}

.js-on .piece-shots {
  display: none;
}

.piece-shots a {
  /* 44px tap target even for a narrow landscape thumbnail. */
  min-width: 2.75rem;
}

.piece-shots img {
  width: auto;
  height: 3.5rem;
}

/* ---------- Symbols ---------- */

.symbols {
  background: var(--deep);
}

/* minmax(15rem, …) produced four columns holding serif quotations two or
   three words wide. A quotation needs a readable measure, so at most two
   columns. */
.symbol-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: var(--sp-4) var(--sp-5);
  max-width: 66rem;
  margin: 0 auto;
}

.symbol {
  padding: var(--sp-3);
  border-top: 1px solid rgba(201, 162, 39, 0.28);
}

.symbol h3 {
  line-height: 1.2;
  font-family: var(--display);
  font-size: var(--s-1);
  font-weight: 400;
  margin: 0 0 var(--sp-1);
  color: var(--gold-soft);
}

.symbol p {
  margin: 0;
  color: var(--linen-dim);
  font-size: var(--s-sm);
}

/* The symbol texts are quotations from the works themselves, not editorial
   copy, so they are set as quotes and credited to their painting. */
.symbol blockquote {
  margin: 0;
  font-family: var(--display);
  font-size: var(--s-1);
  line-height: 1.45;
  color: var(--linen);
  text-wrap: pretty;
}

.symbol figcaption {
  margin-top: var(--sp-2);
  font-size: var(--s-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--patina);
}

.symbol figcaption a {
  display: inline-block;
  /* 44px tap minimum for a 12px label. */
  padding-block: 0.8125rem;
  color: var(--gold-soft);
  text-decoration: none;
  border-bottom: 1px solid rgba(201, 162, 39, 0.4);
}

.symbol figcaption a:hover,
.symbol figcaption a:focus-visible {
  border-bottom-color: var(--gold);
}

/* ---------- Split (atelier / about) ---------- */

.split {
  display: grid;
  gap: var(--sp-5);
  align-items: center;
  max-width: 78rem;
  margin: 0 auto;
}

@media (min-width: 52rem) {
  .split {
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-6);
  }
  .split--reverse .split-media {
    order: 2;
  }
}

.split-media img {
  width: 100%;
  height: auto;
}

.split-body h2 {
  font-family: var(--display);
  font-size: clamp(1.75rem, 4vw, var(--s-3));
  font-weight: 300;
  line-height: 1.2;
  margin: 0 0 var(--sp-2);
}

.split-body p {
  color: var(--linen-dim);
  max-width: 46ch;
}

.signature {
  line-height: 1.3;
  font-family: var(--display);
  font-size: var(--s-2);
  font-style: italic;
  color: var(--gold-soft);
  margin-top: var(--sp-3);
}

/* ---------- Prose (about / imprint) ---------- */

.prose {
  max-width: var(--measure);
  margin: 0 auto;
}

.prose h2 {
  line-height: 1.2;
  font-family: var(--display);
  font-weight: 400;
  font-size: var(--s-2);
  margin: var(--sp-5) 0 var(--sp-2);
}

.prose p {
  color: var(--linen-dim);
  text-wrap: pretty;
}

.prose a {
  color: var(--gold-soft);
}

.prose dl {
  margin: var(--sp-3) 0 0;
}

.prose dt {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
  margin-top: var(--sp-3);
}

.prose dd {
  margin: 0.25rem 0 0;
  color: var(--linen-dim);
}

.placeholder-note {
  border-left: 2px solid var(--coral);
  background: rgba(217, 110, 74, 0.08);
  padding: var(--sp-2) var(--sp-3);
  margin: var(--sp-4) 0;
  font-size: 0.9375rem;
  color: var(--linen-dim);
}

.placeholder-note strong {
  color: var(--coral);
}

/* A note addressed to Katharina, not to visitors. Dashed and dimmed so it
   cannot be mistaken for published content. */
.placeholder-note--internal {
  border-left-style: dashed;
  opacity: 0.85;
}

/* ---------- Page hero (subpages) ----------
   The home hero's side gradient assumes left-aligned text. These pages centre
   their text, so this variant uses vertical scrims only — the reason the home
   hero failed contrast on mobile. */

.page-hero {
  position: relative;
  isolation: isolate;
  display: grid;
  place-items: center;
  min-height: min(48svh, 26rem);
  padding: calc(var(--header-h) + var(--sp-5)) var(--edge) var(--sp-5);
  overflow: hidden;
  text-align: center;
}

.page-hero--tall {
  min-height: min(60svh, 34rem);
}

.page-hero-canvas {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.page-hero-canvas img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
}

.page-hero-canvas::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to bottom,
      rgba(11, 20, 22, 0.86) 0%,
      rgba(11, 20, 22, 0.72) 45%,
      rgba(11, 20, 22, 0.88) 100%
    ),
    radial-gradient(
      ellipse at center,
      rgba(11, 20, 22, 0.55) 0%,
      rgba(11, 20, 22, 0.8) 100%
    );
}

.page-hero-inner {
  max-width: 46rem;
}

.page-hero-inner h1 {
  font-family: var(--display);
  font-size: clamp(2rem, 6vw, var(--s-4));
  font-weight: 300;
  line-height: 1.1;
  margin: 0 0 var(--sp-2);
  text-wrap: balance;
}

.page-hero-inner p {
  color: var(--linen-dim);
  margin: 0;
  text-wrap: pretty;
}

/* ---------- Series navigation ---------- */

.series-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin: 0 auto;
  padding: 0;
  list-style: none;
  max-width: 60rem;
}

.btn--small {
  padding: 0.625rem 1.25rem;
  font-size: var(--s-xs);
}

.gallery--narrow {
  max-width: 58rem;
}

/* ---------- Exhibitions ---------- */

.exh-years {
  max-width: 52rem;
  margin: 0 auto;
  display: grid;
  gap: var(--sp-5);
}

.exh-year-label {
  font-family: var(--display);
  font-size: var(--s-2);
  font-weight: 300;
  line-height: 1.15;
  margin: 0 0 var(--sp-2);
  color: var(--gold-soft);
}

.exh-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
}

.exh-item {
  padding: var(--sp-2) 0;
  border-top: 1px solid rgba(201, 162, 39, 0.16);
  text-wrap: pretty;
}

.exh-item--award {
  display: grid;
  gap: 0.35rem;
}

.exh-award-label {
  font-size: var(--s-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
}

.award {
  max-width: 46rem;
  margin: 0 auto;
  text-align: center;
}

.award-quote {
  font-family: var(--display);
  font-size: clamp(1.375rem, 3.5vw, var(--s-2));
  font-weight: 300;
  line-height: 1.4;
  margin: var(--sp-2) 0 0;
  color: var(--linen);
}

/* ---------- Events ----------
   CURRENTLY UNUSED — no page carries `.event-list` markup. The Art.Vibe section
   on `ausstellungen.html` was removed on request (2026-08-17); all three of its
   evenings had passed and were sold out.

   Deliberately kept, not dead code: `REFINEMENT-BACKLOG.md` §3b records that a
   "Kommende Termine" section should return as soon as one future date exists, and
   `CONTENT-GAPS.md` §5 lists that date as the only thing missing. With these
   rules in place that section is markup alone — no styling to reinvent. Delete
   them only if that plan is abandoned. */

.event-list {
  max-width: 52rem;
  margin: 0 auto;
  display: grid;
  gap: var(--sp-3);
}

.event {
  padding: var(--sp-3);
  background: var(--deep);
  border-left: 2px solid var(--gold);
}

.event-date {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}

.event-day {
  color: var(--patina);
}

.event-title {
  font-family: var(--display);
  font-size: var(--s-2);
  font-weight: 300;
  line-height: 1.15;
  margin: var(--sp-1) 0 var(--sp-2);
}

.event-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.35rem var(--sp-3);
  margin: 0;
  font-size: var(--s-sm);
}

.event-meta dt {
  font-size: var(--s-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--patina);
}

.event-meta dd {
  margin: 0;
  color: var(--linen-dim);
}

.event-note {
  margin: var(--sp-2) 0 0;
  font-size: var(--s-sm);
  color: var(--mist);
}

.event-status {
  margin: var(--sp-2) 0 0;
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--coral);
}

/* ---------- Misc content helpers ---------- */

.media-credit {
  margin: var(--sp-1) 0 0;
  font-size: var(--s-xs);
  letter-spacing: 0.1em;
  color: var(--patina);
}

.pull-quote {
  margin: 0 0 var(--sp-3);
  padding-left: var(--sp-3);
  border-left: 2px solid var(--gold);
  font-family: var(--display);
  font-size: var(--s-1);
  line-height: 1.5;
  color: var(--linen);
}

.contact-list {
  display: grid;
  grid-template-columns: minmax(6rem, auto) 1fr;
  gap: var(--sp-2) var(--sp-3);
  margin: 0;
}

.contact-list dt {
  font-size: var(--s-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--patina);
  /* Reset the .prose dt margin: inside a grid it pushed the label a whole
     --sp-3 below its own value, which read as if the label belonged to the
     line beneath it. */
  margin: 0;
  align-self: start;
  line-height: 1.7;
}

.contact-list dd {
  margin: 0;
  align-self: start;
}

.contact-list dd a {
  display: inline-block;
  /* 44px tap minimum: --sp-1 left it one pixel short. */
  padding-block: 0.5625rem;
}

.footer-base--bare {
  border: 0;
  padding-top: 0;
}

/* ---------- Werkdetail-Viewer ----------
   The painting at full proportion beside her words. Replaces the old lightbox,
   which showed the image alone and trapped Tab on a single button. */

.viewer {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(6, 12, 13, 0.97);
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  justify-items: center;
  padding: var(--sp-2);
}

@media (min-width: 56rem) {
  .viewer {
    grid-template-rows: 1fr;
    place-items: center;
  }
}

.viewer[hidden] {
  display: none;
}

/* One column on small screens: image gets its own row, panel takes the rest
   and scrolls inside it. minmax(0,…) on both rows is what allows the children
   to be smaller than their content and therefore to scroll at all. */
.viewer-inner {
  display: grid;
  /* Both rows capped as a fraction of the available height: an `auto` image
     row grows to the painting's natural size and overflows the overlay.
     The text gets the larger share — one column is where reading happens. */
  grid-template-rows: minmax(0, 4fr) minmax(0, 6fr);
  gap: var(--sp-3);
  width: 100%;
  max-width: 82rem;
  height: 100%;
  min-height: 0;
}

/* With several photographs the strip gets a row of its own, sized to its
   content. Works with a single photograph keep the two-row grid untouched:
   .viewer-shots is display:none then, so it is not a grid item at all and
   leaves neither an empty track nor an extra gap behind.
   minmax(0, Nfr) stays on both fraction tracks — dropping it is what once let
   the painting overflow the overlay. */
.viewer.has-shots .viewer-inner {
  /* 5fr, not the 4fr of the plain grid: the strip takes its own height out of
     the column, and the painting should not be the only thing paying for it.
     Her text scrolls and can afford the difference. */
  grid-template-rows: minmax(0, 5fr) auto minmax(0, 6fr);
}

@media (min-width: 56rem) {
  .viewer {
    padding: var(--sp-4) var(--edge);
  }
  .viewer-inner {
    grid-template-rows: minmax(0, 1fr);
    /* Wider text column: her prose and poems need room, and the panel now
       carries its own padding. */
    grid-template-columns: minmax(0, 1fr) minmax(24rem, 0.8fr);
    gap: var(--sp-4);
    align-items: stretch;
  }
  /* Two columns: the strip belongs under the painting, not on top of her
     text, so both are placed explicitly instead of by auto-flow. */
  .viewer.has-shots .viewer-inner {
    grid-template-rows: minmax(0, 1fr) auto;
  }
  .viewer.has-shots .viewer-media {
    grid-area: 1 / 1;
  }
  .viewer.has-shots .viewer-shots {
    grid-area: 2 / 1;
  }
  .viewer.has-shots .viewer-panel {
    grid-area: 1 / 2 / -1 / 3;
  }
}

/* The work keeps its true proportions — never cropped, never letterboxed.
   The image fits whatever box the grid gives it (100% of both axes) rather
   than carrying its own viewport-relative height cap; a cap changed abruptly
   at the breakpoint and made the painting jump in size on resize. */
/* The image is stretched to the whole cell and then object-fit: contain does
   the letterbox-free scaling. A percentage max-height on the <img> alone does
   not bind here, because the cell's own height is content-derived — which is
   why the painting overflowed the overlay at wide viewports. */
.viewer-media {
  position: relative;
  margin: 0;
  min-height: 0;
  min-width: 0;
}

.viewer-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* contain leaves the painting's own edges; no background box around it. */
  transition: opacity 0.3s var(--ease);
}

/* A crossfade between photographs, not a slide: the image is absolutely
   positioned and scaled by object-fit, so a translateX would need a second
   image track and would fight exactly the positioning that keeps the painting
   inside the overlay. The global prefers-reduced-motion block near the top of
   this file already flattens this transition to 0.01ms. */
.viewer-media.is-swapping img {
  opacity: 0;
}

.viewer-panel {
  /* min-height: 0 + a flex column is what lets .viewer-body actually scroll
     instead of stretching the panel past the overlay. */
  min-height: 0;
  align-self: center;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Opaque: on one column the panel scrolls over the page behind it, which
     otherwise reads through her text. */
  background: var(--abyss);
  /* Her words sit in a panel, not against its edges. */
  padding: var(--sp-3);
  border: 1px solid rgba(201, 162, 39, 0.16);
}

@media (min-width: 56rem) {
  .viewer-panel {
    padding: var(--sp-4);
  }
}

.viewer-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  /* The close button must stay inside the panel, not ride its edge. */
  padding-right: 1px;
}

.viewer-title {
  min-width: 0;
}

.viewer-title {
  font-family: var(--display);
  font-size: clamp(1.5rem, 4vw, var(--s-3));
  font-weight: 300;
  line-height: 1.15;
  margin: 0;
  text-wrap: balance;
}

.viewer-close {
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--gold);
  color: var(--gold-soft);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition:
    background 0.35s var(--ease),
    color 0.35s var(--ease);
}

.viewer-close:hover,
.viewer-close:focus-visible {
  background: var(--gold);
  color: var(--abyss);
}

/* An outward ring on a control at the panel's edge gets clipped; draw it
   inside instead. */
.viewer-close:focus-visible {
  outline-offset: -4px;
}

.rule--left {
  margin-inline: 0;
}

/* Long texts scroll; the painting stays put. */
.viewer-body {
  overflow-y: auto;
  padding-right: var(--sp-1);
  color: var(--linen-dim);
}

.viewer-body p {
  margin: 0 0 var(--sp-2);
}

.viewer-body .piece-verse {
  font-family: var(--display);
  font-size: var(--s-1);
  line-height: 1.55;
  color: var(--linen);
}

.viewer-body .piece-meta {
  margin-bottom: var(--sp-2);
}

/* ---------- Bildstreifen im Viewer ----------
   Strip, kind label and counter for the further photographs of a work.
   Hidden entirely for works that have only one image. */

.viewer-shots {
  display: none;
}

/* min-width: 0 here and on .viewer-thumbs is what lets the strip scroll inside
   itself. Without it the flex children refuse to be narrower than their content
   and widen the whole overlay instead — the same trap as the minmax(0, …) on
   the grid rows, one layout mode over. */
.viewer.has-shots .viewer-shots {
  display: grid;
  /* Counter and label stack in the second column, beside the strip rather than
     under it: two stacked lines are shorter than one thumbnail, so the whole
     block costs exactly the height of the thumbnails and the painting keeps
     the room it had. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--sp-3);
  min-width: 0;
}

.viewer-thumbs {
  grid-row: 1 / 3;
  min-width: 0;
  display: flex;
  gap: var(--sp-1);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x proximity;
}

.viewer-thumb {
  flex: none;
  scroll-snap-align: start;
  /* 44px wide even for a narrow landscape thumbnail. */
  min-width: 2.75rem;
  height: 3.5rem;
  padding: 0;
  background: var(--deep);
  border: 1px solid transparent;
  cursor: pointer;
  transition: border-color 0.35s var(--ease);
}

/* No thumbnail is cropped either — the first one is a reproduction of the
   painting, and contain is what keeps mixed formats honest. */
.viewer-thumb img {
  height: 100%;
  width: auto;
  object-fit: contain;
}

/* State lives on aria-current, the same way .site-nav marks the active page,
   so the gold marker and the announced state cannot drift apart. */
.viewer-thumb[aria-current="true"] {
  border-color: var(--gold);
}

.viewer-thumb:hover {
  border-color: var(--gold-soft);
}

.viewer-thumb:focus-visible {
  outline-offset: -4px;
}

.viewer-count {
  grid-area: 1 / 2;
  align-self: end;
  margin: 0;
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  color: var(--mist);
}

/* Says what kind of photograph this is — a macro shot of the paint surface is
   confusing without it. */
.viewer-kind {
  grid-area: 2 / 2;
  align-self: start;
  margin: 0;
  font-size: var(--s-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--patina);
  /* Never pushes the strip narrower than a couple of thumbnails. */
  max-width: 9rem;
}

/* On one column the arrows sit in a row of their own under the panel, so they
   cannot cover her text. From two columns up they flank the painting. */
.viewer-nav {
  display: flex;
  justify-content: center;
  gap: var(--sp-2);
  padding-top: var(--sp-2);
}

@media (min-width: 56rem) {
  .viewer-nav {
    position: absolute;
    inset: 50% var(--sp-2) auto var(--sp-2);
    translate: 0 -50%;
    justify-content: space-between;
    padding-top: 0;
    pointer-events: none;
  }
}

.viewer-prev,
.viewer-next,
.viewer-step {
  pointer-events: auto;
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  background: rgba(11, 20, 22, 0.72);
  border: 1px solid rgba(201, 162, 39, 0.5);
  color: var(--gold-soft);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(6px);
}

.viewer-prev:hover,
.viewer-next:hover,
.viewer-step:hover,
.viewer-prev:focus-visible,
.viewer-next:focus-visible,
.viewer-step:focus-visible {
  background: var(--gold);
  color: var(--abyss);
}

.viewer-prev[disabled],
.viewer-next[disabled],
.viewer-step[disabled] {
  opacity: 0.3;
  cursor: default;
}

/* The image arrows sit on the painting itself; the work arrows keep the edges
   of the overlay. Must come after the shared skin above so display:none wins
   over its display:grid for single-photograph works. */
.viewer-step {
  display: none;
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  z-index: 1;
}

.viewer.has-shots .viewer-step {
  display: grid;
}

.viewer-step--prev {
  left: var(--sp-2);
}

.viewer-step--next {
  right: var(--sp-2);
}

/* From two columns up the work arrows flank the overlay at half height. Kept
   at half height too, the image arrows landed 28px from them and both read as
   the same "back" control, so they drop to the foot of the painting — beside
   the strip they belong to, and unmistakably a different pair. */
@media (min-width: 56rem) {
  .viewer-step {
    top: auto;
    bottom: var(--sp-2);
    translate: none;
  }
}

/* ---------- Viewer auf einer Spalte ----------
   Below 56rem the overlay is one scrolling column: the painting first at a
   generous height, then the strip, then her text flowing on the ground with no
   box. It replaces a two-row grid in which the text got MORE height than the
   painting (measured 354px of panel against a 294px image at 390x844 — a
   110x140cm canvas rendering 231x294px).

   .viewer-inner is the scroller, not .viewer: row 2 of .viewer's grid already
   pins .viewer-nav outside the scrolling region, so the work arrows stay put
   with no sticky positioning and no background of their own. */
@media (max-width: 55.9375rem) {
  /* rgba(…, 0.97) let the catalogue behind show through her prose once the
     panel lost its own opaque background. */
  .viewer {
    background: var(--abyss);
  }

  .viewer-inner {
    display: block;
    height: 100%;
    overflow-y: auto;
    /* Stops the scroll chaining to the page behind at the end of the text. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* Clears the pinned close button. */
    padding-top: 3.5rem;
  }

  /* A definite height, not a max-height on the image: a percentage cap does
     not bind to a content-sized cell (it once rendered 1199px in a 900px
     viewport). 62svh is also the smallest cap that does not make the painting
     SHRINK crossing 56rem — at 56svh it shrinks 14% at a 700px-tall window,
     at 52svh 8% at 800px. */
  .viewer-media {
    height: 62svh;
    margin-bottom: var(--sp-3);
  }

  /* The photo arrows get a strip of their own at the foot of the cell instead
     of sitting on the canvas: on the most portrait works (r=0.65) the painting
     reaches the cell floor, so bottom-corner arrows would land on the paint. */
  .viewer.has-shots .viewer-media {
    height: calc(62svh + 3.5rem);
  }

  .viewer.has-shots .viewer-media img {
    height: calc(100% - 3.5rem);
  }

  .viewer-step {
    top: auto;
    bottom: 0;
    translate: none;
  }

  .viewer-step--prev {
    left: 0;
  }

  .viewer-step--next {
    left: 3.5rem;
    right: auto;
  }

  .viewer-shots {
    margin-bottom: var(--sp-3);
  }

  /* Unboxed. All four of display, max-height, overflow and .viewer-body's own
     overflow-y have to go, or the text stays trapped in a short window. */
  .viewer-panel {
    display: block;
    max-height: none;
    overflow: visible;
    background: none;
    border: 0;
    padding: 0;
  }

  .viewer-body {
    overflow-y: visible;
    padding-right: 0;
  }

  /* absolute, NOT fixed: the focus trap in assets/site.js filters on
     offsetParent !== null, and a fixed element has no offsetParent — it would
     drop the close button out of the tab cycle while staying visible. .viewer
     is already position: fixed, so absolute pins to the overlay and the column
     scrolls underneath. */
  .viewer-close {
    position: absolute;
    top: var(--sp-2);
    right: var(--sp-2);
    z-index: 3;
    background: rgba(11, 20, 22, 0.72);
    backdrop-filter: blur(6px);
  }
}

/* ---------- Viewer auf dem Desktop: die Seite scrollt, nicht der Kasten ------
   Her longer texts — Solis is four stanzas — were cut off mid-verse inside a
   bordered panel with its own scrollbar (measured 801px of poem in a 645px
   window). The box is gone: the OVERLAY scrolls and the text simply flows.

   The painting does not scroll away with it. .viewer-media and its strip are
   sticky, so the work stays in view for as long as there is text to read —
   which is the whole reason the two-column layout exists.

   This block comes after every @media (min-width: 56rem) above it: the
   selectors have the same specificity, so an override placed earlier loses on
   cascade order and silently does nothing. */
@media (min-width: 56rem) {
  .viewer {
    /* Was a centred grid; a grid item cannot be taller than the overlay and
       scroll the overlay at the same time. Block flow can. */
    display: block;
    overflow-y: auto;
    /* The catalogue behind must not take over at the end of the text. */
    overscroll-behavior: contain;
    /* The panel's own opaque ground is gone, so 0.97 would let the gallery
       ghost through her prose. */
    background: var(--abyss);
    padding: var(--sp-4) var(--edge);
  }

  .viewer-inner {
    /* height: 100% would re-cap the column to one screen. */
    height: auto;
    align-items: start;
    grid-template-rows: auto;
  }

  .viewer.has-shots .viewer-inner {
    grid-template-rows: auto auto;
  }

  /* Sticky, and sized to the viewport rather than to the text column: a
       percentage height here would resolve against the now-unbounded row. */
  .viewer-media {
    position: sticky;
    top: 0;
    height: calc(100svh - 9rem);
  }

  .viewer.has-shots .viewer-media {
    height: calc(100svh - 12rem);
  }

  /* Sticks directly under the painting, so image and strip travel as one. */
  .viewer.has-shots .viewer-shots {
    position: sticky;
    top: calc(100svh - 12rem);
  }

  /* Unboxed. display, max-height and overflow all have to go together, or the
     text stays trapped in a short window. */
  .viewer-panel {
    display: block;
    max-height: none;
    overflow: visible;
    background: none;
    border: 0;
    padding: 0;
    align-self: start;
  }

  .viewer-body {
    overflow-y: visible;
    padding-right: 0;
  }

  /* Title and close stay reachable while the prose scrolls beneath them. Needs
     its own ground, or ascenders show through from the lines passing under. */
  .viewer-head {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--abyss);
    padding-bottom: var(--sp-2);
  }

  /* The work arrows were pinned to the overlay when the overlay was the
     viewport. Now that it scrolls, they must be fixed to stay at half height. */
  .viewer-nav {
    position: fixed;
  }
}

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

.site-footer {
  background: var(--deep);
  padding: var(--sp-6) var(--edge) var(--sp-4);
  border-top: 1px solid rgba(201, 162, 39, 0.18);
}

.footer-grid {
  display: grid;
  gap: var(--sp-4);
  max-width: 78rem;
  margin: 0 auto var(--sp-5);
}

@media (min-width: 46rem) {
  .footer-grid {
    grid-template-columns: 2fr 1fr 1fr;
    gap: var(--sp-5);
  }
}

.footer-grid h2 {
  line-height: 1.2;
  font-family: var(--display);
  font-size: var(--s-2);
  font-weight: 300;
  margin: 0 0 var(--sp-2);
}

.footer-grid h3 {
  font-size: 0.75rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 var(--sp-2);
}

.footer-grid p {
  color: var(--linen-dim);
  margin: 0;
  max-width: 34ch;
}

.footer-grid ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-1);
}

/* :not(.btn) matters: these rules sit later in the sheet at equal specificity,
   so without it they repainted the footer's contact button — and the :hover
   rule left gold text on a gold ground at 1.39:1. */
.footer-grid a:not(.btn) {
  display: inline-block;
  /* 18px of text is not a tap target; this reaches the 44px minimum. */
  padding-block: 0.6875rem;
  color: var(--linen-dim);
  text-decoration: none;
  font-size: var(--s-sm);
}

.footer-grid a:not(.btn):hover,
.footer-grid a:not(.btn):focus-visible {
  color: var(--gold-soft);
}

.footer-base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  justify-content: space-between;
  max-width: 78rem;
  margin: 0 auto;
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(168, 184, 180, 0.14);
  font-size: 0.8125rem;
  color: var(--patina);
}

.footer-base a {
  display: inline-block;
  /* Reaches the 44px tap minimum; 13px of text alone did not. */
  padding-block: 0.8125rem;
  color: var(--patina);
}

.footer-base a:hover,
.footer-base a:focus-visible {
  color: var(--gold-soft);
}
