/* ============================================================================
   Case study v2 "She paints horizons" (Anna Schley) — published, case-studies/
   v1 ("One artist, two routes") was a report and was rejected. This version is
   built out of the direction she chose: ONE hairline fixed across the window, and
   everything with a horizon — her paintings, the screenshots, even the embedded
   concept — placed so that its horizon lands on that line when its section rests.
   Sections are separated by bands of her own paint, each cut with an edge traced
   from a different canvas (tools/case-study/build_v2_assets.py). No brush presets.

   Palette and type come from the concept (her site's own): paper, ink, the rule's
   warm grey, one sea blue for links; Fraunces for display, Frank Ruhl Libre (her
   choice of body face), Martian Mono only for the measured numbers.
   Every block below is named like the data-sid of the section it styles.
   ========================================================================= */


/* ---------------------------------------------------------------------------
   Type — pinned instances in type/ (not the shared fonts/, which the concept
   build rewrites). swap, and the first screen's text waits for its faces (.fonts-wait):
   a late Fraunces still moved the headline itself by 3px (measured 2026-09-11).
   --------------------------------------------------------------------------- */
@font-face {
  font-family: 'Fraunces';
  src: url('type/fraunces-300-opsz144.woff2?v=3e695158') format('woff2');
  font-weight: 300;
  font-display: swap;
}
@font-face {
  font-family: 'Frank Ruhl Libre';
  src: url('type/frankruhllibre-400.woff2?v=f7f52176') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Martian Mono';
  src: url('type/martianmono-400.woff2?v=ff094d91') format('woff2');
  font-weight: 400;
  font-display: swap;
}


/* ---------------------------------------------------------------------------
   Tokens
   --------------------------------------------------------------------------- */
:root {
  --paper: #F1EDE4;
  --ink: #1E1B16;
  --ink-soft: #55524C;          /* 6.7:1 on paper, clears AA as body text */
  --rule: #B7AF9F;              /* the concept's own hairline colour: its line and ours merge */
  --sea: #2B5A67;

  --serif: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --text: 'Frank Ruhl Libre', Georgia, 'Times New Roman', serif;
  --mono: 'Martian Mono', 'Courier New', monospace;

  --g: max(20px, 5vw);
  /* Every height on this page is n * --vh, never vh: a phone URL bar sliding in or out
     changes the live window height by 56-100px, and with vh every section, painting and
     the line moved with it (measured 2026-09-11: up to 597px at 390x844 -> 744). The head
     script in index.html pins --vh to 1% of the window height and re-pins it only on a
     width change or a height change over 160px. Without JS: 1svh, which no URL bar moves. */
  --vh: 1vh;
  /* Height of the fixed line. Whole pixels: every placement below is "line minus a
     fraction of a height", and a fractional line would carry its fraction into all of them. */
  --horizon: calc(55 * var(--vh));
  --horizon: round(calc(55 * var(--vh)), 1px);
  --top: 56px;                  /* header 56: the opening band starts under it (88 while a 32px draft banner sat above) */
  --seam-h: clamp(52px, 7.5vw, 112px);
  /* The unit the hung paintings are sized in: tied to the window's height (so the tallest
     one never climbs out of the screen) unless the width runs out first. */
  --u: min(var(--vh), .62vw);
}
@supports (height: 1svh) { :root { --vh: 1svh; } }

*, *::before, *::after { box-sizing: border-box; }

/* No scroll-snap (removed 2026-09-11). Proximity snap pulled every short wheel scroll
   back to the section it started in: 40 wheel steps of 90px left the page at 142px, and
   it then slid back to 0 (check_cs.py). The horizons still meet the line wherever a
   section's top is at the top of the window; the page no longer drags you there. */
html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: 1.06rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;            /* clip, not hidden: hidden would make body a scroll container */
  position: relative;
}

img { display: block; max-width: 100%; }
figure { margin: 0; }
h1, h2, p { margin: 0; }

a { color: var(--sea); text-underline-offset: .22em; text-decoration-thickness: 1px; }
:focus-visible { outline: 2px solid var(--sea); outline-offset: 4px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--paper); color: var(--ink); padding: .7rem 1.2rem; border: 1px solid var(--ink);
}
.skip:focus { left: 1rem; top: 1rem; }

/* One size of section: a screen. Container, so widths inside can be
   taken from the section (cqw) — 100vw would include a desktop scrollbar and overflow. */
main > section {
  position: relative;
  min-height: calc(100 * var(--vh));
  container-type: inline-size;
}

/* The h2 of each section: one voice, set differently per section by position only. */
h2 {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.75rem, 3.1vw, 3rem);
  line-height: 1.06;
  letter-spacing: -.015em;
}


/* ---------------------------------------------------------------------------
   header — studio name, the two sites
   Absolute and a fixed height (not in the flow): the opening band is measured from
   --top, so the header's height and --top must change together.
   --------------------------------------------------------------------------- */
.head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 6;
  height: 56px;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0 var(--g);
  font-size: 15px;
}
.head a { color: var(--ink); text-decoration: none; }
.head-name { letter-spacing: .02em; }
.head-nav { display: flex; gap: clamp(1rem, 2.4vw, 2.2rem); }
.head-nav a { color: var(--ink-soft); border-bottom: 1px solid var(--rule); padding-bottom: 2px; }
.head a:hover, .head a:focus-visible { color: var(--sea); }


/* ---------------------------------------------------------------------------
   horizon-line — the one unbroken rule, now INVISIBLE (Beri, 2026-09-11: a line
   through the middle of the screen with no job to do). The element stays as the
   measuring reference for verify_v2.py; the paintings still align their horizons
   to this height, so the alignment itself shows the horizon.
   --------------------------------------------------------------------------- */
.horizon {
  position: fixed; left: 0; right: 0; top: var(--horizon);
  height: 1px;
  background: transparent;
  z-index: 5;
  pointer-events: none;
}


/* ---------------------------------------------------------------------------
   Seams — bands of her paint between sections
   The mask is the traced band (her contour on top); the texture is a strip of the
   same canvas. Centred on the section's top edge, so it adds no height and moves
   nothing. With JS the paint is laid left to right by a straight edge, the way a
   knife lays it; the shape itself never moves.
   --------------------------------------------------------------------------- */
.seam {
  position: absolute; left: 0; right: 0; top: 0; z-index: 3;
  height: var(--seam-h);
  transform: translateY(-50%);
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.seam-paint { width: 100%; height: 100%; max-width: none; object-fit: cover; }

.seam--pink-ridge         { -webkit-mask-image: url('img/seam-pink-ridge.svg?v=eae6bb36');         mask-image: url('img/seam-pink-ridge.svg?v=eae6bb36'); }
.seam--dunes-clouds       { -webkit-mask-image: url('img/seam-dunes-clouds.svg?v=9ebc4cd2');       mask-image: url('img/seam-dunes-clouds.svg?v=9ebc4cd2'); }
.seam--storm-blue         { -webkit-mask-image: url('img/seam-storm-blue.svg?v=1cd1bb93');         mask-image: url('img/seam-storm-blue.svg?v=1cd1bb93'); }
.seam--grey-mountain-lake { -webkit-mask-image: url('img/seam-grey-mountain-lake.svg?v=514caacd'); mask-image: url('img/seam-grey-mountain-lake.svg?v=514caacd'); }
.seam--storm-foam         { -webkit-mask-image: url('img/seam-storm-foam.svg?v=79a6501e');         mask-image: url('img/seam-storm-foam.svg?v=79a6501e'); }
.seam--dune-path-sea      { -webkit-mask-image: url('img/seam-dune-path-sea.svg?v=71e67c2d');      mask-image: url('img/seam-dune-path-sea.svg?v=71e67c2d'); }

.js .seam-paint { transform: translateX(-101%); }
.js .seam.in .seam-paint { transform: none; transition: transform 1.3s cubic-bezier(.3, .7, .2, 1); }


/* ---------------------------------------------------------------------------
   opening — the panorama and the headline
   The band's top is the header's bottom; its height is whatever makes 72.34 % of it
   end on the line: (line - top) / .7234. object-position puts the painted horizon at
   72.34 % of the box however it is cropped, so the horizon lands on the line.
   --------------------------------------------------------------------------- */
.opening {
  --band: calc((var(--horizon) - var(--top)) / .7234);
  /* A screen, or taller when the byline and the two-line h1 under the band need it: the
     next band of paint is centred on this section's bottom edge, and on short, wide
     windows (1280x800, 1366x768) the h1's descenders ran into it. The second term is
     the h1's bottom (the same terms as .opening-type, byline and h1 below) plus half a
     seam and 1rem, rounded up to a whole pixel so every section below stays on it. */
  height: max(calc(100 * var(--vh)),
              round(up, calc(var(--top) + var(--band) + clamp(1rem, calc(2.4 * var(--vh)), 1.6rem) + 15px * 1.6
                             + .5rem + 2 * 1.02 * clamp(2.4rem, 4.2vw, 4.2rem) + var(--seam-h) / 2 + 1rem), 1px));
  min-height: 520px;
  overflow: clip;
}

.pano {
  position: absolute; left: 0; top: var(--top);
  width: 100%; height: var(--band);
  object-fit: cover;
  object-position: 50% 72.34%;
}

/* Under the band, on paper. Nothing flows below the h1, so its font arriving late can
   grow it downwards but never shift anything else. */
.opening-type {
  position: absolute; left: var(--g); right: var(--g);
  top: calc(var(--top) + var(--band) + clamp(1rem, calc(2.4 * var(--vh)), 1.6rem));
}
/* Held invisible until its two faces are in (index.html head script, 3 s cap): a late
   Fraunces moved the headline 3px. Without JS the class is never set. */
.fonts-wait .opening-type { opacity: 0; }
.byline { font-size: 15px; letter-spacing: .04em; color: var(--ink-soft); }
h1 {
  margin-top: .5rem;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2.4rem, 4.2vw, 4.2rem);
  line-height: 1.02;
  letter-spacing: -.02em;
}
h1 span { display: block; }


/* ---------------------------------------------------------------------------
   directions — A on the line, B resting on it, C hanging from it
   A's own hairline is at 55 % of its screenshot, so A is placed with the same rule
   as a painting: top = line - .55 x height.
   --------------------------------------------------------------------------- */
.directions {
  /* width capped so A never needs more sky than there is above the line */
  --aw: min(54cqw, calc((var(--horizon) - 150px) / .55 * 1.6));
  --ah: calc(var(--aw) / 1.6);
  --bw: min(30cqw, calc((var(--horizon) - 124px) * 1.6));
  --bh: calc(var(--bw) / 1.6);
}
/* Sits right on top of A, wherever A lands: its bottom is A's top minus a gap. */
.dir-title {
  position: absolute; left: var(--g);
  top: calc(var(--horizon) - .55 * var(--ah) - 1.4rem);
  transform: translateY(-100%);
  max-width: 14em;
}
.dir { position: absolute; }
.dir img { width: 100%; height: auto; box-shadow: 0 1px 0 rgba(30, 27, 22, .08), 0 18px 40px -24px rgba(30, 27, 22, .45); }
.dir--a { left: var(--g); width: var(--aw); top: calc(var(--horizon) - .55 * var(--ah)); }
.dir--b { right: var(--g); width: var(--bw); top: calc(var(--horizon) - 14px - var(--bh)); }
.dir--c { right: var(--g); width: var(--bw); top: calc(var(--horizon) + 14px); }

.dir figcaption {
  position: absolute; left: 0;
  font-size: 15px;
  letter-spacing: .04em;
  white-space: nowrap;
}
.dir figcaption em { font-style: normal; color: var(--sea); margin-left: .6em; }
.dir--a figcaption, .dir--c figcaption { top: calc(100% + .7rem); }
.dir--b figcaption { bottom: calc(100% + .5rem); }


/* ---------------------------------------------------------------------------
   hanging — four canvases, four horizon heights, one line
   Each work: height = --pu units, width from its true aspect, and pushed down by
   (line - hz x height) so its painted horizon meets the line.
   --------------------------------------------------------------------------- */
.hanging { height: calc(100 * var(--vh)); min-height: 560px; }
.wall {
  position: absolute; left: var(--g); right: var(--g); top: 0;
  height: calc(var(--horizon) + 24 * var(--u) + 8px);
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: clamp(16px, 2vw, 32px);
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
}
.wall::-webkit-scrollbar { display: none; }
.work {
  flex: none;
  height: calc(var(--pu) * var(--u));
  width: calc(var(--pu) * var(--u) * var(--ratio));
  margin-top: calc(var(--horizon) - var(--hz) * var(--pu) * var(--u));
}
.work img { width: 100%; height: 100%; object-fit: cover; }

/* Under the lowest painting. Wide enough that the sentence stays on one line: two
   lines ran into the next band of paint at 1440 x 900. */
.hang-text {
  position: absolute; right: var(--g);
  top: calc(var(--horizon) + 24 * var(--u) + 1.2rem);
  max-width: 44rem;
  text-align: right;
}
.hang-text p { margin-top: .8rem; color: var(--ink-soft); }


/* ---------------------------------------------------------------------------
   concept — the concept itself, framed on the line
   The concept draws its own line at 55 % of its window (on a window 760 px or
   narrower: 50 %, or higher on short ones, see the phone block below). --fline is
   that line's distance from the frame's top. Frame height is rounded to 20 px (50 px)
   so that line falls on a whole pixel, and the frame is lifted so that line lands on
   ours: one line, two documents. The concept uses the same rule colour, so the two merge.
   --------------------------------------------------------------------------- */
.concept {
  --fh: 680px;
  --fh: round(down, calc(76 * var(--vh)), 20px);
  --fline: calc(.55 * var(--fh));
}
.concept-frame {
  position: absolute; right: var(--g);
  width: 62cqw;
  height: var(--fh);
  top: calc(var(--horizon) - var(--fline));
  border: 0;
  background: var(--paper);
  box-shadow: 0 0 0 1px rgba(30, 27, 22, .14), 0 24px 60px -30px rgba(30, 27, 22, .5);
}
/* concept-preview — the phone/coarse-pointer fallback (see the mobile block below for its
   geometry and for why the live frame is a scroll trap there). Hidden here; shown, and
   .concept-slot hidden, only under that block's query. */
.concept-preview { display: none; }
/* Left of the frame: the title ends on the line, the link starts under it. */
.concept-text {
  position: absolute; left: var(--g);
  width: calc(38cqw - 3 * var(--g));
  top: 0; bottom: 0;
}
.concept-text h2 { position: absolute; left: 0; right: 0; bottom: calc(100% - var(--horizon) + 1.4rem); }
.concept-text .more { position: absolute; left: 0; top: calc(var(--horizon) + 1.4rem); }

.more, .studio-links a {
  display: inline-block;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: .15em;
  letter-spacing: .03em;
}
.more:hover, .more:focus-visible, .studio-links a:hover, .studio-links a:focus-visible { color: var(--sea); }


/* ---------------------------------------------------------------------------
   wix — her live site, as it is
   Placed like a painting: the dunes-clouds horizon inside the screenshot is at
   73.99 % of it (template-matched against the master), so top = line - .7399 x h.
   --------------------------------------------------------------------------- */
.wix {
  --ww: min(56cqw, calc((var(--horizon) - 80px) / .7399 * 1.6));
  --wh: calc(var(--ww) / 1.6);
  /* As tall as its content, not a screen (2026-09-11): 160-263 px of paper stood under
     it, and with the empty top of .difference that made a window 21 % covered at
     1280x800 (check_cs.py). Ends under the lower of the screenshot's caption and the
     text (its budget: two h2 lines, the three lines p runs at every width over 1000px), plus half a band of paint and
     1rem, rounded up so the sections below stay on whole pixels. */
  min-height: 0;
  height: round(up, calc(max(var(--horizon) + .2601 * var(--wh) + .7rem + 15px * 1.6,
                             var(--horizon) + 1.6rem + 2 * 1.06 * clamp(1.75rem, 3.1vw, 3rem) + .9rem + 3 * 1.06rem * 1.6)
                         + var(--seam-h) / 2 + 1rem), 1px);
}
.wix-shot { position: absolute; right: var(--g); width: var(--ww); top: calc(var(--horizon) - .7399 * var(--wh)); }
.wix-shot img { width: 100%; height: auto; box-shadow: 0 0 0 1px rgba(30, 27, 22, .14), 0 18px 40px -24px rgba(30, 27, 22, .45); }
.wix-shot figcaption { margin-top: .7rem; text-align: right; font-size: 15px; letter-spacing: .04em; }
.wix-text {
  position: absolute; left: var(--g);
  top: calc(var(--horizon) + 1.6rem);
  width: calc(100cqw - var(--ww) - 3 * var(--g));
  max-width: 24rem;
}
.wix-text p { margin-top: .9rem; color: var(--ink-soft); }


/* ---------------------------------------------------------------------------
   difference — the two strokes
   The line is the time axis: 12 s = the full width. The concept's stroke rests on
   the line, the Wix stroke hangs under it. Each stroke is a masked strip of the
   dunes-clouds relief (the painting both sites open with); the image inside is sized
   to the whole track (100cqw), so both strokes show the paint at the same scale.
   --------------------------------------------------------------------------- */
/* As tall as its content, not a screen (2026-09-11): the screen-tall section left the
   emptiest window of the page, 21 % covered at 1440x900 and 19 % at 390x844
   (check_cs.py). The height ends --more-lines lines of .diff-more below its top, plus
   half the next band of paint and 1rem, rounded up so the sections below stay on
   whole pixels. .diff-more runs 2 lines at every width over 760px (40rem wide at most)
   and 3-5 on phones. */
.difference {
  --sh: clamp(16px, 2.1vw, 30px);
  --more-lines: 2;
  height: round(up, calc(var(--horizon) + 2 * var(--sh) + clamp(3.2rem, calc(9 * var(--vh)), 5rem)
                         + var(--more-lines) * 12px * 1.8 + var(--seam-h) / 2 + 1rem), 1px);
  min-height: 0;
}
.diff-head {
  position: absolute; left: var(--g); right: var(--g);
  bottom: calc(100% - var(--horizon) + var(--sh) + clamp(1.6rem, calc(5 * var(--vh)), 3.2rem));
}
.diff-head h2 { font-size: clamp(2.1rem, 5vw, 4.8rem); }
.diff-key { margin-top: 1rem; color: var(--ink-soft); }

.strokes {
  position: absolute; left: var(--g); right: var(--g);
  top: calc(var(--horizon) - var(--sh));
  height: calc(2 * var(--sh) + 1px);
  container-type: inline-size;
}
.stroke { position: absolute; left: 0; height: var(--sh); width: calc(100% * var(--t) / 12); overflow: hidden; }
.stroke--concept { top: 0; }
.stroke--wix { top: calc(var(--sh) + 1px); }
.stroke-paint {
  height: 100%;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.stroke--concept .stroke-paint { -webkit-mask-image: url('img/stroke-concept.svg?v=54f5a45a'); mask-image: url('img/stroke-concept.svg?v=54f5a45a'); }
.stroke--wix .stroke-paint { -webkit-mask-image: url('img/stroke-wix.svg?v=f9d2d0a5'); mask-image: url('img/stroke-wix.svg?v=f9d2d0a5'); }
.stroke-paint img { width: 100cqw; max-width: none; height: 100%; object-fit: cover; object-position: 0 55%; }

/* Laid in real time once the section is in view (script.js adds .run). */
.js .stroke-paint { transform: translateX(-100%); }
.js .run .stroke-paint { transform: none; transition: transform calc(var(--t) * 1s) linear; }

.stroke-label {
  position: absolute;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  white-space: nowrap;
}
.stroke-label--concept { left: calc(100% * 2.4 / 12 + 14px); top: calc(var(--sh) / 2 - 6px); }
.stroke-label--wix { right: 0; top: calc(2 * var(--sh) + 14px); }

.diff-more {
  position: absolute; left: var(--g);
  top: calc(var(--horizon) + 2 * var(--sh) + clamp(3.2rem, calc(9 * var(--vh)), 5rem));
  max-width: 40rem;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.8;
  color: var(--ink-soft);
}


/* ---------------------------------------------------------------------------
   studio — the conclusion, and the closing painting on the line
   storm-foam's horizon (the far shore) is at 46.03 %: same rule as the opening.
   --------------------------------------------------------------------------- */
.studio { height: calc(100 * var(--vh)); min-height: 600px; --ch: calc(50 * var(--vh)); }
.closing {
  position: absolute; left: 0; width: 100%;
  height: var(--ch);
  top: calc(var(--horizon) - .4603 * var(--ch));
  object-fit: cover;
  object-position: 50% 46.03%;
}
.studio-line {
  position: absolute; left: var(--g); right: var(--g);
  bottom: calc(100% - var(--horizon) + .4603 * var(--ch) + clamp(1.2rem, calc(3 * var(--vh)), 2rem));
  max-width: 22em;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.5rem, 2.8vw, 2.7rem);
  line-height: 1.12;
  letter-spacing: -.01em;
}
.studio-links {
  position: absolute; left: var(--g); right: var(--g);
  top: calc(var(--horizon) + .5397 * var(--ch) + clamp(1.2rem, calc(3 * var(--vh)), 2rem));
  display: flex; flex-wrap: wrap; gap: .8rem 2.4rem;
}


/* ---------------------------------------------------------------------------
   footer
   --------------------------------------------------------------------------- */
.foot {
  padding: 3rem var(--g) 1.5rem;
  font-size: 14px;
  color: var(--ink-soft);
}
.foot a { color: inherit; }


/* ---------------------------------------------------------------------------
   Where a section grows past one screen, its text stays absolute and the section
   gets an explicit height rounded UP to a whole pixel. Text in the flow has
   fractional heights (28 px x 1.06 per line...), and every fraction would push the
   sections below it off the line by that fraction at their rest position.
   --------------------------------------------------------------------------- */

/* <= 1332 px: the concept frame takes the full width (it must stay wider than 760 px
   so the concept keeps its 55 % line) and its text moves under it. 1332, not 1239
   (2026-09-11): a 62cqw frame under 826 px (windows 1240-1332 wide) has the concept's
   contact block wrap to two rows, and at 1280x800 it ran 33 px into the concept's band. */
@media (max-width: 1332px) {
  .concept { --text-top: calc(var(--horizon) + var(--fh) - var(--fline) + 1.5rem);
             height: round(up, calc(var(--text-top) + 190px), 1px); }
  /* explicit width: an iframe is a replaced element, left+right alone leave it at 300 px */
  .concept-frame { left: var(--g); width: calc(100cqw - 2 * var(--g)); }
  .concept-text { top: var(--text-top); bottom: auto; right: var(--g); width: auto; }
  .concept-text h2, .concept-text .more { position: static; }
  .concept-text .more { margin-top: 1rem; }
}

/* concept-preview — phones, or ANY coarse pointer at any width (2026-09-11): a live
   iframe there was a scroll trap. Measured at 390x844: the frame rendered 350x750, and a
   wheel or touch drag starting over it scrolled the CONCEPT's own page, not this one — a
   visitor could only move the page from the 20px side gutters. Shown instead: a static
   shot of the concept's own first screen, sized and placed like a painting on this page —
   its own captured aspect (390x844) and FRACTION .5 (the concept's own --horizon on a
   window that tall, measured the same way as every other horizon here: a probe at
   var(--horizon), same technique as verify_v2.py's MEASURE) giving its top, so it lands on
   the line exactly as the iframe did. No JS needed either way: this query alone decides
   it, a phone with JS off gets the same fix. script.js matches the same condition and
   skips creating the iframe there, so nothing is fetched for a box that stays hidden.
   .concept-text is hidden here: its "Open it full screen" line duplicated the preview's
   own "Scroll the concept" link and, stacked right under it, overlapped it (measured). */
@media (max-width: 917px), (pointer: coarse) {
  .concept-slot { display: none; }
  .concept {
    --pw: min(760px, calc(100cqw - 2 * var(--g)));
    --ph: calc(var(--pw) * 844 / 390);
    height: round(up, calc(var(--horizon) + .5 * var(--ph) + 1.5rem + 24px + 1rem), 1px);
  }
  .concept-preview {
    display: block;
    position: absolute; right: var(--g);
    width: var(--pw);
    top: calc(var(--horizon) - .5 * var(--ph));
  }
  .concept-preview img { display: block; width: 100%; height: auto; box-shadow: 0 0 0 1px rgba(30, 27, 22, .14), 0 24px 60px -30px rgba(30, 27, 22, .5); }
  .concept-preview .more { display: block; margin-top: 1rem; }
  .concept-text { display: none; }
}

/* <= 1000 px: the Wix screenshot takes the width, its text goes under it. */
@media (max-width: 1000px) {
  .wix { --ww: calc(100cqw - 2 * var(--g));
         --text-top: calc(var(--horizon) + .2601 * var(--wh) + 3.4rem);
         height: round(up, calc(var(--text-top) + 280px), 1px); }
  .wix-shot { left: var(--g); }
  .wix-text { top: var(--text-top); right: var(--g); width: auto; max-width: 34rem; }
}

/* <= 900 px: directions in one column. A keeps its place on the line; B and C follow
   under it at the same width, each placed by calc so the section height stays exact. */
@media (max-width: 900px) {
  .directions {
    --aw: calc(100cqw - 2 * var(--g));
    --bw: var(--aw);
    --b-top: calc(var(--horizon) + .45 * var(--ah) + 80px);
    --c-top: calc(var(--b-top) + var(--bh) + 72px);
    height: round(up, calc(var(--c-top) + var(--bh) + 104px), 1px);
  }
  .dir--b { top: var(--b-top); }
  .dir--c { top: var(--c-top); }
  .dir--b figcaption { bottom: auto; top: calc(100% + .7rem); }
}


/* ---------------------------------------------------------------------------
   Phones (<= 760 px): the line moves up (phones are short), the hung paintings
   become a sideways row, and the traced edges are shown at a readable scale
   instead of squeezing a 1000-unit contour into 390 px.
   --------------------------------------------------------------------------- */
@media (max-width: 760px) {
  :root {
    /* 52 %, not the concept's 46: this page has no contact block to fit under the
       opening, and the lower line gives the headline the space the band leaves. */
    --horizon: calc(52 * var(--vh));
    --horizon: round(calc(52 * var(--vh)), 1px);
    --u: calc(.9 * var(--vh));
  }
  body { font-size: 1rem; }
  .head { font-size: 14px; }
  .head-nav a:first-child { display: none; }   /* the concept is linked twice below; the header keeps her site */

  .seam { -webkit-mask-size: 260% 100%; mask-size: 260% 100%; -webkit-mask-position: var(--mx, 30%) 0; mask-position: var(--mx, 30%) 0; }
  .seam--dunes-clouds { --mx: 70%; }
  .seam--storm-blue { --mx: 10%; }
  .seam--storm-foam { --mx: 55%; }
  .seam--dune-path-sea { --mx: 85%; }

  .wall {
    left: 0; right: 0;
    justify-content: flex-start;
    padding-inline: var(--g);
    scroll-padding-inline: var(--g);
    scroll-snap-type: x mandatory;
  }
  .work { scroll-snap-align: start; }
  .hang-text { left: var(--g); text-align: left; }

  .diff-more { right: var(--g); }
  .difference { --more-lines: 5; }   /* 3-4 lines at 360-430, 5 at 320 */
  /* Its text is 121 px at every phone width (h2 + 3 lines): 190 px leaves one more line,
     half a band of paint and 1rem. Not a screen: 159-209 px of paper stood under the
     text, the emptiest window on a phone (19 % covered at 390x844, check_cs.py). */
  .wix { height: round(up, calc(var(--text-top) + 190px), 1px); min-height: 0; }
  .studio { --ch: calc(40 * var(--vh)); }
}


/* ---------------------------------------------------------------------------
   Reduced motion: no knife, no timed strokes. Everything stands done.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js .seam-paint, .js .stroke-paint { transform: none; transition: none; }
}

/* ---------------------------------------------------------------------------
   Footer sitemap - the light variant
   WHY this one page paints the block differently (2026-09-21): the markup is the
   same on all 26 pages of sitemap.xml, so a crawler that does not run JavaScript
   finds the same link graph everywhere. The PAINT is not. Everywhere else the
   block is a near-black Archivo panel; here it sat under a warm-cream English
   case study set in Fraunces and Frank Ruhl Libre and read as a different site
   stapled to the bottom of this one. So it uses this page's own tokens - paper
   ground, --ink-soft for the quiet text, --ink for the links, --sea on hover -
   and a --rule hairline where the dark version has a hard edge. This page does
   not load css/werk.css, which is why the rules live here at all.
   --------------------------------------------------------------------------- */
.footer-sitemap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 1.4rem 2rem;
  /* No side padding: .foot already carries --g, and an inset here would break
     the left edge the whole page is aligned to. */
  margin: 0 0 2rem;
  padding: 1.8rem 0 0;
  /* A hairline in the page's own rule colour, not an edge: the block should
     read as the last paragraph of this footer, not as a panel laid over it. */
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  font-family: var(--text);
  font-size: 14px;
  line-height: 1.5;
  text-align: left;
}
/* Mono at the size the page uses for its other measured labels. */
.footer-sitemap .fs-h {
  margin: 0 0 .7rem;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.footer-sitemap a {
  display: block;
  padding: .2rem 0;
  max-width: 100%;
  color: var(--ink);
  font-size: 14px;
  text-decoration: none;
  overflow-wrap: break-word;
  transition: color .15s ease;
}
.footer-sitemap a:hover { color: var(--sea); }
/* Two columns rather than five at phone width: 148px tracks overflow a 390px
   viewport once the footer gutter is subtracted. */
@media (max-width: 560px) {
  .footer-sitemap { grid-template-columns: 1fr 1fr; gap: 1.1rem 1rem; }
}
