/* ============================================================================
   anna schley — the site
   Grown from mockups/a-horizont, which the client approved. The idea is
   unchanged: ONE hairline, fixed to the viewport, and every painting placed so
   that its own painted horizon lands on that line. Nothing here may break that.

   How to read this file: every block below is named exactly like the
   data-sid / data-title of the section it styles, in document order.
   ========================================================================= */


/* ---------------------------------------------------------------------------
   Schriften — self-hosted in the case-study copy
   Google Fonts served a static Fraunces cut at opsz 144 / wght 300. This is the
   variable file, so the optical size is pinned to 144 on every serif element below;
   left on auto, headings at 40px would draw with a heavier, lower-contrast cut.
   Frank Ruhl Libre (below) is the body font, Anna's wish for the English pass
   (2026-09-10) — it only ships at weight 400, the only weight the body text uses.
   --------------------------------------------------------------------------- */
@font-face {
  font-family: 'Fraunces';
  src: url('../fonts/fraunces-var-latin.woff2?v=069cb90e') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: 'Frank Ruhl Libre';
  src: url('../fonts/frankruhllibre-400-latin.woff2?v=f7f52176') format('woff2');
  font-weight: 400;
  font-display: swap;
}
.name, .leaf-title, .mailto, .page-title { font-variation-settings: "opsz" 144; }


/* ---------------------------------------------------------------------------
   Tokens — four colours, no more
   --------------------------------------------------------------------------- */
:root {
  --paper: #F1EDE4;              /* warm off-white, the ground */
  --ink:   #1E1B16;              /* soft near-black with warmth */
  --rule:  #B7AF9F;              /* the horizon hairline, warm grey */
  --sea:   #2B5A67;              /* sampled from the hero painting's distant ridge; links only */
  /* Not a fifth colour: the ink let down by the paper, for secondary lines.
     Resolves to #55524C — 6.7:1 on the paper, so it clears AA as body text. */
  --ink-soft: color-mix(in srgb, var(--ink) 74%, var(--paper));

  --gutter: max(22px, 6vw);
  --pw: 54vw;                    /* plate width ceiling while the label stands beside it */

  /* Height of the fixed rule inside the viewport. Every plate is placed against it. */
  --horizon: calc(55 * var(--vh));
  /* Hero band height. Its top is derived, never typed: see .panorama. */
  --hero-h: calc(78 * var(--vh));

  /* Two guards that keep a plate inside its screen — see .stanza for the maths.
     They exist because this site adds paintings whose horizon sits low in the
     canvas (grey-mountain-lake at 0.788): without them such a plate would need
     more room above the rule than the rule has, and get cut off at the top. */
  --sky-gap: calc(3 * var(--vh));                /* air kept between the plate's top edge and the window */
  --label-room: 7.5rem;          /* room the label needs once it steps under the plate */

  --serif:   'Fraunces', 'Iowan Old Style', Georgia, serif;
  /* Body font, Anna's wish (2026-09-10): Frank Ruhl Libre, replacing Archivo. */
  --grotesk: 'Frank Ruhl Libre', Georgia, serif;

  /* Slow, flat, no overshoot — the page drifts, it never springs. */
  --drift: 900ms cubic-bezier(.16, .84, .44, 1);
}

* { box-sizing: border-box; }

/* No scroll-snap in the concept copy: proximity snapping pulled every short wheel
   scroll back to where it started (measured 2026-09-10: 20 wheel steps of 90px moved
   the page 0px), which reads as the page jumping. */

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--grotesk);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img { display: block; max-width: 100%; }


/* ---------------------------------------------------------------------------
   Sprungmarke — skip link
   Absolute, so it never adds height to the first screen. The first screen is
   measured (both calls to action must be visible without scrolling) and a
   link in the flow would push them down.
   --------------------------------------------------------------------------- */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--paper);
  color: var(--ink);
  padding: .8rem 1.2rem;
  border: 1px solid var(--ink);
  text-decoration: none;
}

.skip:focus { left: 12px; top: 12px; }


/* ---------------------------------------------------------------------------
   horizontlinie — the one unbroken rule
   Fixed, so it holds the same height through the whole document. Drawn above the
   paintings so it visibly cuts each canvas at its own painted horizon.
   --------------------------------------------------------------------------- */
.horizon {
  position: fixed;
  top: var(--horizon);
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule);
  z-index: 4;
  pointer-events: none;
}


/* ---------------------------------------------------------------------------
   Motion — horizontal drift only
   Images and labels arrive by sliding a few percent along the horizon. No scale,
   no rotation, no bounce. Only transform and opacity are animated.
   --------------------------------------------------------------------------- */
/* Hidden ONLY once JS has claimed the page. Without the .js gate the name — the one
   thing this page exists to say — starts at opacity 0 in the stylesheet, so any hiccup
   in the reveal (throttled tab, silent observer, script blocked) leaves a blank screen.
   Content is visible by default; JS opts into animating it. */
.js .drift {
  opacity: 0;
  transform: translateX(var(--from, 2.5%));
  transition: opacity var(--drift) var(--d, 0ms),
              transform var(--drift) var(--d, 0ms);
  will-change: transform, opacity;
}

.js .drift.in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .drift,
  .js .drift.in {
    opacity: 1;
    transform: none;
    transition: none;
    will-change: auto;
  }
}


/* ---------------------------------------------------------------------------
   Links — one treatment for every link on the site
   --------------------------------------------------------------------------- */
a { color: var(--sea); }

/* 2px, not the hairline width: a focus ring has to be found by someone who is
   tabbing, and 1px of a warm grey on warm paper is not findable. */
:focus-visible { outline: 2px solid var(--sea); outline-offset: 4px; }


/* ---------------------------------------------------------------------------
   horizont — erster Blick
   One screen: name, one line of German, the panorama across the rule, the
   exhibition, and both ways to make contact. Nothing here needs a scroll.
   --------------------------------------------------------------------------- */
.opening {
  position: relative;
  height: calc(100 * var(--vh));
  min-height: 560px;
  overflow: clip;              /* the band is allowed to sit a few px above the fold */
}

/* The whole alignment trick, in two declarations:
   object-position Y is the painting's own horizon fraction (72.34%), so the horizon
   lands at 72.34% of the box no matter how the box is cropped; the box top is then
   simply the rule minus that same fraction of the box height. */
.panorama {
  position: absolute;
  left: 0;
  width: 100%;
  height: var(--hero-h);
  top: calc(var(--horizon) - 0.7234 * var(--hero-h));
  object-fit: cover;
  object-position: 50% 72.34%;
  z-index: 0;
}

.opening-type {
  position: absolute;
  z-index: 3;
  top: calc(7 * var(--vh));
  left: var(--gutter);
  right: var(--gutter);
}

.name {
  margin: 0;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(3.5rem, 12vw, 10rem);
  line-height: .84;
  letter-spacing: -.025em;
  text-transform: lowercase;
}

.tagline {
  margin: 1.5rem 0 0;
  font-size: 15px;
  letter-spacing: .06em;
}


/* ---------------------------------------------------------------------------
   horizont — Ausstellung und Kontakt (untere Hälfte des ersten Blicks)
   Sits on paper below the band, so the links keep full contrast.
   --------------------------------------------------------------------------- */
.opening-foot {
  position: absolute;
  z-index: 3;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(5 * var(--vh));
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem 4rem;
  flex-wrap: wrap;
}

.show { margin: 0; max-width: 34rem; }

/* Grotesk, not the serif. Fraunces at 300 is drawn for display sizes; at 15px its
   thin strokes break up and the line reads as weak rather than quiet. The serif is
   spent on exactly two words on this page — her name — and the section headings. */
.show-title {
  margin: 0;
  font-size: 15px;
  letter-spacing: .06em;
}

.show-meta {
  margin: .5rem 0 0;
  font-size: 13px;
  letter-spacing: .05em;
  color: var(--ink-soft);
}

/* Stacked, not side by side: side by side the two underlines land at different
   heights (the major link carries a note) and the pair reads as an accident. */
.asks {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  gap: 1.6rem;
}

.ask {
  color: inherit;
  text-decoration: none;
  display: block;
  transition: color 300ms ease-out;
}

.ask-line {
  display: inline-block;
  border-bottom: 1px solid currentColor;
  padding-bottom: .3em;
  font-size: 15px;
  letter-spacing: .06em;
}

/* Weight through size and the note underneath it, not through a second typeface. */
.ask--major .ask-line {
  font-size: clamp(1.05rem, 1.7vw, 1.35rem);
  letter-spacing: .02em;
}

.ask-note {
  display: block;
  margin-top: .7rem;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.ask:hover,
.ask:focus-visible { color: var(--sea); }


/* ---------------------------------------------------------------------------
   werke — Zwischentitel (eyebrow)
   A band of air between the opening and the first painting, with the section's
   name resting in its lower left. Deliberately small: the paintings are the
   headline of this page, a heading that competed with them would be wrong.
   --------------------------------------------------------------------------- */
.eyebrow {
  /* Gutter as MARGIN, not padding: the heading drifts in by 2.5% of its own width,
     and a box that reached the window edge would push that 2.5% past it — a
     horizontal scrollbar on every screen size. Inside the margin it stays clear. */
  margin: 0 var(--gutter);
  padding: 0 0 calc(3 * var(--vh));
  /* Whole pixels. Every painting below sits under this band; a fractional band
     height (26vh of 844px is 219.44px) shifts each plate off the rule by the
     fraction, because the browser can only scroll to whole pixels. */
  height: calc(26 * var(--vh));
  height: round(calc(26 * var(--vh)), 1px);
  min-height: 130px;
  display: flex;
  align-items: flex-end;
  font-family: var(--grotesk);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   platte-1 … platte-7 — die Bilder an der Linie
   Each stanza is one screen tall. The plate's top is the rule minus the
   painting's own horizon fraction (--hz) of its height (--ph), so when the
   stanza is at the top of the window the painted horizon is on the rule.
   --------------------------------------------------------------------------- */
.stanza {
  position: relative;
  height: calc(100 * var(--vh));
  min-height: 560px;

  /* Each stanza carries its own --pvh (how tall the plate wants to be) and --ratio.
     Three shared ceilings, in order:
       --pw          the label stands beside the plate and must keep its half;
       sky guard     the plate can never need more room above the rule than the
                     rule has — that is what --hz of the height costs;
     and, once the label steps underneath (≤1000px), a fourth below. */
  --ph: min(
          var(--pvh),
          calc(var(--pw) / var(--ratio)),
          calc((var(--horizon) - var(--sky-gap)) / var(--hz))
        );
}

.stanza-fig { position: absolute; inset: 0; margin: 0; }

.plate {
  position: absolute;
  height: var(--ph);
  width: calc(var(--ph) * var(--ratio));   /* --ratio keeps the painting uncropped */
  top: calc(var(--horizon) - var(--hz) * var(--ph));
  z-index: 1;
}

.stanza--left  .plate { left: var(--gutter); }
.stanza--right .plate { right: var(--gutter); }

/* The label sits on the free side, its first line resting just under the rule —
   the rule doubles as the label's own baseline. */
.label {
  position: absolute;
  z-index: 3;
  top: calc(var(--horizon) + 1.1rem);
  max-width: 20rem;
}

.stanza--left  .label { right: var(--gutter); text-align: right; }
.stanza--right .label { left: var(--gutter); text-align: left; }

.label-t {
  display: block;
  font-size: 15px;
  letter-spacing: .06em;
}

.label-m {
  display: block;
  margin-top: .45rem;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   technik / ausstellungen / kontakt — Textseiten an derselben Linie
   A text section is built like a plate: everything that introduces it sits ABOVE
   the rule and ends on it, everything that follows starts below it. min-height,
   never height, so a long word or a large font never pushes text off the top.
   --------------------------------------------------------------------------- */
.leaf {
  position: relative;
  min-height: calc(100 * var(--vh));
  padding: 0 var(--gutter) calc(14 * var(--vh));
}

.leaf-top {
  min-height: var(--horizon);
  padding-bottom: 1.3rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  max-width: 40rem;
}

.leaf-bottom { padding-top: 2.2rem; }

.leaf-title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.6rem, 4.2vw, 2.9rem);
  line-height: 1.05;
  letter-spacing: -.02em;
}

.leaf-lead {
  margin: 1.4rem 0 0;
  font-size: 15px;
  line-height: 1.7;
  max-width: 34rem;
}


/* ---------------------------------------------------------------------------
   technik — die zwei Werkstattfotos und die Leinwand im Atelier
   A plain row that wraps. The photographs are evidence for the paragraph above
   the rule, so they are small and captioned, not gallery pieces.
   --------------------------------------------------------------------------- */
.media-row {
  display: flex;
  flex-wrap: wrap;
  gap: 2.4rem;
  align-items: flex-start;
}

.media {
  margin: 0;
  flex: 0 1 20rem;
  min-width: 13rem;
}

.media img,
.media video {
  width: 100%;
  height: auto;
  background: var(--rule);       /* the box has its colour before the file arrives */
}

.media--clip { flex: 0 1 13rem; }

.media figcaption {
  margin-top: .7rem;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   ausstellungen — die Liste, die altert
   No "current" or "upcoming" heading anywhere: a heading like that is wrong the
   morning after the show. Entries are dated and newest first, so a past show
   simply becomes a past entry and nothing on the page has to be corrected.
   --------------------------------------------------------------------------- */
.shows {
  margin: 0;
  padding: 0;
  list-style: none;
  max-width: 40rem;
}

.shows li + li {
  margin-top: 2.2rem;
  padding-top: 2.2rem;
  border-top: 1px solid var(--rule);
}

.shows-when {
  margin: 0;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.shows-what {
  margin: .5rem 0 0;
  font-size: 15px;
  letter-spacing: .06em;
}

.shows-where {
  margin: .35rem 0 0;
  font-size: 13px;
  letter-spacing: .05em;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   kontakt — die Adresse und die Einladung
   --------------------------------------------------------------------------- */
.mailto {
  display: inline-block;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.3rem, 3.4vw, 2.2rem);
  letter-spacing: -.01em;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: .12em;
  transition: color 300ms ease-out;
}

.mailto:hover,
.mailto:focus-visible { color: var(--sea); }

.contact-note {
  margin: 2.4rem 0 0;
  max-width: 34rem;
  font-size: 15px;
  line-height: 1.7;
}

.contact-note + .contact-note { margin-top: 1rem; }


/* ---------------------------------------------------------------------------
   fuss — Impressum, Datenschutz, Shop
   --------------------------------------------------------------------------- */
.foot {
  padding: calc(6 * var(--vh)) var(--gutter) calc(7 * var(--vh));
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: .8rem 2.2rem;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.foot a { color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid var(--rule); }
.foot a:hover, .foot a:focus-visible { color: var(--sea); border-bottom-color: currentColor; }


/* ---------------------------------------------------------------------------
   shop / recht — die stillen Seiten
   Shop, Impressum and Datenschutz share one shape: the title rests on the rule,
   the text runs below it. Same tokens, same line, far less on the screen.
   --------------------------------------------------------------------------- */
/* The way back. Every page that is not the landing page carries her name above its
   own title, so the visitor is never one dead end away from the work. */
.crumb {
  margin: 0 0 1.5rem;
  font-size: 12px;
  letter-spacing: .18em;
  text-transform: uppercase;
}

.crumb a { color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid var(--rule); }
.crumb a:hover, .crumb a:focus-visible { color: var(--sea); border-bottom-color: currentColor; }

.page-title {
  margin: 0;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2.2rem, 7vw, 4.5rem);
  line-height: .95;
  letter-spacing: -.025em;
  text-transform: lowercase;
}

.prose { max-width: 38rem; }

.prose p { margin: 0 0 1.15rem; line-height: 1.75; }

.prose h2 {
  margin: 2.8rem 0 .8rem;
  font-family: var(--grotesk);
  font-weight: 400;
  font-size: 12px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.prose dl { margin: 0 0 1.15rem; }
.prose dt { font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); margin-top: 1rem; }
.prose dd { margin: .2rem 0 0; }

.prose ul { margin: 0 0 1.15rem; padding-left: 1.2rem; }
.prose li { margin-bottom: .4rem; line-height: 1.75; }

/* A value the client still has to supply. Loud on purpose — nobody should be able
   to publish this page and not notice it. */
.gap {
  background: var(--rule);
  padding: .05em .4em;
  font-size: .92em;
  letter-spacing: .04em;
}


/* ---------------------------------------------------------------------------
   Schmales Fenster I — the label steps under its plate
   Below this width the two no longer fit side by side without the plate growing
   into the label; once the label is underneath, the plate may take the full page
   — but it must now also leave room for the label BELOW the rule.
   --------------------------------------------------------------------------- */
@media (max-width: 1000px) {
  :root { --pw: 88vw; }

  .stanza {
    --ph: min(
            var(--pvh),
            calc(var(--pw) / var(--ratio)),
            calc((var(--horizon) - var(--sky-gap)) / var(--hz)),
            calc((calc(100 * var(--vh)) - var(--horizon) - var(--label-room)) / (1 - var(--hz)))
          );
  }

  .label,
  .stanza--left .label,
  .stanza--right .label {
    left: var(--gutter);
    right: var(--gutter);
    max-width: none;
    text-align: left;
    /* rule + the part of the plate hanging below the rule + a gap */
    top: calc(var(--horizon) + var(--ph) - var(--hz) * var(--ph) + 1.4rem);
  }

  .leaf-top { max-width: none; }
}


/* ---------------------------------------------------------------------------
   Schmales Fenster II — the rule moves up, the first screen stacks
   Phones are short: the band has to give height back to the contact block, or
   one of the two calls to action ends up below the fold.
   --------------------------------------------------------------------------- */
@media (max-width: 760px) {
  :root {
    --horizon: calc(46 * var(--vh));
    --hero-h: calc(65 * var(--vh));
  }

  .opening { min-height: 580px; }

  .opening-foot {
    bottom: calc(4 * var(--vh));
    display: block;
  }

  .asks {
    display: block;
    text-align: left;
    margin-top: 2rem;
  }

  .ask + .ask { margin-top: 1.6rem; }

  .show-meta { font-size: 12.5px; }

  .eyebrow { height: calc(20 * var(--vh)); height: round(calc(20 * var(--vh)), 1px); min-height: 110px; }

  .media-row { gap: 1.8rem; }
  .media { flex: 1 1 100%; }
  .media--clip { flex: 0 1 60%; }
}


/* ---------------------------------------------------------------------------
   Niedriges Fenster — the band clears the exhibition text and CTA
   Concept-only fix (2026-09-10): on short windows the opening band, sized off
   --hero-h, reached down far enough to sit under the exhibition text/CTA below it.
   A shorter band alone fixes it — --horizon (and so every OTHER horizon on the
   page) is never touched here.
   Its top edge must clear the tagline too: the second term of the min() is the
   height at which the band starts 1rem under the tagline (.opening-type top + one
   line of the name + the tagline's margin and line), divided by the painting's
   horizon fraction. Change .name or .tagline sizes and this has to follow.
   With JS the switch is html.vh-short, set by the head script from the pinned height;
   the media query is only the no-JS path. A live max-height query would swap the
   band whenever a phone's URL bar moves the height across 860px.
   --------------------------------------------------------------------------- */
:root {
  --hero-h-short: min(calc(40 * var(--vh)), calc((var(--horizon) - calc(7 * var(--vh)) - .84 * clamp(3.5rem, 12vw, 10rem)
                                  - 1.5rem - 15px * 1.55 - 1rem) / 0.7234));
}
@media (max-height: 860px) {
  :root:not(.js) { --hero-h: var(--hero-h-short); }
}
:root.vh-short { --hero-h: var(--hero-h-short); }


/* ---------------------------------------------------------------------------
   entwurf — the draft note (case-study copy only)
   --------------------------------------------------------------------------- */
.concept-note {
  position: absolute;
  z-index: 6;
  top: 0;
  right: 0;
  margin: 0;
  max-width: min(30rem, 100%);
  padding: .55rem var(--gutter) .6rem 1rem;
  background: var(--paper);
  color: var(--ink-soft);
  font-size: 12px;
  letter-spacing: .04em;
  line-height: 1.45;
}
.concept-note a { color: var(--ink); }


/* ---------------------------------------------------------------------------
   Gepinnte Fensterhöhe — the unit every vh above was rewritten to
   The head script sets --vh inline to 1% of the height it pinned; these two
   lines are the value before it runs, and without JS.
   --------------------------------------------------------------------------- */
:root { --vh: 1vh; }
@supports (height: 1svh) { :root { --vh: 1svh; } }

/* ---------------------------------------------------------------------------
   horizont — the two short asks share a row (windows wider than 760px)
   The English pass added the phone number as a third ask; stacked, the column grew
   to 179px and its top ask ran onto the band's lower edge (14px at 1440x900, 20px at
   1536x864). Email and phone carry no note, so side by side their underlines land at
   the same height — the reason for the stack (see .asks) does not apply to them.
   --------------------------------------------------------------------------- */
@media (min-width: 761px) {
  .asks { display: grid; grid-template-columns: auto auto; justify-content: end; justify-items: end; }
  .ask--major { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------------------
   Schriften da — two boxes on the first screen wait for the webfonts
   The CTA/exhibition block hangs off the bottom edge and the draft note off the
   right edge, so a font swap after the first paint moved both: a new line break
   or a new text width moves their top or left edge (measured 2026-09-10). Held at
   opacity 0 — which a layout shift does not count — until script.js sets
   html.fonts-in (both fonts loaded, or 3s). Without JS nothing is hidden.
   --------------------------------------------------------------------------- */
.js:not(.fonts-in) .opening-foot,
.js:not(.fonts-in) .concept-note { opacity: 0; }

/* ---------------------------------------------------------------------------
   werke — one sideways row (owner's request, 2026-09-11)
   The paintings hang side by side, like the "She chose the horizon." row on the
   case study, and the visitor moves ALONG the row. Every plate is still placed by
   the site's own rule — top = --horizon minus its horizon fraction of its height —
   so all seven painted horizons sit at one height. That alignment is the line now:
   the drawn 1px rule (.horizon) is gone from this page.
   Default = a native sideways scroller: the whole behaviour without JS and with
   reduced motion. script.js adds .row-pinned (end of this block) for everyone else.
   --------------------------------------------------------------------------- */
/* Plate width ceiling: the window less its gutters. The old 54vw/88vw ceiling kept
   room for a label BESIDE the plate; the label is above or below it now. */
:root { --pw: calc(100vw - 2 * var(--gutter)); }

.row {
  height: calc(100 * var(--vh));   /* one pinned window; --horizon is measured from its top */
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;  /* the end of the row is not a browser back-swipe */
}

.row-track {
  display: flex;
  width: max-content;
  height: 100%;
  padding: 0 var(--gutter);
  gap: max(2rem, 6vw);
}

/* A stanza is one column of the row: as wide as its plate, as tall as the window.
   The size ceilings are the site's own three (see .stanza) with the new --pw. The
   narrow layout's fourth one — room for the label under the plate — is gone: the
   label now takes whichever side has more room. */
#werke .stanza {
  flex: none;
  height: auto;
  min-height: 0;
  width: calc(var(--ph) * var(--ratio));
  --ph: min(
          var(--pvh),
          calc(var(--pw) / var(--ratio)),
          calc((var(--horizon) - var(--sky-gap)) / var(--hz))
        );
  /* Free window above and below the plate, from --hz, --ratio and --pvh alone. */
  --room-above: calc(var(--horizon) - var(--hz) * var(--ph));
  --room-below: calc(100 * var(--vh) - var(--horizon) - (1 - var(--hz)) * var(--ph));
  --cap-gap: 1.1rem;
}

#werke .plate { left: 0; right: auto; }

/* The label goes to the side with more room: a plate that reaches far down (horizon
   high in the canvas) gets it above, one that towers up gets it below. This first
   top is the fallback for browsers without sign(): always below. */
#werke .label {
  left: 0;
  right: 0;
  max-width: none;
  text-align: left;
  top: calc(var(--horizon) + (1 - var(--hz)) * var(--ph) + var(--cap-gap));
}

@supports (top: calc(sign(1px) * 1px)) {
  /* --below is 1 when the room under the plate is larger, else 0. The label's top is
     then the plate's bottom edge, or its top edge with the label lifted by its own
     height. One expression for all seven: no side is typed in per painting. */
  #werke .stanza { --below: max(0, sign(var(--room-below) - var(--room-above))); }
  #werke .label {
    top: calc(var(--room-above) - var(--cap-gap) + var(--below) * (var(--ph) + 2 * var(--cap-gap)));
    translate: 0 calc((var(--below) - 1) * 100%);
  }
}

/* The figure is a Tab stop but spans the whole window-tall column; the ring goes
   round the painting instead. */
#werke .stanza-fig:focus-visible { outline: none; }
#werke .stanza-fig:focus-visible .plate { outline: 2px solid var(--sea); outline-offset: 4px; }

/* "Digital illustrations" stands in the row where the illustrations begin, its words
   ending just above the horizon like the leaf titles. A fixed width, so a late font
   cannot change the row's length (and with it the page's height). */
#werke .row-track > .eyebrow {
  flex: none;
  width: 9rem;
  height: var(--horizon);
  min-height: 0;
  margin: 0;
  padding: 0 0 1.3rem;
}

/* Pinned (script.js): #werke carries the row's overflow as extra height, the row
   sticks to the top of the window across it, and the track is moved sideways. clip,
   not hidden: a hidden box can still be scrolled by focus, which would fight the
   transform. */
#werke.row-pinned .row {
  position: sticky;
  top: 0;
  overflow: hidden;
  overflow: clip;
  touch-action: pan-y pinch-zoom;  /* vertical swipes stay the browser's; sideways ones go to script.js */
}
#werke.row-pinned .row-track { will-change: transform; }

/* Phones: one painting per screen. They get the same --pw as every other window (the
   window less its gutters): 80vw until 2026-09-11 left a landscape painting 234px tall
   on a 390x844 phone, and the 24px of the next painting it kept free at the row's start
   never showed anyway (a painting is revealed once 20% of it is in view). */

/* ---------------------------------------------------------------------------
   Weniger Leerraum — less empty space (owner, 2026-09-11)
   Measured first (tools/case-study/measure_space.py): the empty screens were the
   text sections, the column "Digital illustrations" took in the row, and the band
   before the row. The paintings, their horizons and the label rule are untouched.
   --------------------------------------------------------------------------- */
/* Text sections: no window-tall minimum and no title pushed down to the horizon. That
   placement only meant something while a drawn rule and scroll-snap held each section
   at the top of the window; both are gone from this page. One even break between
   sections instead. Technique needs none: the row's own air below its paintings is its
   break. The shop (its own rule, no row) keeps the site's layout: #werke ~ scopes this. */
#werke ~ .leaf { min-height: 0; padding-top: calc(14 * var(--vh)); padding-bottom: 0; }
#werke ~ .leaf .leaf-top { min-height: 0; }
#werke ~ #technik { padding-top: 0; }
#werke ~ .leaf:last-child { padding-bottom: calc(14 * var(--vh)); }

/* "Works": less paper before the row, which has its own air above painting 1. Whole
   pixels, like the site's band (see .eyebrow): the row is placed under it. */
#werke > .eyebrow { height: calc(14 * var(--vh)); height: round(calc(14 * var(--vh)), 1px); min-height: 0; }
@media (max-width: 760px) {
  #werke > .eyebrow { height: calc(10 * var(--vh)); height: round(calc(10 * var(--vh)), 1px); }
}

/* "Digital illustrations" takes no column: width 0, and the negative margin gives back
   the second track gap (the same max(2rem, 6vw) as .row-track), so paintings 4 and 5
   are one ordinary gap apart. The words run to the right, over painting 5's room above,
   and end one label gap above its top edge: --ph and the top edge are the same
   expressions as #werke .stanza's, fed with painting 5's numbers (inline, from the
   builder). Painting 5's own label never takes that side: its room below is larger at
   every window this was measured at (see check_horizon.py). */
#werke .row-track > .eyebrow {
  width: 0;
  margin-right: calc(-1 * max(2rem, 6vw));
  white-space: nowrap;
  --ph: min(
          var(--pvh),
          calc(var(--pw) / var(--ratio)),
          calc((var(--horizon) - var(--sky-gap)) / var(--hz))
        );
  height: calc(var(--horizon) - var(--hz) * var(--ph));
  padding: 0 0 1.1rem;
}

/* ---------------------------------------------------------------------------
   Phone horizon (2026-09-11)
   The line moves from 46 to 50 % of the window: at 46 % a landscape painting with its
   horizon high in the canvas left 37 % of the window empty under its label. It stays
   higher only where 50 % would leave the opening's band under ~123px above the contact
   block (375x667). --foot-h = the contact block's measured height at that width.
   The band on every phone (not only short ones) is as tall as fits, up to 40 % of the
   window, between 1rem under the tagline and 1rem above the contact block: it ran 13px
   into the exhibition text at 375x667.
   --------------------------------------------------------------------------- */
@media (max-width: 760px) {
  :root {
    --foot-h: 273px;
    --horizon: min(calc(50 * var(--vh)), calc(max(calc(100 * var(--vh)), 580px) - calc(4 * var(--vh)) - var(--foot-h) - 50px));
  }
  /* :root:root outranks :root.vh-short and :root:not(.js) above. */
  :root:root {
    --hero-h: min(calc(40 * var(--vh)),
                  calc((var(--horizon) - calc(7 * var(--vh)) - .84 * clamp(3.5rem, 12vw, 10rem)
                        - 1.5rem - 15px * 1.55 - 1rem) / 0.7234),
                  calc((max(calc(100 * var(--vh)), 580px) - calc(4 * var(--vh)) - var(--foot-h) - 1rem
                        - var(--horizon)) / 0.2766));
  }
}
@media (max-width: 379px) { :root { --foot-h: 292px; } }
@media (max-width: 356px) { :root { --foot-h: 311px; } }
