/* ============================================================
   ALEX KOCH — TYPE SPECIMEN SYSTEM
   Two grounds, one typeface, no third colour.
   Every spread inverts against the one before it.

   This sheet is self-contained. front-page.php dequeues main.css
   and the block library, so nothing here is an override — it is
   the whole stylesheet for the front page.
   ============================================================ */

@font-face{font-family:'N27';font-style:normal;font-weight:300;font-display:swap;src:url(../fonts/n27-light-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:300;font-display:swap;src:url(../fonts/n27-lightitalic-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/n27-regular-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:normal;font-weight:500;font-display:swap;src:url(../fonts/n27-medium-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:normal;font-weight:700;font-display:swap;src:url(../fonts/n27-bold-webfont.woff2) format('woff2');}

:root{
  --ink:#0B0B0B;
  --paper:#F7F6F2;

  --mar: clamp(18px, 5vw, 72px);
  --gut: clamp(10px, 1.5vw, 26px);

  --ff: 'N27', 'Helvetica Neue', Arial, sans-serif;

  --t-label: 10.5px;
  --t-cap: 13px;

  /* The hero's own small size. It is declared here rather than on the hero
     because the nav is set in it too: the standing links and the mark read
     against the hero's row of small type on first view, and two sizes a few
     tenths apart read as a mistake rather than as a hierarchy. */
  --t-hero-sm: clamp(12px, .92vw, 16px);
  --t-body: clamp(14px, 1.02vw, 16px);
  --t-lead: clamp(19px, 2.05vw, 29px);
  --t-h3: clamp(18px, 1.6vw, 23px);
  --t-h2: clamp(30px, 4.6vw, 70px);
  --t-mega: clamp(44px, 14.1vw, 232px);

  /* the fixed nav has to clear itself when an anchor lands */
  --navclear: clamp(58px, 7vw, 92px);

  color-scheme: only light;
}

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

html{ -webkit-text-size-adjust:100%; scroll-behavior:smooth; }

body{
  font-family: var(--ff);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.45;
  background: var(--ink);
  color: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

::selection{ background:#0B0B0B; color:#F7F6F2; }

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

/* ---------- skip link -------------------------------------- */

.skip{
  position:absolute; left:-9999px; top:0; z-index:200;
  background:#F7F6F2; color:#0B0B0B;
  padding:12px 18px;
  font-size: var(--t-label); letter-spacing:.185em; text-transform:uppercase;
  text-decoration:none;
}
.skip:focus{ left:0; }

/* ---------- grounds -------------------------------------- */

.spread{
  --bg: var(--paper);
  --fg: var(--ink);
  --hair: rgba(11,11,11,.20);
  --mute: rgba(11,11,11,.55);
  --flare:#A8231A;
  background: var(--bg);
  color: var(--fg);
  padding: clamp(56px, 9vw, 140px) var(--mar);
  position: relative;
  overflow-x: clip;
}
.spread.ink{
  --bg: var(--ink);
  --fg: var(--paper);
  --hair: rgba(247,246,242,.24);
  --mute: rgba(247,246,242,.58);
  --flare:#F1543A;
}
.spread.tight{ padding-top: clamp(30px,4vw,58px); padding-bottom: clamp(30px,4vw,58px); }
.spread.tall{ min-height: 100dvh; display:flex; flex-direction:column; justify-content:space-between; }

/* an anchored section must not open underneath the fixed nav */
.spread[id], #contact-form{ scroll-margin-top: var(--navclear); }
#top{ scroll-margin-top: 0; }

/* ---------- grid ----------------------------------------- */

.grid{
  display:grid;
  grid-template-columns: repeat(12, minmax(0,1fr));
  column-gap: var(--gut);
  row-gap: clamp(20px,2.4vw,36px);
}
/* grid items default to min-width:auto and refuse to shrink below their
   content; without this a long line pushes the track past the viewport */
.grid > *{ min-width: 0; }
.c1-2{grid-column:1/3}   .c1-3{grid-column:1/4}   .c1-4{grid-column:1/5}
.c1-5{grid-column:1/6}   .c1-6{grid-column:1/7}   .c1-7{grid-column:1/8}
.c1-8{grid-column:1/9}   .c1-12{grid-column:1/-1}
.c3-8{grid-column:3/9}   .c3-9{grid-column:3/10}  .c3-12{grid-column:3/13}
.c4-8{grid-column:4/9}   .c4-12{grid-column:4/13}
.c5-7{grid-column:5/8}   .c5-8{grid-column:5/9}   .c5-9{grid-column:5/10}
.c5-12{grid-column:5/13} .c6-9{grid-column:6/10}   .c6-12{grid-column:6/13}
.c7-10{grid-column:7/11} .c7-12{grid-column:7/13}
.c8-10{grid-column:8/11} .c8-12{grid-column:8/13}
.c9-12{grid-column:9/13} .c10-12{grid-column:10/13} .c11-12{grid-column:11/13}

/* ---------- type roles ------------------------------------ */

.label{
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing:.185em;
  text-transform: uppercase;
  line-height:1.4;
  color: var(--mute);
}
.label.solid{ color: var(--fg); }

.cap{ font-size: var(--t-cap); line-height:1.4; color: var(--mute); }
.cap.solid{ color: var(--fg); }

.mega{
  font-size: var(--t-mega);
  font-weight:700;
  line-height:.78;
  letter-spacing:-.035em;
  text-transform: uppercase;
  color: var(--fg);
}

.h2{
  font-size: var(--t-h2);
  font-weight:700;
  line-height:.96;
  letter-spacing:-.028em;
  text-wrap: balance;
}
.h2.light{ font-weight:300; letter-spacing:-.018em; line-height:1.02; }

.h3{
  font-size: var(--t-h3);
  font-weight:700;
  line-height:1.12;
  letter-spacing:-.018em;
}

.lead{
  font-size: var(--t-lead);
  font-weight:300;
  font-style: italic;
  line-height:1.24;
  letter-spacing:-.012em;
  text-wrap: pretty;
}

.prose p{ max-width: 62ch; line-height:1.52; }
.prose p + p{ margin-top: .9em; }
.prose.narrow p{ max-width: 34ch; line-height:1.4; }

.dim{ color: var(--mute); }

/* ---------- bleed / cropped display ----------------------- */

/* type allowed to run past the right edge and be cropped by the frame */
.bleed{
  overflow-x: clip;
  overflow-y: visible;
  margin-right: calc(var(--mar) * -1);
}
/* set larger than the fitted display, so the frame cuts deep into a word
   and reads as a decision rather than an accident */
.bleed .mega{ white-space: nowrap; font-size: clamp(56px, 17.6vw, 300px); }

/* four-line display, sized to stay inside the margins */
.mega.four{ font-size: clamp(40px, 12.5vw, 205px); }

/* long-line display: sized off the widest line so nothing is cut */
.mega.fit{ font-size: clamp(34px, 11vw, 180px); }

/* keeps hero copy clear of the rotated edge type */
.inset-l{ margin-left: clamp(0px, 13vw, 210px); }

/* ---------- hairlines ------------------------------------- */

.rule{ border:0; border-top:1px solid var(--hair); width:100%; }

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

.nav{
  position: fixed;
  top:0; left:0; right:0;
  z-index: 90;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: 1rem;
  padding: clamp(14px,1.7vw,22px) var(--mar);
  mix-blend-mode: difference;
  color:#FFFFFF;
  pointer-events:none;
}
.nav > *{ pointer-events:auto; }
.nav-mark{
  font-size: var(--t-hero-sm);
  font-weight:700;
  letter-spacing:-.01em;
  background:none; border:0; color:inherit; cursor:pointer;
  font-family: var(--ff);
  text-decoration:none;
}
.nav-links{ display:flex; gap: clamp(14px,2.4vw,40px); }
.nav-links a{
  background:none; border:0; padding:0; cursor:pointer;
  font-family: var(--ff);
  font-size: var(--t-hero-sm);
  font-weight:400;
  color:inherit;
  text-decoration:none;
  opacity:.62;
  transition: opacity .25s cubic-bezier(.16,1,.3,1);
}
.nav-links a:hover{ opacity:1; }
.nav a:focus-visible{ outline:2px solid currentColor; outline-offset:4px; }

@supports not (mix-blend-mode: difference){
  .nav{ mix-blend-mode:normal; background:#0B0B0B; color:#F7F6F2; }
}

/* the WordPress admin bar owns the top of the viewport when signed in */
body.admin-bar .nav{ top:32px; }
@media screen and (max-width:782px){
  body.admin-bar .nav{ top:46px; }
}

/* ---------- links & buttons ------------------------------- */

a{ color: inherit; }

.link{
  display:inline-block;
  font-size: var(--t-cap);
  text-decoration:none;
  border-bottom:1px solid currentColor;
  padding-bottom:2px;
  transition: opacity .25s cubic-bezier(.16,1,.3,1);
}
.link:hover{ opacity:.55; }

.link-big{
  display:inline-block;
  font-size: clamp(17px,1.9vw,26px);
  font-weight:500;
  letter-spacing:-.02em;
  text-decoration:none;
  border-bottom:1.5px solid currentColor;
  padding-bottom:3px;
  transition: opacity .25s cubic-bezier(.16,1,.3,1);
}
.link-big:hover{ opacity:.55; }

.linkrow{ display:flex; flex-wrap:wrap; gap: clamp(16px,2vw,34px); align-items:flex-end; }

.btn{
  display:inline-block;
  font-family: var(--ff);
  font-size: var(--t-label);
  font-weight:500;
  letter-spacing:.185em;
  text-transform:uppercase;
  padding: 15px 26px;
  border:1px solid var(--fg);
  background: var(--fg);
  color: var(--bg);
  border-radius:0;
  cursor:pointer;
  text-decoration:none;
  transition: background .25s cubic-bezier(.16,1,.3,1), color .25s cubic-bezier(.16,1,.3,1);
}
.btn:hover{ background: transparent; color: var(--fg); }

/* the second action and everything after it: the same button, held open,
   so the pair reads as one first choice and one alternative */
.btn.ghost{ background: transparent; color: var(--fg); }
.btn.ghost:hover{ background: var(--fg); color: var(--bg); }
.btn:focus-visible{ outline:2px solid var(--fg); outline-offset:3px; }

a:focus-visible, button:focus-visible, summary:focus-visible{
  outline:2px solid currentColor; outline-offset:3px;
}

/* ---------- hero ------------------------------------------ */

/* The hero is set to a printed sheet's proportions rather than the page's.
   Two things carry that: the display block runs the full measure, which
   assets/js/specimen.js sizes it to do, and the small type is set at roughly
   a twentieth of it rather than at the page's own small size — the ratio
   between the two is what the eye reads as scale, not either one alone. */
.hero-top{
  /* Every piece of small type in the hero is set from one token. The label
     roles and the caption roles differ everywhere else on the sheet; here the
     four blocks are read together against the display, so they take one size
     and one colour and the display is left as the only change in scale. */
  --t-label: var(--t-hero-sm);
  --t-cap:   var(--t-hero-sm);
  padding-top: clamp(84px,8.4vw,132px);
}

/* nothing in the hero is set in the muted ink: the display is the hierarchy */
.hero-top .label,
.hero-top .cap{ color: var(--fg); }

/* Three columns across the head: the name on the left margin, the H1, which
   carries the phrase a search engine reads, and the standing list beside it.
   All three start on the same line, so the row is one straight rule of small
   type from margin to margin above the display. */
/* .reveal sets its own children to block, and it is written further down the
   sheet, so the grid has to be reasserted from inside the hero. */
/* The row of small type is set off the display by the same distance the
   display is set off the intro line below it — ink to ink, not box to box,
   so the two openings in the sheet are one measure. assets/js/specimen.js
   measures both and writes the difference to --bar-lift. A transform, because
   the section is a full-viewport column set to space-between: a margin here
   would be taken out of the space between the head and the foot instead of
   moving the row.

   Offset rather than transform: the row is a child of .reveal, and the entry
   animation is set to hold its last frame, which pins transform at none for
   as long as the page is open. Relative position is the one way up that the
   animation does not own. */
.hero-head .hero-bar{
  display:grid;
  align-items:start;
  margin-bottom: clamp(26px,3.4vw,54px);
  position: relative;
  top: calc(var(--bar-lift, 0px) * -1);
}

/* The three blocks across the head, and the three across the foot, are placed
   by hand, and in each set two of the column ranges share a column. Auto
   placement reads that overlap as a collision and drops the second block onto
   a row of its own — which is what took the standing note off the name's line
   and the actions off the copy's. Naming the row holds each set on the one
   line it is written as. Wide screens only: below 900px every block runs the
   full measure and the sets are meant to stack. */
@media (min-width: 901px){
  .hero-head .hero-bar > *,
  .hero-foot .grid > *{ grid-row: 1; }
}
/* the column is the measure here, not a ch count: the H1 is set at the hero's
   own small size, which is larger than the page's, and a cap written for the
   page's size would break the line twice */
.hero-bar h1{ max-width: none; }

/* The standing note is set to the right margin rather than to the head of its
   column. Its column and the actions' column are the same one, and the second
   action is flush to the end of it, so the two edges land on the same pixel
   and the sheet closes on one straight right margin. */
.hero-bar .cap{ text-align: right; }

/* The name, the heading and the standing list at the head of the hero read
   as one register with the intro line set below the display: same family,
   same weight — the head of the sheet and its foot are set with one voice. */
.hero-bar .hero-name,
.hero-bar h1.label,
.hero-bar .cap{
  font-family: var(--ff);
  font-weight: 400;
}
/* The two standing lines are set in .cap and the name and heading in .label,
   and .label carries the tracking. Uppercase at small size needs that tracking
   to stay readable, so the caps take the same figure and the four blocks of
   the hero read as one register rather than two. */
.hero-bar .cap,
.hero-foot .cap{ letter-spacing: .185em; }

/* The name holds the first four columns of the top row. It takes its size and
   its colour from the row, so no rule of its own is needed beyond the column
   it sits in — the grid's own row-gap now carries the space under it. */
/* A box's left edge is not where its ink begins. The G below is set at display
   size and carries a side bearing to match; the A here is set in small type
   and carries almost none, so two blocks flush to the same margin read as one
   stepped in against the other. assets/js/specimen.js measures both letters at
   the size each is actually painted in and writes the difference to --name-x.
   A transform rather than a margin, so the column is not re-laid to carry it. */
.hero-name{
  color: var(--fg);
  transform: translateX(var(--name-x, 0px));
}

/* The actions moved down to the foot, where they hold the corner opposite
   the arrow and close the frame the display block sits inside. */
.hero-actions{
  display:flex;
  flex-wrap:wrap;
  justify-content:flex-end;
  align-items:flex-end;
  gap: clamp(8px,.8vw,14px);

  /* The row sets the actions to its foot, and that foot is the foot of the
     line box the copy beside them ends in — half a line's leading and a
     descender below the last of that copy's ink. assets/js/specimen.js
     measures both and writes the difference to --cta-lift, which closes the
     buttons' outlines on the same pixel the copy's last row of ink does. A
     transform, because a margin would grow the row and take the copy down
     with it. */
  transform: translateY(calc(var(--cta-lift, 0px) * -1));
}

/* ---------- the cycling row ------------------------------- */

/* Two display rows, one held and one cycling, set at the same size and
   leading so the pair reads as a single sentence. Both take that size from
   the block above them, which assets/js/specimen.js sets to whatever makes
   the longer of the two run from margin to margin. */
.hero-head{ font-size: var(--t-mega); }
.hero-lead, .hero-cycle{ display:block; font-size: 1em; }

/* The mark stays inside this row now, but the clip stays with it: it is the
   frame the sweep is cut against, and it holds a word inside the margin while
   the box under it is being resized. One axis only: clipping the other would
   take the overshoot off the top and foot of the display. */
.hero-cycle{
  --in: .42s;
  --out: .3s;
  --ease: cubic-bezier(.42,0,.2,1);
  position:relative;
  overflow: clip visible;
}

/* One box with every word stacked inside it. The first word is in flow, so
   the row keeps its height, its baseline and a sensible width with no
   JavaScript at all; the rest are laid over it. assets/js/specimen.js writes
   --cw, the width of whichever word is showing, and that is what carries the
   heart along with the word instead of leaving it parked in one place. The
   width is only ever changed while the box is empty, so it never animates. */
.cycle{
  position:relative;
  display:inline-block;
  vertical-align:baseline;
  width: var(--cw, auto);
}

/* A word is only ever a horizontal wipe, cut at the pixel the mark is on:
   right to left while it erases, left to right while it types. Nothing fades
   and nothing moves, so the change reads as the mark's doing rather than as
   an effect of its own. Waiting: cut back to the head of the line. */
.cycle-word{
  white-space:nowrap;
  clip-path: inset(-.3em 100% -.3em 0);
  transition: clip-path var(--in) var(--ease);
}
.cycle-word ~ .cycle-word{ position:absolute; left:0; top:0; }

/* showing */
.cycle-word.is-on{ clip-path: inset(-.3em 0 -.3em 0); }

/* leaving: cut back to the head of the line, so the ink empties from the
   right under the mark as it runs back over it. Same geometry as waiting; the
   class is here to carry the shorter beat an erase is set in. */
.cycle-word.is-out{
  clip-path: inset(-.3em 100% -.3em 0);
  transition: clip-path var(--out) var(--ease);
}

/* The heart is a mark, not a glyph, and it is set against the cycling word
   rather than against the line. assets/js/specimen.js measures where the ink
   of a cap actually starts and stops and writes --cap-h and --cap-d, so the
   heart tops out and bottoms out on the same pixels the T does. The numbers
   below are the fallback, and they are close, not exact.

   --hx is the sweep, in pixels, from the mark's rest at the tail of the
   word. An erase sets it to minus the width of the box, which lands the mark
   at the head of the line; typing returns it to zero. The word's clip is cut
   to the same fraction over the same beat, so the ink's edge and the mark
   travel as one thing. */
.hero-cycle .heart{
  position:relative;
  top: var(--cap-d, 0em);
  height: var(--cap-h, .715em);
  margin: 0 0 0 .16em;
  transform: translateX(var(--hx, 0px));
  transition: transform var(--in) var(--ease);
  /* the mark is a small vector on a row of very large type: promoting it keeps
     its sweep off the same paint as the ink it is cutting */
  will-change: transform;
}

/* the sweep that takes a word away is the shorter of the two */
.hero-cycle.is-erasing .heart{ transition: transform var(--out) var(--ease); }

.hero-foot{ margin-top: clamp(36px,5vw,72px); }
.hero-foot .grid{ row-gap: clamp(16px,2vw,28px); align-items:end; }

/* The arrow is cut to its own ink: the path runs from x=0 to the miter at the
   point, x=85.967, and the viewBox stops there, so whatever width the element
   is given is the width the eye sees. assets/js/specimen.js measures the G in
   the display above and writes that width to --arrow-w, which lands the point
   on the back of that letter. The clamp is the answer before script runs. */
/* Its head starts where the display's ink starts, not where the display's box
   does: --arrow-x is the F's left side bearing, measured at the size the row
   below the arrow is actually painted in, and the width is taken back by the
   same amount so the point stays on the back of the letter above it. */
.arrow{
  display:block;
  width: calc(var(--arrow-w, clamp(48px,5.4vw,88px)) - var(--arrow-x, 0px));
  transform: translateX(var(--arrow-x, 0px));
  max-width:none;
  height:auto;
  stroke: currentColor;
  stroke-width:1.25;
  fill:none;
}

/* ---------- capability cloud ------------------------------ */

/* one size throughout — the interest is in where each phrase sits,
   so every placement below is chosen, not generated */
.cloud{
  display:grid;
  grid-template-columns: repeat(12, minmax(0,1fr));
  column-gap: var(--gut);
  row-gap: clamp(10px,1.3vw,20px);
  margin-top: clamp(30px,4vw,58px);
}
.cloud span{
  font-size: clamp(12px, 1.03vw, 16px);
  font-weight:500;
  letter-spacing:.055em;
  text-transform:uppercase;
  line-height:1;
  white-space:nowrap;
}
.cloud span:nth-child(1) { grid-area: 1 / 1 / 2 / 5;   }
.cloud span:nth-child(2) { grid-area: 1 / 8 / 2 / 13;  }
.cloud span:nth-child(3) { grid-area: 2 / 5 / 3 / 11;  }
.cloud span:nth-child(4) { grid-area: 3 / 2 / 4 / 8;   }
.cloud span:nth-child(5) { grid-area: 4 / 1 / 5 / 5;   }
.cloud span:nth-child(6) { grid-area: 4 / 6 / 5 / 10;  }
.cloud span:nth-child(7) { grid-area: 5 / 1 / 6 / 6;   }
.cloud span:nth-child(8) { grid-area: 5 / 8 / 6 / 13;  }
.cloud span:nth-child(9) { grid-area: 6 / 4 / 7 / 11;  }
.cloud span:nth-child(10){ grid-area: 7 / 2 / 8 / 9;   }
.cloud span:nth-child(11){ grid-area: 7 / 9 / 8 / 13;  }
.cloud span:nth-child(12){ grid-area: 8 / 5 / 9 / 10;  }

/* ---------- track record -----------------------------------
   A plate cut into panels. No rule closes the band at its head or its foot.
   The uprights hold it on their own, and the space between the figures is a
   room each figure stands in rather than a gap between six items on a shelf.

   Two rules make the panel. An upright hairline opens it, and a rake runs
   from that hairline's foot to the top of the next one. The rake is the only
   ornament on the spread and it is doing work: it splits the panel into a
   high left corner and a low right one, and the two marks are set into those
   corners — the note small and muted at the top, the figure large and pushed
   to the far edge underneath. Nothing is centred, nothing is boxed, and the
   white the rake cuts loose is what the eye reads first.

   The rake is drawn, not skewed. A rotated rule has to know the panel's
   proportions to land on both corners, and the panel changes shape with every
   width the sheet is read at; the drawn box stretches while the stroke stays
   a hair. A slow sweep lights the two rules that make each room in turn, so
   what moves is the room and not a box around the mark.

   Panels are equal. The set is six marks of one kind and an uneven rail would
   claim a rank the figures do not have; a long value takes a second line
   inside its own panel instead of taking width from its neighbours.
   ----------------------------------------------------------- */

.spread.track{
  padding-top: clamp(30px, 3.8vw, 66px);
  padding-bottom: clamp(30px, 3.8vw, 66px);
}

.track-head{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap: var(--gut);
  padding-bottom: clamp(12px, 1.3vw, 20px);
}

.track-band{
  --band-t: clamp(14px, 1.6vw, 26px);
  --band-b: clamp(13px, 1.4vw, 23px);
  display:flex;
  align-items:stretch;
  padding: var(--band-t) 0 var(--band-b);
}

.fig{
  position:relative;
  flex: 1 1 0;
  min-width:0;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  min-height: clamp(158px, 16.8vw, 244px);
  padding-left: clamp(16px, 1.9vw, 34px);
  padding-right: clamp(10px, 1.25vw, 22px);
}
.fig:first-child{ padding-left:0; }

/* the upright. it runs past the head and the foot of the band so the band
   reads as one plate cut through rather than as a row of items with air
   between them */
.fig::before{
  content:"";
  position:absolute;
  left:0;
  top: calc(var(--band-t) * -1);
  bottom: calc(var(--band-b) * -1);
  width:1px;
  background: var(--hair);
}
.fig:first-child::before{ display:none; }

/* the rake, corner to corner across the whole panel: its foot meets this
   panel's upright and its head meets the next one */
.rake{
  position:absolute;
  left:0;
  top: calc(var(--band-t) * -1);
  display:block;
  width:100%;
  height: calc(100% + var(--band-t) + var(--band-b));
  pointer-events:none;
}
.rake line{
  stroke: var(--hair);
  stroke-width:1;
  vector-effect: non-scaling-stroke;
}

/* The sweep. The band lights itself in a pass from the first panel to the
   last, then rests before the next pass. Each room lights its rake, its own
   upright and the next panel's; an upright belongs to two rooms, so it holds
   across both. The rise and fall are long and overlap the neighbours', so the
   light travels along the band as one wave instead of stepping room to room.
   The band answers nothing: there is no hover state. */
.track-band{
  --sweep-step: .5s;
  --sweep: 6s;
}
.fig:nth-child(1){ --i:0; }
.fig:nth-child(2){ --i:1; }
.fig:nth-child(3){ --i:2; }
.fig:nth-child(4){ --i:3; }
.fig:nth-child(5){ --i:4; }
.fig:nth-child(6){ --i:5; }
.fig:nth-child(7){ --i:6; }
.fig:nth-child(8){ --i:7; }

@keyframes rake-sweep{
  0%          { stroke: var(--hair); }
  10%, 13%    { stroke: var(--fg); }
  28%, 100%   { stroke: var(--hair); }
}
@keyframes upright-sweep{
  0%          { background-color: var(--hair); }
  10%, 21%    { background-color: var(--fg); }
  36%, 100%   { background-color: var(--hair); }
}

@media (prefers-reduced-motion: no-preference){
  .rake line{
    animation: rake-sweep var(--sweep) cubic-bezier(.65,0,.35,1) infinite;
    animation-delay: calc(var(--i, 0) * var(--sweep-step));
  }
  .fig::before{
    animation: upright-sweep var(--sweep) cubic-bezier(.65,0,.35,1) infinite;
    animation-delay: calc((var(--i, 0) - 1) * var(--sweep-step));
  }
}

/* both marks are positioned so they sit over the drawn rake rather than
   under it */
.fig span{
  position:relative;
  display:block;
  max-width: 16ch;
  font-size: var(--t-label);
  letter-spacing:.185em;
  line-height:1.55;
  text-transform:uppercase;
  color: var(--mute);
}

.fig b{
  position:relative;
  display:block;
  align-self:flex-end;
  max-width:100%;
  margin-top: clamp(22px, 3.2vw, 52px);
  text-align:right;
  font-weight:400;
  letter-spacing:-.03em;
  line-height:.9;
  font-variant-numeric: tabular-nums;
}
.fig.lg b{ font-size: clamp(20px, 2.1vw, 35px); }
.fig.xl b{ font-size: clamp(22px, 2.4vw, 42px); }

/* A panel needs width before it needs height: below the wide sheet the rail
   folds to three abreast and then to two, and the panel loses depth at each
   fold so the band never becomes a column of tall boxes. The uprights and
   the rakes stop at the row they belong to once there is more than one row,
   and the rows are set apart by space rather than by a rule. */
@media (max-width: 1080px){
  .track-band{ flex-wrap:wrap; }
  .fig{
    flex: 0 0 33.3333%;
    min-height: clamp(140px, 19vw, 196px);
    padding-left: clamp(14px, 2.4vw, 28px);
  }
  .fig::before{ top:0; bottom:0; }
  .rake{ top:0; height:100%; }
  .fig:nth-child(3n+1){ padding-left:0; }
  .fig:nth-child(3n+1)::before{ display:none; }
  .fig:nth-child(n+4){
    margin-top: var(--band-t);
    padding-top: var(--band-t);
  }
}

@media (max-width: 620px){
  .fig{
    flex: 0 0 50%;
    min-height: clamp(126px, 31vw, 160px);
  }
  .fig:nth-child(3n+1){ padding-left: clamp(14px, 2.4vw, 28px); }
  .fig:nth-child(3n+1)::before{ display:block; }
  .fig:nth-child(odd){ padding-left:0; }
  .fig:nth-child(odd)::before{ display:none; }
  .fig:nth-child(n+4){ margin-top:0; padding-top:0; }
  .fig:nth-child(n+3){
    margin-top: var(--band-t);
    padding-top: var(--band-t);
  }
  .fig span{ max-width: 20ch; }
}

/* ---------- the services field -----------------------------
   Six columns of small type standing shoulder to shoulder, read as one
   block before any single word of it is read.

   The register is the point. This is the only place on the sheet set in a
   fixed-width face, and the reason is mechanical rather than decorative: a
   fixed width is what makes six narrow columns line up into a single grey
   field instead of six ragged paragraphs. A proportional face cannot hold
   that texture at this size.

   The note decays after it ends — see ak_svc_decay(). The decayed run is
   generated, hidden from assistive technology, and carries no meaning. It
   is mass, and mass is what makes the field a field.

   The sixth column is set in the one other colour on the sheet. Six even
   columns have no end and no beginning; the colour gives the eye somewhere
   to stop, and it is the only ornament here that could not be removed
   without the spread turning back into a wall.
   ------------------------------------------------------------ */

.ledger-head{ align-items: baseline; row-gap: clamp(22px,3vw,40px); }
.ledger-lede{ margin-top: clamp(15px,1.7vw,24px); max-width:34ch; }

/* The head's third register: tracked pairs, both halves at label size, the
   term muted and the value solid. It is the only place on the sheet where
   two columns of small caps stand together, and it sets the plate up as
   something catalogued rather than something advertised. */
.plate-spec{
  display:grid;
  grid-template-columns: max-content minmax(0,1fr);
  column-gap: clamp(14px,1.6vw,26px);
  row-gap: clamp(9px,1vw,14px);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing:.185em;
  text-transform:uppercase;
  line-height:1.45;
}
.plate-spec dt{ color: var(--mute); }
.plate-spec dd{ color: var(--fg); }

.svc-field{
  list-style:none;
  margin-top: clamp(44px,6.4vw,120px);
  padding-top: clamp(16px,1.8vw,28px);
  border-top: 1px solid var(--hair);
  display:grid;
  grid-template-columns: repeat(6, minmax(0,1fr));
  column-gap: var(--gut);
  row-gap: clamp(28px,3.4vw,52px);

  font-family: ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  --svc-fs: clamp(9.5px, .74vw, 12px);
  font-size: var(--svc-fs);
  line-height: 1.45;
  font-variant-ligatures: none;
}

.svc-col{
  --svc-fg: var(--fg);
  color: var(--svc-fg);

  /* the column is the measure the name is cut to — see .svc-name */
  container-type: inline-size;
}

/* the last of the six, in the sheet's only other colour */
.svc-field > li:nth-child(6){ --svc-fg: var(--flare); }

/* ---------- the name ---------------------------------------
   The name is set to its own column rather than to a size. A monospaced face
   gives every character the same advance, so the width of a name is nothing
   but its length: the renderer carries the count in as --chars, and the size
   that fills the measure is the measure divided by it. The name is therefore
   as large as one line of that column will hold, and the six names come out
   at six sizes — short ones large, long ones small — each one running very
   nearly the full width of the column it heads.

   0.66em is the advance of one character plus its letter-spacing, taken at
   the widest of the monospaced faces this stack can land on, so the line
   lands inside the measure on every machine rather than only on this one.

   The cap stops a two-word name from outgrowing the plate on a narrow screen,
   and it is what keeps the space under the name positive: the name's box and
   that space always add up to 2.9 lines of the copy's own leading, so all six
   columns start their copy on the same line however large their names are. */
.svc-name{
  --svc-name-fs: min(calc(var(--svc-fs) * 2.4), calc(100cqw / (var(--chars-max, var(--chars, 24)) * 0.66)));

  font-size: var(--svc-name-fs);
  font-weight: 700;
  line-height: 1;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  margin-bottom: calc(var(--svc-fs) * 2.9 - var(--svc-name-fs));
}

/* A browser with no container units gets the field's own size back, which is
   where the names stood before, rather than an unresolved length. */
@supports not (width: 1cqw){
  .svc-name{
    font-size: inherit;
    line-height: 1.45;
    white-space: normal;
    margin-bottom: 1.45em;
  }
}

.svc-copy{
  text-align: justify;
  text-align-last: left;
  hyphens: none;
  overflow-wrap: anywhere;
  word-break: normal;

  /* how far down the column the two cuts run — see .svc-cut */
  --cut-h: 65em;
}

/* ---------- the cut ----------------------------------------
   Six rectangles is what a browser gives you for free and it is the one thing
   on this sheet that looks typed rather than printed. Each column is flowed
   between two contours instead, one down each side, so the ends of the lines
   describe a torn strip. There is nothing to see here: both cutters are empty
   floats with no ink of their own, and the shape is carried in on the element
   as --cut-l and --cut-r by inc/blocks-specimen.php, seeded on the service
   name so a column is cut the same way on every load.

   The cutter is a little over a third of the measure, which is the whole safety margin: a
   line can lose at most a third to each side, and the generator holds the two
   sides together well under that. Without the cap the justification opens
   into rivers and the field stops reading as type.

   --cut-h has to be told how far the copy runs, because a float cannot be
   given a height in percent. It is set a little short of the real depth on
   purpose: overshooting would leave dead air under every column, where
   undershooting only lets the last few lines run straight. */
.svc-cut{
  display: block;
  width: 36%;
  height: var(--cut-h);
  pointer-events: none;
}

.svc-cut-l{
  float: left;
  shape-outside: var(--cut-l, polygon(0 0, 0 0, 0 100%));
}

.svc-cut-r{
  float: right;
  shape-outside: var(--cut-r, polygon(100% 0, 100% 0, 100% 100%));
}

/* The decayed run sits a shade back from the note it grew out of, which is
   the only thing telling the eye where the sense stops. Any further apart
   and it reads as two paragraphs. */
.svc-decay{ opacity: .72; }

@media (hover:hover) and (pointer:fine){
  .svc-col{ transition: opacity .5s cubic-bezier(.16,1,.3,1); }
  .svc-field:hover > li{ opacity: .42; }
  .svc-field:hover > li:hover{ opacity: 1; }
  .svc-decay{ transition: opacity .5s cubic-bezier(.16,1,.3,1); }
  .svc-col:hover .svc-decay{ opacity: .95; }
}

/* The columns come up left to right as the field enters, driven by the
   scroll position rather than by a script. A browser without view timelines
   gets the field already standing. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .svc-col{
      animation: rowSettle linear both;
      animation-timeline: view();
      animation-range: entry 4% cover calc(14% + var(--col, 0) * 3%);
    }
  }
}

/* Six columns need a page to stand on. Below that the field folds to three
   and then to two, and it never folds to one: a single column of decaying
   text is a mistake on a page, where six of them are a design. */
/* Below this the type has hit its floor and stops shrinking with the page, so
   the columns take more lines to say the same thing and the cut has further
   to run. */
@media (max-width: 1280px){
  .svc-copy{ --cut-h: 75em; }
}

@media (max-width: 1080px){
  .svc-field{ grid-template-columns: repeat(3, minmax(0,1fr)); }

  /* three columns are twice the measure, so the same copy sets in half the
     depth */
  .svc-copy{ --cut-h: 39em; }
}

@media (max-width: 620px){
  .svc-field{ grid-template-columns: repeat(2, minmax(0,1fr)); font-size: 9.5px; }

  /* the decay is cut to a depth rather than run to its end, so the section
     does not become the longest thing on a phone */
  .svc-copy{
    /* the copy is cut off here, so the contour has nowhere further to go */
    --cut-h: 26em;
    max-height: 26em;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
  }
}

/* ---------- the reasons field ------------------------------
   Not a second ledger. The list above is ruled and even, so this one drops
   the rules entirely and sets the statements in two columns that never line
   up: every entry in the right-hand column hangs lower than the one beside
   it. A short stroke stands over each name where the numeral used to sit. */

.reasons{
  list-style:none;
  margin-top: clamp(40px,5.2vw,88px);
  display:grid;
  grid-template-columns: repeat(2, minmax(0,1fr));
  column-gap: clamp(30px,5vw,112px);
  row-gap: clamp(36px,4.6vw,80px);
}
.reasons > li:nth-child(even){ margin-top: clamp(26px,4.2vw,88px); }

.reason-name{
  font-size: clamp(19px,1.9vw,27px);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -.018em;
  max-width: 22ch;
}
.reason-name::before{
  content:"";
  display:block;
  width: 34px;
  height:1px;
  background: var(--fg);
  opacity:.5;
  margin-bottom: clamp(15px,1.7vw,24px);
}
.reason-note{
  margin-top: clamp(11px,1.2vw,18px);
  font-size: var(--t-cap);
  line-height: 1.52;
  color: var(--mute);
  max-width: 34ch;
}

.reasons-head{ align-items: baseline; }
.reasons-meta .cap{ margin-top: 12px; }

/* ---------- simple rule lists ----------------------------- */

.rulelist{ list-style:none; margin-top: 14px; }
.rulelist li{
  border-top:1px solid var(--hair);
  padding: 11px 0;
  line-height:1.36;
  max-width: 42ch;
}
.rulelist li:last-child{ border-bottom:1px solid var(--hair); }

.plainlist{ list-style:none; }
.plainlist li{ padding: 5px 0; line-height:1.36; }

.struck{ list-style:none; }
.struck li{
  padding: 9px 0;
  border-top:1px solid var(--hair);
  text-decoration: line-through;
  text-decoration-thickness:1px;
  color: var(--mute);
}
.struck li:last-child{ border-bottom:1px solid var(--hair); }

/* ---------- weight ladder --------------------------------- */

.ladder{ list-style:none; margin-top: clamp(22px,2.6vw,40px); }
.ladder > li{ border-top:1px solid var(--hair); padding: clamp(16px,2vw,28px) 0; }
.ladder > li:last-child{ border-bottom:1px solid var(--hair); }
.bar{ height: clamp(14px,1.6vw,24px); background: var(--fg); }
.ladder .grid{ align-items:center; row-gap:12px; }

/* ---------- schedule table -------------------------------- */

.sched{ width:100%; border-collapse:collapse; margin-top: 14px; }
.sched th, .sched td{
  text-align:left;
  border-top:1px solid var(--hair);
  padding: 12px 0;
  vertical-align: baseline;
  line-height:1.35;
}
.sched tr:last-child td{ border-bottom:1px solid var(--hair); }
.sched th{
  font-size: var(--t-label);
  font-weight:500;
  letter-spacing:.185em;
  text-transform:uppercase;
  color: var(--mute);
  border-top:0;
  padding-bottom:10px;
}
.sched td:last-child{
  text-align:right;
  font-size: var(--t-label);
  letter-spacing:.185em;
  text-transform:uppercase;
  color: var(--mute);
  white-space:nowrap;
  padding-left: 18px;
}
.tablewrap{ overflow-x:auto; }

/* ---------- centred "masthead" mode ----------------------- */

.masthead{ text-align:center; }
.masthead .kicker{
  font-size: var(--t-label);
  letter-spacing:.42em;
  text-transform:uppercase;
  color: var(--mute);
}
.masthead h2{
  font-size: clamp(32px,6.6vw,104px);
  font-weight:300;
  letter-spacing:.02em;
  line-height:1.04;
  text-transform:uppercase;
  margin: clamp(24px,3.4vw,52px) 0;
}
.masthead .spokes{
  display:flex; justify-content:space-between; flex-wrap:wrap; gap:14px;
  font-size: var(--t-label); letter-spacing:.24em; text-transform:uppercase; color: var(--mute);
}

/* space-between equalises the gaps, not the words, so the middle spoke drifts
   toward whichever outer word is narrower. Handing the two outer spokes an
   equal share of the free space instead puts the middle one on the centre
   line of the spread, whatever the outer words happen to be. */
.masthead .spokes > span:first-child,
.masthead .spokes > span:last-child{ flex:1 1 0; }
.masthead .spokes > span:first-child{ text-align:left; }
.masthead .spokes > span:last-child{ text-align:right; }

/* tracking hangs a .24em space off the last letter of every spoke, which
   would sit the glyphs a hair left of their own box. Pulling that space back
   out of the layout makes optical centre and geometric centre the same. */
.masthead .spokes > span{ margin-right:-.24em; }

/* ---------- braced paragraph ------------------------------- */

/* the braces are set in the bold cut against light text, so they read as a
   held mark rather than as another line of copy */
.masthead .braced{
  position:relative;
  width:fit-content;
  max-width:100%;
  margin: clamp(28px,3.4vw,50px) auto 0;
  padding-inline: clamp(34px,4.4vw,72px);
}
.masthead .braced p{ max-width:52ch; }
.masthead .braced::before,
.masthead .braced::after{
  position:absolute;
  top:50%;
  translate:0 -50%;
  font-weight:500;
  font-size: clamp(62px,7.6vw,124px);
  line-height:.78;
  /* full strength, and taken from the spread's own text colour so the mark
     stays ink on the paper ground and does not vanish if the ground flips */
  color: currentColor;
  pointer-events:none;
}
.masthead .braced::before{ content:"{"; left:0; }
.masthead .braced::after{ content:"}"; right:0; }
.masthead .keeps{ margin-top: clamp(34px,4.6vw,72px); display:grid; gap: clamp(18px,2.4vw,34px); }
.masthead .keeps h3{ font-size: var(--t-h3); font-weight:500; letter-spacing:-.01em; }
.masthead .keeps p{ font-size: var(--t-cap); color: var(--mute); max-width: 46ch; margin: 6px auto 0; line-height:1.42; }

/* ---------- vertical edge type ---------------------------- */

.edgetype{
  position:absolute;
  left: var(--mar);
  top: 50%;
  transform: translateY(-50%) rotate(180deg);
  writing-mode: vertical-rl;
  white-space: nowrap;
  font-size: clamp(24px,3.4vw,56px);
  font-weight:400;
  letter-spacing:.16em;
  text-transform:uppercase;
  color: transparent;
  -webkit-text-stroke: 1px var(--fg);
  pointer-events:none;
  user-select:none;
}

/* ---------- the heart ------------------------------------- */

/* The display face carries no heart, so it is drawn to the same rules that
   face is: flat terminals, 45-degree chamfers, not a single curve. It sits
   on the text baseline and takes its size from whatever type it is set in. */
.heart{
  display:inline-block;
  height:.74em;
  width:auto;
  fill: currentColor;
  vertical-align: baseline;
}

/* ---------- figure spread --------------------------------- */

/* Set to the full measure by assets/js/specimen.js, which is the only way to
   land on both margins when the number itself is editable. The clamp is the
   no-JavaScript fallback and stays under the measure on purpose. */
.figure{
  display:block;
  margin-top: clamp(40px,5.4vw,90px);
  font-size: clamp(56px, 28vw, 430px);
  font-weight:700;
  line-height:.78;
  letter-spacing:-.05em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.figure-note{
  margin-top: clamp(12px,1.4vw,22px);
  font-size: var(--t-label);
  letter-spacing:.185em;
  text-transform:uppercase;
  color: var(--mute);
  max-width: 74ch;
}

/* the two columns sit at different heights, the way they would on a
   printed spread — the drop is the composition, not a mistake */
.offset-down{ margin-top: clamp(22px,4.6vw,92px); }

/* ---------- the SEO spread -------------------------------- */

.lowline{
  font-size: clamp(19px,2.5vw,38px);
  font-weight:400;
  font-style: italic;
  letter-spacing:-.015em;
  line-height:1.1;
}
.lowline sup{ font-size:.5em; vertical-align: super; line-height:0; }

/* On the SEO spread the headline is not a line set over the word. The
   letters are held short of the height the column offers, and the headline
   takes the air that leaves above them at the left of the row: its first
   letter tops out on the column's first line, and its first inked pixel
   stands on the S's first inked pixel. Neither is a box edge, so both are
   cut in assets/js/specimen.js; the corner below is the fallback, which is
   the correct corner and the wrong two distances. It is taken out of the flow so
   the row is measured as though it were not there. */
.seo-row > .lowline{
  position: absolute;
  top: var(--title-top, 0px);
  left: var(--title-left, 0px);
  margin: 0;
  max-width: 46%;
  text-align: left;
}

/* The spread is one word, broken by a column of text: the letters stand on a
   shared baseline at the foot of the row, the column runs the full height of
   it. The column is the tallest thing here, so it sets the height and the
   letters hang from its bottom edge — a tall, slender measure against two
   very large glyphs.

   The word runs S — E — column — O, so the copy stands before the last
   letter and the first two letters stand together. The column is spaced
   like a letter rather than like a column, and so is the E, so all three
   distances are margins cut by assets/js/specimen.js and not flex gaps: no
   two of them are the same number, because the letters facing each one
   carry different side bearings. The clamps are the no-JavaScript fallback.

   The letters are set a little under the size the row would carry, which is
   what opens the air the headline stands in. The row is then set in the
   middle of the page, so that slack falls away evenly at either end and the
   whole spread stands centred rather than hung on the left margin. The
   headline is cut to the S, which is the left edge of it. */
.seo-row{
  position: relative;
  display:flex;
  align-items: flex-end;
  justify-content: center;
  gap: 0;
  margin-top: clamp(30px,3.6vw,60px);
}

/* The copy stands between the E and the O, and the order is set here rather
   than in the template: below 900px the row is not a flex container, order
   is ignored, and the copy falls back to the order it is written in — which
   is the order that reads when the word is stacked. */
.seo-g1{ order:1; }
.seo-g2{ order:2; }
.seo-col{ order:3; }
.seo-g3{ order:4; }

/* assets/js/specimen.js sizes all three letters to one shared value: just
   under whatever would fill the row once the column has taken its measure,
   SEOCUT there being the fraction. The clamp below is the no-JavaScript
   fallback and stays deliberately short of the edges. */
.seo-glyph{
  flex: none;
  font-size: clamp(61px, 21vw, 416px);
  font-weight:700;
  line-height:.74;
  letter-spacing:-.055em;
  white-space: nowrap;
}

/* The S and the E are two boxes, so the air between them is a margin like
   the two the column carries, and cut to the same distance ink to ink. */
.seo-g2{ margin-left: var(--seo-se, clamp(12px, 2.2vw, 48px)); }

/* A narrow measure, justified, set tight. Roughly thirty characters a line,
   which is what makes the column read as a column rather than a paragraph
   that happens to be squeezed. */
.seo-col{
  flex: none;
  width: clamp(200px, 17.5vw, 310px);
  align-self: stretch;
  margin-left: var(--seo-ml, clamp(12px, 2.2vw, 48px));
  margin-right: var(--seo-mr, clamp(12px, 2.2vw, 48px));

  /* The column's last line has to print on the letters' baseline, and a box
     that ends on the same pixel does not: the leading below a 13px line and
     the leading below a 500px one are different distances. The offset is
     measured in the script and taken here, on the copy, because the letters
     are the fixed thing on this spread. It is a relative offset, so nothing
     in the row is re-laid out by it. */
  position: relative;
  top: var(--seo-drop, 0px);

  font-size: clamp(12.5px, .92vw, 15px);
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
}
.seo-col p{ line-height:1.34; max-width:none; font-style:italic; }
.seo-col .ak-p-next{ margin-top:.75em; }

/* The first letter cycles. The box is sized by a hidden S, so the word at
   rest sits exactly where it would if nothing ever moved; the other three
   glyphs are stacked on top of that box and switched by opacity.
   Three states, .8s each, in an even rotation — nothing is held longer than
   anything else, so the word never appears to settle and then jump. */
.morph{ position:relative; display:inline-block; }
.morph i{ visibility:hidden; font-style:normal; }
.morph b{
  position:absolute;
  left:0; top:0;
  font-weight:inherit;
  opacity:0;
  animation-duration: 1.8s;
  animation-timing-function: steps(1, end);
  animation-iteration-count: infinite;
}
.morph b.g-s{ animation-name: gS; opacity:1; }
.morph b.g-g{ animation-name: gG; }
.morph b.g-a{ animation-name: gA; }
.morph b.g-x{ animation-name: gX; }

/* The blank is the fourth state: a solid block standing in for the letter.
   It only works if it is exactly the letter's size, so assets/js/specimen.js
   measures the S itself — advance width and cap height — and writes the two
   values here. The em values are the fallback if that never runs. */
.morph b.g-x span{
  display:inline-block;
  width: var(--blk-w, .615em);
  height: var(--blk-h, .715em);
  background: currentColor;
  vertical-align: baseline;
}

@keyframes gS{ 0%{opacity:1} 33.333%{opacity:0} 100%{opacity:0} }
@keyframes gG{ 0%{opacity:0} 33.333%{opacity:1} 66.667%{opacity:0} 100%{opacity:0} }
@keyframes gA{ 0%{opacity:0} 66.667%{opacity:1} 100%{opacity:0} }
@keyframes gX{ 0%{opacity:0} 75%{opacity:1} 100%{opacity:1} }

/* On the SEO spread the footnote is the headline's other half — it spells out
   the three letters — so the two start on the same vertical rather than one at
   the word and one at the page margin. The row is centred, so where that
   vertical falls is a measurement and not a number: assets/js/specimen.js
   stands the asterisk's first inked pixel on the S's and writes the distance
   as a left margin here. The page margin is the fallback if that never runs. */
.footnote{
  margin-top: clamp(26px,3.4vw,52px);
  font-size: var(--t-cap);
  color: var(--mute);
  max-width: 58ch;
}

/* paragraphs emitted by ak_paragraphs() */
.ak-p-next{ margin-top:.9em; }

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

.form{ display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: clamp(20px,2.4vw,34px) var(--gut); }
.field{ display:flex; flex-direction:column; gap:8px; }
.field.wide{ grid-column: 1 / -1; }
.field label{
  font-size: var(--t-label);
  letter-spacing:.185em;
  text-transform:uppercase;
  color: var(--mute);
}
.field input, .field select, .field textarea{
  font-family: var(--ff);
  font-size: var(--t-body);
  color: var(--fg);
  background: transparent;
  border:0;
  border-bottom:1px solid var(--hair);
  border-radius:0;
  padding: 9px 0;
  transition: border-color .25s cubic-bezier(.16,1,.3,1);
  width:100%;
}
.field select{ appearance:none; cursor:pointer; }
.field textarea{ resize:vertical; min-height: 96px; line-height:1.45; }
.field input:focus, .field select:focus, .field textarea:focus{
  outline:none; border-bottom-color: var(--fg);
}
.field input::placeholder, .field textarea::placeholder{ color: var(--mute); }
.hp{ position:absolute; left:-9999px; }
.form-foot{ grid-column:1/-1; display:flex; flex-wrap:wrap; gap: 22px; align-items:center; justify-content:space-between; }

/* the handler answers with a redirect, so the reply is a block above the form */
.notice{
  border:1px solid var(--fg);
  padding: clamp(16px,2vw,24px);
  margin-bottom: clamp(22px,2.6vw,36px);
  max-width: 52ch;
}
.notice strong{
  display:block;
  font-size: var(--t-h3);
  font-weight:700;
  letter-spacing:-.018em;
  line-height:1.12;
}
.notice p{ margin-top:8px; color: var(--mute); line-height:1.45; }

/* ---------- faq ------------------------------------------- */

.faq{ margin-top: clamp(22px,2.8vw,44px); }
.faq details{ border-top:1px solid var(--hair); }
.faq details:last-of-type{ border-bottom:1px solid var(--hair); }
.faq summary{
  list-style:none;
  cursor:pointer;
  padding: clamp(16px,1.9vw,26px) 0;
  display:flex;
  justify-content:space-between;
  gap: 24px;
  align-items:baseline;
  font-size: var(--t-h3);
  font-weight:500;
  letter-spacing:-.015em;
  line-height:1.2;
}
.faq summary::-webkit-details-marker{ display:none; }
.faq summary::after{
  content:"+";
  font-weight:400;
  font-size: var(--t-h3);
  color: var(--mute);
  flex:none;
}
.faq details[open] summary::after{ content:"–"; }
.faq .answer{ padding: 0 0 clamp(20px,2.2vw,32px); max-width: 62ch; line-height:1.5; color: var(--mute); }

/* ---------- footer ---------------------------------------- */

.foot{ background: var(--ink); color: var(--paper); padding: clamp(40px,5vw,72px) var(--mar) clamp(28px,3vw,44px); }
.foot .hair{ border-top:1px solid rgba(247,246,242,.24); padding-top: clamp(22px,2.6vw,36px); }
.foot .grid{ row-gap: 26px; }
.foot .label{ color: rgba(247,246,242,.58); }
.foot a{ text-decoration:none; border-bottom:1px solid rgba(247,246,242,.4); padding-bottom:1px; }
.foot a:hover{ border-bottom-color: rgba(247,246,242,1); }
.foot .colophon{ margin-top: clamp(36px,4.6vw,66px); font-size: var(--t-label); letter-spacing:.185em; text-transform:uppercase; color: rgba(247,246,242,.42); display:flex; flex-wrap:wrap; gap:8px 26px; justify-content:space-between; }

/* ---------- motion ---------------------------------------- */

@keyframes riseIn{
  from{ opacity:0; transform: translateY(.14em); clip-path: inset(0 0 102% 0); }
  to  { opacity:1; transform: translateY(0);     clip-path: inset(-.2em 0 -.2em 0); }
}
.reveal > *{
  display: block;            /* clip-path mis-clips an inline box that wraps */
  animation: riseIn .7s cubic-bezier(.16,1,.3,1) both;
}
.reveal > *:nth-child(1){ animation-delay:.02s }
.reveal > *:nth-child(2){ animation-delay:.10s }
.reveal > *:nth-child(3){ animation-delay:.18s }
.reveal > *:nth-child(4){ animation-delay:.26s }

/* The reasons settle as they come up the page. This is
   driven by the scroll position itself rather than by a script, so there is
   no observer to run and nothing to fail: a browser without view timelines
   simply gets the rows already in place. Same curve as riseIn above — the
   page keeps one easing. */
@keyframes rowSettle{
  from{ opacity:0; transform: translateY(15px); }
  to  { opacity:1; transform: none; }
}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .reasons > li{
      animation: rowSettle linear both;
      animation-timeline: view();
      animation-range: entry 6% cover 24%;
    }
  }
}

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  .reveal > *{ animation:none; opacity:1; transform:none; clip-path:none; }
  *{ transition-duration:.01ms !important; }

  /* the hero row settles on the first word and stays there, and the heart
     stays where that word leaves it */
  .cycle-word{ transition:none; }
  .hero-cycle .heart{ transition:none; }

  /* the word settles on S and stays there */
  .morph b{ animation:none; }
  .morph b.g-s{ opacity:1; }
  .morph b.g-g, .morph b.g-a, .morph b.g-x{ opacity:0; }
}

/* ---------- responsive ------------------------------------ */

@media (max-width: 900px){
  /* the display scale is set by the widest line, which no longer fits
     the narrow measure — one size governs all three display roles here */
  :root{ --t-mega: clamp(26px, 10.4vw, 92px); }
  .bleed .mega, .mega.four, .mega.fit{ font-size: var(--t-mega); }

  .grid > *{ grid-column: 1 / -1 !important; }

  /* the measure is already narrow here, so every ch-based cap is dead
     weight — and an inline one would otherwise force a wider box */
  .spread p, .spread li, .spread h1, .spread h2, .spread h3,
  .prose p, .masthead p{ max-width: 100% !important; }

  /* the scatter needs width to read as a scatter; below that it is a list */
  .cloud{ display:flex; flex-wrap:wrap; gap: 12px 20px; }
  .cloud span{ white-space:normal; }

  .form{ grid-template-columns: 1fr; }
  .spread.tall{ min-height: auto; }
  .edgetype{ display:none; }

  /* the reasons lose the second column, and with it the stagger that
     only existed to keep the two columns from lining up */
  .plate-spec{ column-gap: clamp(12px,4vw,20px); }
  .reasons{ grid-template-columns: 1fr; row-gap: clamp(30px,6vw,52px); }
  .reasons > li:nth-child(even){ margin-top: 0; }
  .reasons-meta{ margin-top: 4px; }
  .masthead .spokes{ justify-content:center; gap: 10px 22px; }

  /* the row wraps into a list here, so the outer spokes stop carrying the
     centring and go back to sitting with the rest */
  .masthead .spokes > span:first-child,
  .masthead .spokes > span:last-child{ flex:0 0 auto; text-align:center; }

  .masthead .braced{ padding-inline: clamp(20px,6.5vw,34px); }
  .masthead .braced::before,
  .masthead .braced::after{ font-size: clamp(40px,11vw,64px); }
  .bleed .mega{ white-space: normal; }
  .bleed{ margin-right:0; }
  .inset-l{ margin-left:0; }
  .nav-links{ gap: 14px; }

  /* the display block no longer runs the measure here, so the small type
     goes back to the size the rest of the page is set in and the two rows
     take the page's own display scale */
  :root{ --t-hero-sm: 13px; }
  .hero-head{ font-size: var(--t-mega); }
  .hero-bar h1{ max-width:100% !important; }
  .hero-actions{ justify-content:flex-start; }

  /* one column here, so there is no right margin to hold to */
  .hero-bar .cap{ text-align:left; }
  .nav-mark{ display:none; }

  /* the three spreads that are built on width rather than on the grid */
  .figure{ font-size: clamp(46px, 23vw, 130px); }
  .offset-down{ margin-top: 0; }

  /* No room for a slender column between the letters, so the copy comes out
     of the word and the word closes back up: the headline reads first, the
     three letters stand together on one line under it as the one word they
     are, and the copy runs the full measure beneath them.

     The row stays a flex container here so that order can be used to lift
     the O out from behind the copy and set it beside the E; the template's
     written order is headline, cycling letter, copy, E, O, and only order
     can put that word back together without moving anything in the markup.
     The headline and the copy each take the whole line, the three letters
     take one line between them, and align-items keeps them on a shared
     baseline whatever the cycling letter is doing.

     --seo-stack tells assets/js/specimen.js it is in this state. The script
     then sizes the three letters to the width of the row rather than to
     what is left beside a column, which is what makes the word run edge to
     edge. The clamp below is the no-JavaScript fallback. */
  .seo-row{
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: flex-start;
    --seo-stack: 1;
  }
  .seo-row > .lowline{
    position: static;
    order: 1;
    flex: 0 0 100%;
    max-width: none;
    text-align: left;

    /* The letters are set with line-height .74, so their ink stands well
       above the top of their boxes. The distance below is the box distance;
       the air the eye reads between the headline and the word is a good deal
       less than it, which is why it is set this open. */
    margin-bottom: clamp(30px, 11vw, 84px);
  }
  .seo-glyph{ font-size: clamp(64px, 32vw, 170px); }
  /* Only the cycling letter is stepped out past the left edge of the
     measure. Type this large carries a side bearing the eye reads as an
     indent, so a letter set flush to the box stands visibly inside the
     headline above it. The E and the O stay on the measure they were set to:
     the ten pixels the first letter takes are given straight back at the E,
     which puts the pair back where they stood and leaves the step as air
     between the cycling letter and them. The margin is smaller than the page
     margin the spread is set in, so nothing is clipped; the script takes the
     letters a little under the full measure so the O still lands inside the
     right edge. */
  .seo-g1{ order: 2; margin-left: -10px; }
  .seo-g2{ order: 3; margin-left: 10px; }
  .seo-g3{ order: 4; }
  .seo-col{
    order: 5;
    flex: 0 0 100%;
    width:auto;
    max-width:100%;
    margin: clamp(16px,4vw,32px) 0 0;
    top: 0;
    font-size: var(--t-body);
    text-align: justify;
    text-justify: inter-word;
    hyphens: auto;
    -webkit-hyphens: auto;
  }
  .seo-col p{ line-height:1.45; }
}

/* ---------- spread 02 — the portrait plate ------------------
   The picture stands the whole height of the spread and bleeds off the
   left edge. Its width is measured in the page's own grid — the left
   margin, columns one to three, and the two gutters between them — so
   the plate stops exactly where column four begins and the rows beside
   it sit on the same grid as every other spread.

   min-height keeps the spread near the picture's own 896:1167 when the
   rows are short. 34vw tracks that proportion across the whole range the
   margin and gutter clamps allow, so the crop stays at the shoulders
   rather than closing in on the head.
   ------------------------------------------------------------ */

.spread-portrait{
  --col: calc((100% - (2 * var(--mar)) - (11 * var(--gut))) / 12);
  --plate: calc(var(--mar) + (3 * var(--col)) + (2 * var(--gut)));
  display:flex;
  flex-direction:column;
  min-height: clamp(300px, 34vw, 660px);
}
.spread-portrait > .grid{ flex:1; align-content:stretch; }

.portrait{
  position:absolute;
  inset:0 auto 0 0;
  width: var(--plate);
  overflow:hidden;
  background: var(--bg);
}
/* the global img rule sets height:auto; the plate needs the section's height */
.portrait img{
  display:block;
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:50% 44%;
}
/* on the dark ground the plate reverses rather than sitting in a white box */
.spread-portrait.ink .portrait img{ filter: invert(1); }

/* The three columns are now three rows, read as an index: hairline, label,
   line. space-between sets the first against the top of the plate and the
   last against its foot, so the type is measured off the picture. */
.threeup-rows{
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  gap: clamp(20px, 2.6vw, 40px);
}
/* nine equal columns in a box exactly nine page-columns wide, so the row
   lands on the page grid without subgrid */
.threeup-row{
  display:grid;
  grid-template-columns: repeat(9, minmax(0,1fr));
  column-gap: var(--gut);
  row-gap: 10px;
  border-top:1px solid var(--hair);
  padding-top: clamp(13px, 1.4vw, 20px);
}
.threeup-row .label{ grid-column:1/4; }
.threeup-row .threeup-say{
  grid-column:4/10;
  max-width:40ch;
  font-size: var(--t-h3);
  line-height:1.32;
}
.threeup-row .threeup-say .link{ font-size: inherit; }

@media (max-width: 900px){
  /* the plate cannot stand beside the rows at this width, so it stands
     above them — still off the left edge, still its own proportion */
  .spread-portrait{ min-height:0; }
  .portrait{
    position:static;
    width:min(62vw, 320px);
    aspect-ratio: 896 / 1167;
    margin:0 0 clamp(30px,6vw,52px) calc(var(--mar) * -1);
  }
  .threeup-row{ grid-template-columns:1fr; }
  .threeup-row .label,
  .threeup-row .threeup-say{ grid-column:1/-1; }
}

/* --------------------------------------------------------------------------
   The rest of the library.

   The five faces above are the ones this page composes itself from. These are
   the cuts the per-word picker can reach: every N27 weight with its drawn
   italic, plus the three faces the other templates use. A declaration costs
   nothing until a character is actually set in it, so the page still fetches
   only what it paints.
   -------------------------------------------------------------------------- */

@font-face{font-family:'N27';font-style:normal;font-weight:100;font-display:swap;src:url(../fonts/n27-thin-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:100;font-display:swap;src:url(../fonts/n27-thinitalic-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:normal;font-weight:200;font-display:swap;src:url(../fonts/n27-extralight-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:200;font-display:swap;src:url(../fonts/n27-extralightitalic-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:400;font-display:swap;src:url(../fonts/n27-regularitalic-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:500;font-display:swap;src:url(../fonts/n27-mediumitalic-webfont.woff2) format('woff2');}
@font-face{font-family:'N27';font-style:italic;font-weight:700;font-display:swap;src:url(../fonts/n27-bolditalic-webfont.woff2) format('woff2');}

@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:400 700;font-display:swap;src:url(../fonts/grotesk-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:'Space Grotesk';font-style:normal;font-weight:400 700;font-display:swap;src:url(../fonts/grotesk-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:300 800;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;}
@font-face{font-family:'Inter';font-style:normal;font-weight:300 800;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:'Agency';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/agency.otf) format('opentype');}
@font-face{font-family:'Neiko';font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/neiko.woff2) format('woff2');}

/* A word the picker has dressed. The synthesis switch is the promise that a
   face with no drawn italic is never slanted by the browser instead; it is
   repeated on the element so it survives whatever the surrounding rule sets. */
.ak-t {
	font-synthesis: none;
}
/* The heading in the head bar and the intro line in the foot both sit a touch
   inside the column they are placed in. Both are pulled 65px back toward the
   left margin, and the intro line's measure is opened by 65px so its first
   sentence holds one row. Relative position rather than transform: the entry
   animation on the hero holds its last frame, which pins transform for as long
   as the page is open. Wide screens only: below 900px the blocks stack and run
   the full measure, where a pull to the left would eat the margin. */
@media (min-width: 901px){
  .hero-bar h1.label,
  .hero-foot .cap.solid{ position: relative; left: -65px; }
  .hero-foot .cap.solid{ max-width: calc(48ch + 65px) !important; }
}


/* ---------- the services index -----------------------------
   One ink column, six services cut across it, and nothing else on the plate.

   The column stands down the middle of the plate and runs the height of the
   set. Every service is shifted a different distance from the left, so each
   name meets the column at a different point and no two entries break in the
   same place. Over the paper a name is ink; over the column it is paper.

   That switch is not two copies of the line clipped against each other. The
   column is a single sheet laid over the names, painted white and set to
   difference, so every pixel under it is inverted: paper turns to ink, ink
   turns to paper, and the boundary falls on the exact letter the column's
   edge lands on. White is the value that inverts this sheet's two grounds
   back into each other almost exactly — #F7F6F2 comes out #08090D and
   #0B0B0B comes out #F4F4F4 — so no third colour enters the spread. Because
   the sheet blends live, a name that moves under it re-breaks as it moves;
   nothing has to be kept in step.

   A name is set to its measure rather than to a size. The renderer carries
   the character count in as --chars and the size is the cell divided by it,
   capped, so a short service stands enormous and a long one comes down to
   sit inside the same cell. Nothing here is a fixed font-size, so the whole
   set scales with the page instead of breaking at a width.

   The note is a drawer. It is held shut against the side of the name and
   rolled open when the name is read — out to the left of a name that sits to
   the right, out to the right of a name that sits to the left — so it always
   opens into the empty half of the plate and never crosses the column. The
   roll is a clip, not a width, so nothing on the plate is laid out twice. The
   name gives a little ground the other way as the drawer comes out, which is
   what makes the two read as one movement rather than as two animations.
   ------------------------------------------------------------ */

.svc-index{
  --band: clamp(180px, 21vw, 340px);   /* the fallback band's own measure */
  --column: 1;                         /* whether the continued column is cut */
  --bleed: clamp(28px, 3.6vw, 76px);   /* how far it runs past the set */
  --nudge: clamp(7px, .95vw, 18px);    /* what the name gives to the drawer */
  --svc-e: cubic-bezier(.16,1,.3,1);
  --svc-roll: cubic-bezier(.22,.9,.28,1); /* the note's own travel, longer and softer than the name's */

  position: relative;

  /* the margins have to stay larger than --bleed, or the column runs up
     under the plate's heading and inverts it */
  margin-top: clamp(56px, 7vw, 116px);
  margin-bottom: clamp(56px, 7vw, 116px);
}

/* The column as this plate alone draws it. It is drawn after the entries and
   over them, and it inverts whatever it covers rather than painting a colour
   of its own. It must not be given a z-index and nothing above it may isolate,
   or it has no backdrop to invert and the plate goes blank.

   This is the fallback. Where the script runs it stands down, and the band is
   cut to the copy column on the spread below and drawn on <main> instead, so
   that one band crosses both spreads. See "The column, continued". */
.svc-index::after{
  content: '';
  position: absolute;
  top: calc(var(--bleed) * -1 + 100px);
  bottom: calc(var(--bleed) * -1 + 100px);
  left: 50%;
  width: var(--band);
  transform: translateX(-50%);
  background: #fff;
  mix-blend-mode: difference;
  pointer-events: none;
}

/* ------------------------------------------------------------
   The column, cut.

   The band is drawn on <main> rather than on the plate, because its measure
   is taken off the copy column on the spread below and off nothing the plate
   itself holds. It is the last positioned thing in <main>, which is the whole
   of why it paints over the plate without a z-index of its own; nothing
   between it and the root may isolate, or it loses the backdrop it inverts
   and the plate goes blank.

   It carries no colour. It is white set to difference, so it prints as the
   opposite of whatever stands under it: black down the paper of the services
   plate. It stops at the foot of that segment. The ink spread below stands on
   its own ground with its copy set in paper, and nothing crosses into it.

   Its measure and its left edge are the copy column's on that lower spread,
   cut ink to ink — the first pixel that column paints to the last, not the
   box the type was set in. Its two ends are the segment's own top and foot,
   so the band stands against the spread above it and against the spread
   below it, touching both and overhanging neither. All four numbers come
   from assets/js/specimen.js, because none of them is a box edge. Without
   the script nothing is drawn here and the plate keeps its own centred
   band.
   ------------------------------------------------------------ */

main{ position: relative; }

main::after{
  content: '';
  display: none;
  position: absolute;
  top: calc(var(--col-t, 0px) + 100px);
  height: calc(var(--col-h, 0px) - 200px);
  left: var(--col-l, 0px);
  width: var(--col-w, 0px);
  background: #fff;
  mix-blend-mode: difference;
  pointer-events: none;
}

main.ak-column::after{ display: block; }
main.ak-column .svc-index::after{ display: none; }

.svc-row{
  --x: 0%;    /* how far the cell starts in from the left */
  --w: 100%;  /* the cell's measure, which is the name's measure */
  position: relative;
  padding: clamp(14px, 1.8vw, 32px) 0;
}

.svc-cell{
  margin-left: var(--x);
  width: var(--w);
  container-type: inline-size;
}

.svc-h{
  /* One size for the whole set. The names were sized by their own length
     before this, which set a short service enormous and a long one small;
     the set now reads as one register and the difference between the
     entries is where each one stands, not how big it is.

     There is no upper stop on the size, because the shifts below are
     fractions of the plate: a size that stopped growing while the plate
     went on growing would pull every name off the band on a wide screen. */
  --svc-size: clamp(30px, 5.85vw, 200px);

  font-size: var(--svc-size);
  font-weight: 700;
  line-height: .9;
  letter-spacing: -.018em;
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--fg);
  transition: transform .62s var(--svc-e);
}

/* The editor may have left a size on a name from when it was set small. The
   line is cut to its measure here, so the marker's size is discarded and its
   face and weight are taken from the line. */
.svc-h .ak-t{ font: inherit; letter-spacing: inherit; }

/* The drawer, shut. It stands with its mouth on the name's inked edge — the
   cut the script hands back as --tl and --tr — and the note is parked one
   full measure past that mouth, under the name itself. Opening does not
   uncover the note where it already lies: it draws it out from under the
   name, so the two read as one piece of type being pulled clear rather than
   as a panel switching on in the clear space beside it. */
.svc-note{
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: calc(var(--t-cap) * 1.42);
  line-height: 1.5;
  color: var(--fg);
  pointer-events: none;
  overflow: hidden;
}
.svc-note > span{
  display: block;
  box-sizing: border-box;
  will-change: transform;
  transition: transform .78s var(--svc-roll);
}

/* Six shifts, repeating: right, left, right, left, right, left. A name that
   sits to the right opens its drawer to the left and a name that sits to the
   left opens to the right.

   The band stands between 52% and 72% of the plate. Each pair of figures is
   cut so the name runs across all of that rather than up to the edge of it —
   the shift puts the name's start left of the band and the measure carries
   it past the far side — while leaving the empty half wide enough for the
   drawer to open into. */
.svc-index > .svc-row:nth-of-type(6n+1){ --x:46%; --w:54%; }
.svc-index > .svc-row:nth-of-type(6n+2){ --x:34%; --w:66%; }
.svc-index > .svc-row:nth-of-type(6n+3){ --x:40%; --w:60%; }
.svc-index > .svc-row:nth-of-type(6n+4){ --x:10%; --w:90%; }
.svc-index > .svc-row:nth-of-type(6n+5){ --x:30%; --w:70%; }
.svc-index > .svc-row:nth-of-type(6n+6){ --x:20%; --w:80%; }

/* Odd entries sit right: the drawer stands to their left and rolls out that
   way, and the name answers by moving right. */
.svc-index > .svc-row:nth-of-type(odd) > .svc-note{
  right: calc(100% - var(--x) - var(--tl, 0px));
  width: calc(var(--x) + var(--tl, 0px) - 2%);
}
.svc-index > .svc-row:nth-of-type(odd) > .svc-note > span{ padding-right: 1.6ch; transform: translateX(100%); }

/* Even entries sit left: the drawer stands to their right. */
.svc-index > .svc-row:nth-of-type(even) > .svc-note{
  left: calc(var(--x) + var(--tr, var(--w)));
  width: calc(100% - var(--x) - var(--tr, var(--w)) - 2%);
}
.svc-index > .svc-row:nth-of-type(even) > .svc-note > span{ padding-left: 1.6ch; transform: translateX(-100%); }

/* Reading one entry puts the other five back, and pulls its drawer open. The
   dim sits on the row's two halves rather than on the row, so it does not
   fight the settle the row is animating on the way in. */
@media (hover:hover) and (pointer:fine){
  .svc-row > *{ transition: opacity .5s var(--svc-e); }
  .svc-index:hover > .svc-row > *{ opacity: .22; }
  .svc-index > .svc-row:hover > *{ opacity: 1; }

  .svc-index > .svc-row:hover > .svc-note > span{ transform: translateX(0); }

  .svc-index > .svc-row:nth-of-type(odd):hover .svc-h{ transform: translateX(var(--nudge)); }
  .svc-index > .svc-row:nth-of-type(even):hover .svc-h{ transform: translateX(calc(var(--nudge) * -1)); }
}

/* The entries come up in order as the plate enters, driven by the scroll
   position rather than by a script. The animation sits on the row and the
   hover sits on its two halves, so neither one holds the other's opacity. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .svc-index > .svc-row{
      animation: rowSettle linear both;
      animation-timeline: view();
      animation-range: entry 2% cover calc(12% + var(--r, 0) * 2.5%);
    }
  }
}

/* A reader who has asked for stillness gets the drawer, not the roll. */
@media (prefers-reduced-motion: reduce){
  .svc-h,
  .svc-note,
  .svc-note > span{ transition-duration: .01ms; }
  .svc-index > .svc-row:hover > .svc-note > span{ transform: none; }
}

/* ---- below the fold of a page -----------------------------
   The column, the shifts and the drawers are all one idea and they go
   together. A name set across an inverting column needs room on both sides of
   it; a drawer needs a half of the plate to open into. Neither exists on a
   narrow screen, so the entry folds back to a name at the full measure with
   its note under it, and the ruled sheet comes back to hold the six apart. */
@media (max-width: 1000px){
  .svc-index{
    margin-bottom: clamp(24px, 3vw, 48px);
    border-bottom: 1px solid var(--hair);

    /* the spread below is stacked at this width and its copy runs the full
       measure, so there is nothing narrow left to cut the band to */
    --column: 0;
  }
  .svc-index::after{ display: none; }

  .svc-row{
    --x: 0% !important;
    --w: 100% !important;
    border-top: 1px solid var(--hair);
    padding: clamp(14px,1.5vw,24px) 0 clamp(26px,2.9vw,46px);
  }

  .svc-note{
    position: static;
    transform: none;
    overflow: visible;
    width: auto !important;
    max-width: 48ch;
    margin-top: clamp(14px,2vw,22px);
  }
  .svc-note > span{ padding: 0 !important; transform: none !important; }

  .svc-h{ --svc-size: clamp(30px, 9.2vw, 92px); }
}

@media (max-width: 620px){
  .svc-h{
    --svc-size: clamp(28px, 10.6vw, 64px);
    letter-spacing: -.01em;
  }
  .svc-note{ font-size: 18.75px; }
  .svc-row{ padding: clamp(12px,1.4vw,18px) 0 clamp(22px,2.6vw,34px); }
}

/* ---------- the run ----------------------------------------
   The six columns above are one paragraph here. What was a column gap is a
   few spaces the author typed into the run itself, so the reasons read as one
   continuous statement rather than as six entries in a table, and where the
   run breaks is a decision made in the editor rather than one the grid makes.

   The face, the rule above it and the measure are the plate's, so the spread
   still belongs to the sheet it sits on: only the columns are gone.
   ------------------------------------------------------------ */

.reasons-run{
  --run-lh: 1.85;

  margin-top: clamp(44px,6.4vw,120px);
  padding-top: clamp(16px,1.8vw,28px);
  border-top: 1px solid var(--hair);

  font-family: ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  font-size: clamp(11px, .92vw, 15px);
  line-height: var(--run-lh);
  font-variant-ligatures: none;
  color: var(--fg);

  text-align: justify;
  text-align-last: left;
  hyphens: none;
  overflow-wrap: break-word;
}

.reasons-run p{ margin: 0; }

/* A blank line typed in the editor is one empty line of the run's own
   leading. The gap between paragraphs is set to exactly that, so the canvas
   and the page break in the same places. */
.reasons-run .ak-p-next{ margin-top: calc(var(--run-lh) * 1em); }

/* The names run in at the head of their sentences, in the uppercase the
   columns used to head themselves with, without taking a line of their own. */
.reasons-run strong{
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* The run is typed into the page in the editor, where an empty field would
   otherwise be an invisible one. */
.reasons-run [data-rich-text-placeholder]::after{ opacity: .45; }

@media (max-width: 1080px){
  .reasons-run{ font-size: clamp(10.5px,1.35vw,13px); }
}

/* Justification needs a measure to work with. A phone has not got one, so the
   run is set ragged there rather than opening into rivers. */
@media (max-width: 620px){
  .reasons-run{
    font-size: 11px;
    text-align: left;
  }
}

/* ------------------------------------------------------------
   09 — WHY ME, AS A FIELD
   ------------------------------------------------------------
   The columns and then the run are gone. Every reason stands at its own
   point on the sheet, hairlines run between the points, and the head sits
   at the root of them. The placing is written down in ak_reasons_map();
   this only says how a point is drawn once it has been put somewhere.

   Nothing here is large except the rail's word and the two strokes. That is
   deliberate: the plate is almost entirely empty, and emptiness only reads
   as emptiness when there is nothing big in it competing for the eye. A
   headline at h2 size would fill the sheet on its own, so the head is set
   at lead size and the field is left to be a field.

   The rail always carries the paper ground's colour, whichever ground the
   spread itself is set to, so it reads as a light panel cut into the sheet.
   ------------------------------------------------------------ */

.spread.reasons-map{
  /* the field carries the spread's own margin, so the sheet is handed to it
     whole */
  padding: 0;
}

.rmap{
  display: block;
}

/* ---------- the head ---------------------------------------- */

/* The name of the plate, set flat in the top left corner in the same register
   the track record's head uses, so the two plates open the same way. */
.rmap__rail{
  padding: clamp(30px, 3.8vw, 66px) var(--mar) 0;
}

.rmap__rail span{
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .185em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--mute);
  white-space: nowrap;
}

/* ---------- the plot --------------------------------------- */

.rmap__field{
  padding: clamp(40px, 6vw, 96px) var(--mar);
  min-width: 0;
}

/* Percentages on an absolutely placed child resolve against the padding box,
   so the padding above would not hold anything off the edges. The plot is the
   box the points are measured in, and it carries no padding of its own. */
.rmap__plot{
  --plot-h: clamp(620px, 72vh, 880px);

  position: relative;
  min-height: var(--plot-h);

  /* The sheet runs to 100% but the lowest point sits at 63.5%, so better than a
     third of the plot is dead air no mark ever reaches. The coordinates are
     shares of this box, so the box cannot simply be made shorter — the band is
     cropped off the bottom instead.

     76px is what the lowest point actually occupies past its own origin (its
     name, the rule under it and the sentence that rolls out, plus the few
     pixels the first point's cap sits below its own box top). Taking 32.5% back
     and handing those 76px to the crop leaves the air under the last sentence
     equal to the air over the first name, at every height the plot is drawn. */
  margin-bottom: calc(76px - .325 * var(--plot-h));
}

.rmap__wires{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}

/* One hairline, at the weight of every other rule on the sheet. The stroke is
   held off the scaling so a line at 8 degrees and a line at 70 come out the
   same thickness, which is the whole point of a hairline. */
.rmap__line{
  stroke: var(--fg);
  stroke-width: 1;
  opacity: .5;
  transition: stroke-width .18s cubic-bezier(.22,1,.36,1),
              opacity .18s cubic-bezier(.22,1,.36,1);
}

/* The wire under the wire. Drawn in nothing and sixteen units across, so the
   cursor can find a line that is one pixel wide. The sheet takes no pointer at
   all; this is the one thing on it that puts its hand up. */
.rmap__hit{
  stroke: transparent;
  stroke-width: 16;
  pointer-events: stroke;
}

/* ---------- the head, at the root of the wires -------------- */

/* 27% is not a free number. The wires leave the head at 29% across, so its own
   words have to stop before that at every width the field is drawn at — hence a
   share rather than a measure, and a floor low enough never to overtake it.

   45% is not free either. The three wires that leave the head leave it at 43,
   45 and 47, so the point they fan out of sits at 45; the head is hung from its
   own middle at that same height, and the three of them therefore run back to
   the title and not to the sentence under it. That only holds if the title is
   the whole of what is being centred, so the lede is lifted out of the flow and
   hung underneath — see .rmap__lede. */
.rmap__hub{
  position: absolute;
  left: 0;
  top: 45%;
  transform: translateY(-50%);
  width: clamp(200px, 27%, 380px);
}

.rmap__title{
  font-size: var(--t-lead);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.008em;
  text-wrap: balance;
}

.rmap__lede{
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;

  margin-top: clamp(14px, 1.6vw, 22px);
  font-size: var(--t-cap);
  line-height: 1.5;
  max-width: 26ch;
}

/* ---------- a point on the sheet --------------------------- */

.rmap__nodes{
  position: absolute;
  inset: 0;
  list-style: none;
  pointer-events: none;
}

/* Four offsets, all written by the script, all in pixels, all zero until it
   writes them — so the placing above is the whole of the layout and the sheet
   is drawn correctly before a single frame has run.

   --dx/--dy is the pull towards the cursor. --px/--py is the shove a point
   takes from whichever other point is currently open. They are separate so the
   two can be eased at different rates and neither has to know about the other;
   they meet here, once, in one transform. */
.rmap__node{
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  width: clamp(158px, 16.5vw, 246px);
  pointer-events: auto;

  --dx: 0px;
  --dy: 0px;
  --px: 0px;
  --py: 0px;
  --ox: 0px;
  --oy: 0px;
  transform: translate3d(calc(var(--dx) + var(--px) + var(--ox)), calc(var(--dy) + var(--py) + var(--oy)), 0);
}

/* The point being read runs its sentence down over whatever the shove has not
   cleared, so it has to be the thing on top. */
.rmap__node:is(:hover, :focus-visible){ z-index: 3; }

/* Why me: Built around your needs and budget stands 40px further right and 20px
   lower than its placing. A fixed offset in pixels, so it is the same at every
   width; the script reads it and moves the point's wire with it. */
@media (min-width: 901px){
  .rmap__node[data-idx="3"]{ --ox: 40px; --oy: 20px; }
}

.rmap__node:focus-visible{
  outline: 1px solid var(--fg);
  outline-offset: clamp(8px, .8vw, 13px);
}

/* The rule belongs to the name and not to the column, so it is as wide as the
   name is and no wider. A rule run to a fixed measure turns six labels into six
   table cells, which is the thing this plate stopped being. */
.rmap__name{
  width: fit-content;
  max-width: 100%;

  font-size: var(--t-cap);
  font-weight: 400;
  line-height: 1.28;
  letter-spacing: 0;

  padding-bottom: .5em;
  border-bottom: 1px solid var(--fg);
}

/* The clip the sentence rolls out of.

   Opening is CSS and nothing else, so a sheet with no JavaScript is still six
   names that each answer when they are rested on. The air above the sentence is
   the clip's padding rather than the sentence's margin, because the script
   measures this box by its own height and a margin sits outside that.

   --h is that measurement, written on here per point. The fallback is what the
   roll-out runs to before the script has measured anything and if it never
   does: deep enough for the longest of the six, and a clip that opens to more
   room than its sentence needs simply stops early. */
.rmap__reveal{
  padding-top: clamp(11px, 1.15vw, 17px);

  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height .46s cubic-bezier(.22,1,.36,1),
              opacity .3s ease;
}

.rmap__node:is(:hover, :focus-visible) .rmap__reveal{
  max-height: var(--h, 15em);
  opacity: 1;
}

.rmap__note{
  font-size: clamp(10.5px, .82vw, 12.5px);
  font-style: italic;
  font-weight: 300;
  line-height: 1.58;
  color: var(--mute);
  text-wrap: pretty;
}

/* While one point is being read the other five go back. Not far — the shape of
   the cloud is the thing being looked at and it cannot be allowed to disappear
   — but far enough that the sentence rolling out is what is being read. */
.rmap__node{
  transition: opacity .28s ease;
}

.rmap__plot:has(.rmap__node:is(:hover, :focus-visible)) .rmap__node:not(:hover, :focus-visible){
  opacity: .42;
}

/* ---------- the two strokes -------------------------------- */

.rmap__marks{
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.rmap__mark{
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  display: block;
}

.rmap__stroke{
  display: block;
  width: clamp(5px, .5vw, 8px);
  height: clamp(94px, 10.6vw, 152px);
  background: var(--fg);
  transform: rotate(var(--a));
  transform-origin: 50% 0;
}

.rmap__caption{
  display: block;
  margin-top: clamp(16px, 1.9vw, 28px);
  font-size: clamp(10.5px, .82vw, 12.5px);
  font-style: italic;
  font-weight: 300;
  line-height: 1.4;
  color: var(--fg);
  white-space: nowrap;
}

/* ---------- the plate's own register ----------------------- */

/* Not a point on the sheet. It sits under the field on a rule, at label size,
   the way a plate number sits under a plate. */
.rmap__spec{
  margin-top: clamp(40px, 5vw, 84px);
  padding-top: clamp(14px, 1.6vw, 24px);
  border-top: 1px solid var(--hair);
  max-width: 460px;
}

/* ---------- reading one point ------------------------------ */

/* The field is a field, so the eye has nowhere to start. Resting on one point
   takes the others back far enough to read it and not so far that the shape
   of the field is lost. */
/* A wire is lit by the point it arrives at. There is no selector for "the wire
   whose data-to matches the point being rested on", so the six pairs are
   written out; keying them off the plot rather than the point is what lets the
   effect reach back up out of the list and onto the sheet. Resting on the line
   itself lights it too, because a line made wide enough to be pointed at ought
   to answer when it is. */
.rmap__wire:hover .rmap__line,
.rmap__plot:has(.rmap__node[data-idx="0"]:is(:hover, :focus-visible)) .rmap__line[data-to="0"],
.rmap__plot:has(.rmap__node[data-idx="1"]:is(:hover, :focus-visible)) .rmap__line[data-to="1"],
.rmap__plot:has(.rmap__node[data-idx="2"]:is(:hover, :focus-visible)) .rmap__line[data-to="2"],
.rmap__plot:has(.rmap__node[data-idx="3"]:is(:hover, :focus-visible)) .rmap__line[data-to="3"],
.rmap__plot:has(.rmap__node[data-idx="4"]:is(:hover, :focus-visible)) .rmap__line[data-to="4"],
.rmap__plot:has(.rmap__node[data-idx="5"]:is(:hover, :focus-visible)) .rmap__line[data-to="5"]{
  stroke-width: clamp(5px, .5vw, 8px);
  opacity: 1;
}

/* Nothing travels. The sentence is there or it is not, the point sits where it
   was placed, and the cursor pulls nothing along with it — the script reads
   this and stops driving the offsets rather than driving them into transitions
   that have been switched off. */
@media (prefers-reduced-motion: reduce){
  .rmap__line,
  .rmap__node,
  .rmap__reveal{ transition: none; }
}

/* ---------- a phone has no field --------------------------- */

/* A hand's width cannot hold a field: the points would sit on top of one
   another and the wires would be longer than the gaps they cross. The placing
   is dropped rather than squeezed, the hairlines go with it, and what is left
   is the same six names under the same rules, read down instead of across.
   The rail stays — it is the plate's mark, not its layout. */
@media (max-width: 900px){

  /* nothing is placed by share here, so there is no dead band to crop */
  .rmap__plot{ min-height: 0; margin-bottom: 0; }
  .rmap__wires{ display: none; }

  .rmap__hub,
  .rmap__lede,
  .rmap__nodes,
  .rmap__node,
  .rmap__marks,
  .rmap__mark{
    position: static;
    width: auto;
    transform: none;
  }

  .rmap__hub{ max-width: 30ch; }

  .rmap__marks{
    display: flex;
    align-items: flex-start;
    gap: clamp(30px, 8vw, 56px);
    margin-top: clamp(44px, 8vw, 72px);
  }

  .rmap__nodes{
    display: grid;
    gap: clamp(34px, 6.4vw, 54px);
    margin-top: clamp(44px, 8vw, 72px);
  }

  /* The measure is set on the text and not on the block it sits in: a line of
     italic this small reads to about fifty characters and no further, whatever
     width the hand holding it happens to be. */
  .rmap__node{ max-width: 46ch; }
  .rmap__note{ max-width: 50ch; }

  /* A finger cannot rest on anything, so there is nothing to roll out: every
     sentence stands open under its name and nothing is pulled anywhere. The
     script stands down at this width as well. */
  .rmap__reveal{
    max-height: none;
    overflow: visible;
    opacity: 1;
  }

  .rmap__plot:has(.rmap__node:is(:hover, :focus-visible)) .rmap__node:not(:hover, :focus-visible){
    opacity: 1;
  }
}





/* Why me: the head set in capitals, N27 bold italic, at twice its former size;
   the six points at one and a half times theirs; the sentence under the head removed. */
.rmap__title{
  font-family: 'N27', var(--ff);
  font-size: calc(var(--t-lead) * 2);
  font-weight: 700;
  font-style: italic;
  text-transform: uppercase;
  line-height: 1;
  letter-spacing: -.01em;
}
.rmap__lede{ display: none; }
.rmap__name{ font-size: calc(var(--t-cap) * 1.5); }
.rmap__note{ font-size: clamp(15.75px, 1.23vw, 18.75px); }
@media (min-width: 901px){
  .rmap__node{ width: clamp(237px, 24.75vw, 369px); }
  .rmap__name{ font-size: min(calc(var(--t-cap) * 1.5), 1.5vw); white-space: nowrap; max-width: none; }
}



/* Why me: the head box runs to the right edge of the screen; the spread clips the overflow. */
.rmap__title{ width: 100vw; max-width: none; text-wrap: wrap; }


/* Why me: a point that rises clear of the head keeps the ground it rose off,
   so the cursor it rose out from under is still resting on it and the point
   does not drop straight back. --lift is written by the script; nought at rest. */
.rmap__node::after{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: var(--lift, 0px);
}
@media (max-width: 900px){
  .rmap__node::after{ display: none; }
}
/* Service descriptions in italic */
.svc-note,
.svc-note > span {
	font-style: italic;
}
