/* ---------------------------------------------------------------------------
   Lusaxweb — Rupture
   Two colours, no greys of our own. Everything that needs to be readable over
   both of them goes through `mix-blend-mode: difference`, so the chrome never
   has to know whether it is sitting on ink or on paper.
--------------------------------------------------------------------------- */

/* The second face, and it has one job: the things on this page that are
   written rather than labelled — the line over the run, and the name each work
   answers with. Everything else is Satoshi, which is what the chrome is set in.

   Served as OTF and not as WOFF2, deliberately. These are 45 and 47 KB as they
   ship, which is under what most WOFF2 web faces weigh, and there is no build
   step in this project to convert them in — a toolchain added to save eight
   kilobytes is a toolchain that then has to exist forever. */
@font-face {
  font-family: "PP Supply Sans";
  src: url("../public/fonts/PPSupplySans-Regular.otf") format("opentype");
  font-weight: 400;
  font-display: block;
}
@font-face {
  font-family: "PP Supply Sans";
  src: url("../public/fonts/PPSupplySans-Ultralight.otf") format("opentype");
  font-weight: 200;
  font-display: block;
}

@font-face {
  font-family: "Satoshi";
  src: url("../public/fonts/Satoshi-Light.woff2") format("woff2");
  font-weight: 300;
  font-display: block;
}
@font-face {
  font-family: "Satoshi";
  src: url("../public/fonts/Satoshi-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: block;
}
@font-face {
  font-family: "Satoshi";
  src: url("../public/fonts/Satoshi-Medium.woff2") format("woff2");
  font-weight: 500;
  font-display: block;
}
@font-face {
  font-family: "Satoshi";
  src: url("../public/fonts/Satoshi-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: block;
}
@font-face {
  font-family: "Satoshi";
  src: url("../public/fonts/Satoshi-Black.woff2") format("woff2");
  font-weight: 900;
  font-display: block;
}

:root {
  /* ONE GROUND — and the sheet opens PALE.

     These two used to be turned over seven times on the way down a single
     page: every block carried its own ground, and every seam between two of
     them was a full-screen crossing from paper to ink. The arc (`--cap`)
     existed to soften that crossing, and softening it was never the fix — the
     eye re-adapts at every one of them, and a photograph reads as two different
     photographs depending on which neighbour it drew.

     So: the sheet is ONE tone, top to bottom, across every section — the run,
     the landing, Services, Ventures, About and the form — and the dial in the
     crown turns the WHOLE of it over at once. NOTHING is allowed to disagree
     with it. Not a block at the foot of a page, not an opened service, not the
     load screen. A section that had an opinion of its own about which way up it
     was is the one failure this file is written to make impossible, and the
     rule is stated as a rule and not as a habit: `--paper` is the ground,
     `--ink` is what is printed on it, and a ground painted `var(--ink)` is a
     bug unless what it stands on is a PHOTOGRAPH — which has no polarity to
     follow and is the single documented exception (`.hb--reel`).

     The two values below are the pale sheet, and it is the default rather than
     a preference: a first visit, a private window and a browser with storage
     switched off all open white. The dark one exists and is remembered — see
     the boot script in index.html, which is the only other place these two
     names are ever written. */
  --paper: #ffffff;
  --ink: #000000;
  --pad: clamp(16px, 2.4vw, 40px);
  /* The shell's own margin, and only the shell's: the four corners of the
     chrome — wordmark, index, crown, count, gauge, marks — sit 40% tighter to
     the edge than everything printed inside the sheet. The furniture on a
     plate and the padding of a section still read against `--pad`; a control
     that lives on the edge of the screen is a different distance from a title
     that lives on a card, and conflating the two is what left the corners
     breathing. Everything that has to stay in register with a corner control
     (`.opened`, the section's filing) is measured from here too. */
  --pad-edge: calc(var(--pad) * 0.6);
  /* THE DOCK, AND THE FLOOR IT MAKES.

     The track's button is the tallest thing in the bottom rail — the gauge is
     five pixels shorter and the row of social marks eight — so its height plus
     the shell's own margin IS the strip at the foot of every screen that is
     permanently spoken for. Nothing printed inside the sheet may end inside it.

     That number lived in `.sound` and was read nowhere else, which is exactly
     how the foot of the landing came to reserve fifty-six pixels for a rail
     sixty pixels tall: two places agreeing about the same distance by hand,
     until one of them was edited. It is one declaration now — the control that
     draws the circle reads `--dock`, and everything that has to STOP above the
     rail reads `--floor` — so the two cannot drift apart again.

     `--safe-b` is the home indicator, declared in styles.responsive.css and
     zero on every display without one. It is in here rather than in the rail's
     own rule because the floor is the rail's whole footprint, inset included:
     a phone that pushes the rail up by the bar it swipes on has to push
     everything standing above the rail up by the same amount. */
  --dock: clamp(30px, 2.5vw, 36px);
  /* AND THE RAIL IS NOT ALWAYS ONE CONTROL TALL. Above 620 it is three columns
     and `--dock` is its height, because the button is the tallest thing in it.
     Below 620 it stacks into three ROWS and the same rail is a hundred and
     eleven pixels — a number no stylesheet can read off a sibling, and a number
     that changes the day a fourth control joins the row.

     So `--rail-h` is MEASURED. `floor.js` publishes it here from a
     ResizeObserver on the rail itself; `--dock` is the fallback for the one
     frame before the first measurement lands, and it is deliberately the
     SHORTER of the two states — see the head of that file. */
  --floor: calc(var(--pad-edge) + var(--safe-b, 0px) + var(--rail-h, var(--dock)));
  --hair: 1px;
  /* THE RING, and there is only one of it.

     Three controls in the shell are the same object — the track's button at the
     foot of the sheet, the invert dial in the crown and the key beside it. One
     circle, one hairline, a mark in the middle that carries the state. They
     were drawn at three different strengths, in three different blocks, and
     that is the whole history of this control being wrong: the track's button
     sat at 26% and read exactly right, while the dial next to it was walked
     from 10% to 38% to the FULL ink chasing a legibility problem it never had
     — and each of those revisions is a different pair of numbers written in a
     different place, so the corner could not stay in step even when somebody
     meant it to.

     So the weight is stated ONCE, here, and the three controls read it.

     AT HALF THE INK, and it was at a quarter. The old number came from the
     track's button and the argument for it was right in the abstract — a
     hairline is a BOUNDARY and the mark inside it is what the reader is meant
     to look at — but a quarter of the ink is 1px at 26% over an unknown ground,
     which is a line you can only see once you already know where to look. All
     three of these controls were being missed. Half is still a boundary and not
     a mark: against the half-disc at FULL strength two millimetres away, the
     ring reads as the edge of the control and the disc reads as its state,
     which is the whole of what the quarter was protecting and it does it at a
     weight that survives a photograph. Hover lifts it to 85%.

     One number for the three, and that is the point of it being here: the
     corner got out of step twice before, each time because somebody raised the
     strength in one of three blocks.

     `currentColor` and not `--ink`, and that is what makes one number work in
     two layers: custom properties substitute where they are USED, so this
     resolves against whatever the control is inked in — white inside `.chrome`,
     which rides `difference`, and `--ink` inside the crown, which carries its
     own pair and turns over with the sheet. Dark line on the pale sheet, light
     line on the dark one, and nothing to keep in step by hand. */
  --ring: color-mix(in srgb, currentColor 50%, transparent);
  --ring-hi: color-mix(in srgb, currentColor 85%, transparent);
  /* THE SHELL AT REST, and there is one of it for the same reason there is one
     ring.

     Two runs of small marks sit on the rails and neither of them is the thing
     the reader came for: the five words at the head and the six social marks
     at the foot. Both were held back by being made faint — the run at 0.45 and
     the marks at 0.42 — and faint is not the same as quiet. At those numbers,
     over a
     bright frame of the ribbon or a pale product shot, they were not held back,
     they were gone; and they were two different numbers in two blocks saying
     one thing, which is exactly the shape the ring was in before it was pulled
     up here.

     Quiet is the JOB of the size and the spacing — ten pixels, tracked, on a
     rail — and this only has to keep them off the same plane as what is
     current. Whatever is current, hovered or focused goes to 1, and the step
     from here to there is still a clear change. */
  --quiet: 0.72;
  --t-micro: clamp(0.62rem, 0.74vw, 0.95rem);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-snap: cubic-bezier(0.7, 0, 0.16, 1);
  /* Overshoots. Two controls in this piece are meant to arrive with a bounce
     rather than a glide, and both do it on a curve rather than on a spring
     library. */
  --ease-pop: cubic-bezier(0.18, 1.5, 0.35, 1);
  --ease-pop-soft: cubic-bezier(0.2, 1.32, 0.36, 1);

  /* --- THE UNCOMMON FACE, named once, for the whole site -------------------
     The site has two faces and only two. Satoshi is what it READS in — the
     body, the labels, the rows, the chrome — and it is set on `body` once,
     twenty lines down. This is the other one, and it is what the site SAYS
     things in: the quote at the size of a wall, the display figures in the
     count, the numbered heads on About.

     It lived on `.about` and was spelled out by hand in six other places, one
     of them with a `Georgia` in the fallback stack that no other copy of it
     has. Six literals is six chances to fall back to Satoshi on the day
     somebody renames a file, and the day it happens the page does not break —
     it just quietly stops being the studio's page. One name, one stack, both
     sheets read it. */
  --face-display: "PP Supply Sans", "Satoshi", system-ui, sans-serif;

  /* --- THE SOFT LINE -------------------------------------------------------
     One weight, everywhere a hairline is drawn ON TYPE rather than as
     structure: the chips, the cell rules under the figures, the thread under
     the moves. `--hair` is how THICK; this is how HARD it prints.

     `currentColor` and not `--ink`, which is what makes it survive the one
     documented exception to the ground: on a block standing on a photograph
     the local colour is white, and the line follows the type it belongs to
     instead of staying the sheet's black. It resolves at the point of USE, so
     declaring it here rather than per page costs nothing and means the two
     sheets cannot drift to two different opinions about how hard a hairline
     should print — which is exactly what had happened. */
  /* --- HOW HARD A HAIRLINE PRINTS, and it is ONE number --------------------
     Every rule on the sheet — the seam at the top of a block, the two edges of
     the reel's column, the line under a question, the line under a column
     title in the foot — was carrying an opacity written where it was drawn.
     Sixteen of them, between 0.06 and 0.55, and the spread was legible: the
     foot's column titles printed at half the ink and read as underlines, the
     question list printed at a tenth and read as nothing, and the two were
     four screens apart on one page. Nobody chose that; it is what happens when
     a shared weight is stated sixteen times.

     So it is stated once, here, and every structural line reads it.

     THE NUMBER IS NOT THE SAME ON BOTH SHEETS, and that is the whole reason
     there are two of them. A hairline's weight is not its alpha, it is the
     CONTRAST the alpha buys against the ground under it — and the two grounds
     are not symmetric. Black at 13% on white and white at 13% on black are not
     the same line: the second one is barely there. So the pair is named for
     what the LINE is, not for which sheet is up:

       --rule-light   a WHITE hairline, on the dark sheet or on a photograph
       --rule-dark    a BLACK hairline, on paper

     `--rule-sheet` is whichever of the two the SHEET is currently printing in,
     and `--rule-o` is what a line actually reads. They are the same value
     everywhere except in the one block that pins its own pair — `.hb--reel`,
     standing on a photograph, which is the site's one documented exception to
     the ground. That block re-points `--rule-o` between `--rule-sheet` and
     `--rule-light` on its own crossfade rather than writing a number of its
     own, which is why the two names exist: a local override cannot read the
     value it is overriding.

     `--line-soft` is derived from it rather than being a second opinion about
     the same thing. It was 17% flat, which is a third weight on a page that
     should have one: the chips, the cell rules under the figures and the
     thread under the moves are hairlines like any other, and they turn over
     with the sheet like any other. `--hair` is still how THICK; this is how
     HARD. */
  --rule-light: 0.2;
  --rule-dark: 0.13;
  --rule-sheet: var(--rule-dark);
  --rule-o: var(--rule-sheet);
  --line-soft: color-mix(in srgb, currentColor calc(var(--rule-o) * 100%), transparent);

  /* THE JOINT'S SIZE. Its WEIGHT is not here and cannot be: the node is a
     multiple of the LOCAL `--rule-o` and not of the sheet's, so that a corner
     inside the reel's ink-to-paper crossfade is joined at the weight the rules
     meeting there are actually being drawn at. A custom property resolves its
     own `var()`s where it is DECLARED, so a `--joint-o` written here would be
     frozen at the sheet's weight and would ignore that crossfade entirely. It
     is spelled out in `.joint` instead, where it is read against whatever
     weight is in force at the corner. */
  --joint: 3px;
}

/* The dark sheet prints its rules in white, so it takes the white weight. This
   is the ONLY place the choice is made — see the note above. */
:root[data-sheet="dark"] {
  --rule-sheet: var(--rule-light);
}

/* --- THE JOINT -------------------------------------------------------------
   A 3px disc dropped exactly where a vertical rule crosses a horizontal one.

   It is not decoration. Four hairlines meeting at four corners with nothing at
   the corners is four lines that happen to end near each other; a node at the
   crossing is what makes them one FRAME. Draughtsmen have done it forever, and
   they do it for the same reason: the eye needs to be told that the meeting
   was intended.

   Centred on the crossing by construction: the caller names the POINT with
   `left`/`top` and the disc pulls itself back by half of its own size, so it
   does not care how thick `--hair` is, which side of the pixel a rule landed
   on, or whether the corner is measured from the near edge or the far one.

   `transform` is spent on that centring and is therefore NOT available for the
   entrance — which is what the separate `scale` property is for, and why the
   node can arrive with the frame without either rule fighting the other for
   the same declaration.

   It inherits `currentColor` like the rules it joins, so a frame printed white
   over a photograph gets a white node and the same frame on paper gets a black
   one, with nothing to keep in step by hand.

   --- AND IT HAS TO BE SEEN ---------------------------------------------------
   The weight was `1 - (1 - o)²` — the hairline printed exactly twice, which is
   what physically happens where two strokes cross. Honest, and invisible: on the
   plate half of the reel's crossfade the rules run at 13%, so the node came out
   at 24% — a three-pixel dot at a quarter strength, which on a white sheet is
   nothing at all. Four corners of nothing is the frame the node exists to stop.

   A draughtsman's node is not two passes of the same nib. It is a DIFFERENT
   mark, put down deliberately, and it is darker than either line through it —
   that is how the eye reads it as a decision rather than as an overlap. So it is
   four times the rule's own weight, held under 0.7 so it stays ink on a sheet
   rather than a bullet: 52% where the rules run at 13%, 70% where they run at
   20%, and it still turns over with the sheet and still tracks the reel's
   crossfade because it is still read off the LOCAL `--rule-o`.

   Three pixels, round, centred — the size and the shape are unchanged, and the
   size is deliberate: bigger than this and the node stops being a node and
   starts being a dot somebody put in the corner. */
.joint {
  position: absolute;
  width: var(--joint);
  height: var(--joint);
  border-radius: 50%;
  background: currentColor;
  opacity: min(0.7, calc(var(--rule-o) * 4));
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* --- glass -----------------------------------------------------------------
   The one thing in the piece that carries a ground of its own.

   Everything else is either the material or `mix-blend-mode: difference` over
   it, and both of those work because the ribbon is one bit. A photograph
   running full bleed is not: difference against a mid-tone returns a mid-tone,
   and a control that vanishes over half the images is not a control. So the
   two pieces that have to be readable no matter what is behind them — the
   cursor's label and the way back out of a work — are smoked glass.

   Smoked, and not clear, for exactly that reason. `brightness()` in the
   backdrop filter is what guarantees the contrast rather than hoping for it:
   the darkest ground it can be handed is already dark, and the lightest is
   pulled down to somewhere white type sits on comfortably. */
.back__glass,
.tag__disc {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.07);
  border: var(--hair) solid rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(16px) saturate(150%) brightness(0.42);
  -webkit-backdrop-filter: blur(16px) saturate(150%) brightness(0.42);
  box-shadow:
    0 14px 34px rgba(0, 0, 0, 0.3),
    inset 0 1px 0 rgba(255, 255, 255, 0.42);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Firefox before 103 and anything else without the filter gets a flat scrim.
   Less beautiful, exactly as readable, which is the half that matters. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .back__glass,
  .tag__disc {
    background: rgba(0, 0, 0, 0.66);
  }
}

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

/* Out of sight, in the tree. The page has one heading and the design does not
   draw it; this is how it stays a heading rather than becoming nothing.
   `clip-path` and a 1px box rather than `display: none`, which would take it
   out of the accessibility tree along with the layout. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Satoshi", system-ui, -apple-system, sans-serif;
  font-weight: 400;
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

#sheet {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Load-bearing. Without it the browser claims the gesture and the
     pointermove stream dies halfway through a drag. */
  touch-action: none;
  cursor: none;
}

body.is-coarse #sheet {
  cursor: auto;
}

/* --- the opened project ----------------------------------------------------
   A second canvas over the first. It is transparent and non-interactive until
   an aperture is tapped; `is-live` is set by overlay.js on the frame the flood
   starts and cleared on the frame it has finished draining, so the ribbon is
   never left underneath a canvas that is eating its gestures for nothing. */
#opening {
  position: fixed;
  inset: 0;
  /* Above the sheet, below the chrome: the close control is difference-blended
     over whatever the project happens to be showing, exactly as the lockup is
     over the ribbon. */
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  pointer-events: none;
  cursor: ns-resize;
}

#opening.is-live {
  pointer-events: auto;
}

/* --- the shell, and what is only the ribbon's -------------------------------
   Two sets, and the whole of the site's navigation rests on the line between
   them.

   The SHELL is the site: the wordmark, the run of sections, the track's
   control, the marks pointing elsewhere, and the two controls in the crown.
   It is on screen in every section and it does not know which one you are in.
   A studio's name and its links do not stop being true because you walked into
   Services, and a menu that empties itself out on the way into a section is a
   site that has to be reloaded to be left.

   The FURNITURE below is the ribbon's own, and only the ribbon's: the sentence
   over the run, the reading of the fusion, the count of twenty-three, the
   filing plates, the cursor lens and its tag. These are readings OF the run —
   there is no twenty-three and no fusion in Services — so they go out with it.

   The one thing the shell gives up is the run of five words, and only inside a
   work: see `.crumbs`, which takes its place on the same centre line. */

/* The ribbon's furniture, gone the moment the run is not what is on screen —
   because a project is open on top of it, or because another section is. */
body.is-open .creed,
body.is-open .lockup__slot,
body.is-open .gauge,
body.is-open .probe,
body.is-book .creed,
body.is-book .lockup__slot,
body.is-book .gauge,
body.is-book .probe {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.32s var(--ease-out);
}

/* THE RUN OF SIX WORDS DOES NOT LEAVE. Not for a work, not for a book, not for
   a section.

   It used to: opening a work took the index out and stood the path in its grid
   cell, on the argument that nothing in the run points at a single work so the
   run cannot say where you are. That argument is about the PATH and it is
   still right — what was wrong was the trade. A reader inside a work who wants
   Services should not have to leave the work to find the word, and a shell
   that empties itself out one section at a time is not a shell, it is six
   fragments taking turns.

   So the run stays put and the path arrives UNDER it — see `.crumbs`, which is
   absolutely placed against this rail rather than sharing its cell. Two rows,
   two meanings: where you can go, and where you are.

   Both halves of that are in the stylesheet now. For a while only this one was:
   the run was told to stay and the path was left in the run's own grid cell, so
   the head of the sheet printed the two on top of each other and the menu read
   as doubled. See the note over `.crumbs`. */
body.is-open .index,
body.is-book .index {
  opacity: 1;
  pointer-events: auto;
}

/* The way out spent a while at the foot of the sheet, in the middle of the
   bottom rail. It was sent there because the top left corner has the wordmark
   in it — the shell stays up while a work is open, and two things cannot have
   the same corner — and the middle of the bottom rail was the one slot none of
   the four corners had already taken.

   It was free, and it was not where anybody was looking. Everything that says
   where you are is at the HEAD of the sheet: the name, the run, and the path
   under it that reads WORK / VUESAX. A reader who wants out of a work reads
   that line and then has to travel the height of the window to act on it.

   So the way back comes up to the head, and it does not need the corner to do
   it. It takes the row UNDER the corner — the wordmark on the rail, the work's
   own business hung beneath it, which is the same filing `.services__head`
   does one section over. The offset is written the way that one is, `--pad-edge`
   plus the rail's line and the air below it, so the two cannot drift apart.

   Under the path and not beside it. The pill is pinned to the left and the path
   is centred on the sheet, so the two could hold the same row without ever
   touching — but a 45px lens dropped into the line the path is set on is the
   loudest thing on that line, and the head of the sheet would read as three
   voices at once. It takes the row after instead, and the offset says so: the
   rail's line, then the path's, then the way out. Three rows, three meanings —
   who this is, where you are, and how you leave.

   The box hugs the pill now rather than spanning the rail. Edge to edge with
   the button centred in it was the right shape for a thing centred on the
   sheet and is the wrong one for a thing pinned to a corner — a full-width box
   across the head would lie over the run and the path for no gain, and every
   `pointer-events` argument in this file is about not doing that. */
.opened {
  position: fixed;
  inset: calc(var(--pad-edge) + 2.9em + 2.4em) auto auto var(--pad-edge);
  /* Above the opened work's canvas, and above the ribbon's own chrome. */
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 24px;
  pointer-events: none;
  font-size: var(--t-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-weight: 500;
  /* Absent on the run, and absent rather than transparent. The pill used to be
     held back by `opacity: 0` on `.back__glass` alone, which is a control that
     is still there: it keeps the middle of the bottom rail, it carries a live
     `backdrop-filter`, and any frame that finds `is-open` on the body while
     the ribbon is what is under it prints a way out of nowhere. There is
     nowhere to go back to from the run, so on the run there is no way back.

     `visibility` and not `display`, because the entrance is a transform and a
     transform needs a box to start from: `display: none` would leave the pill
     no layout to arrive into and it would appear rather than land. The delay
     on the way out is the length of the glass's own fade — the control keeps
     its box for exactly as long as it is still fading, and is gone the frame
     after. */
  visibility: hidden;
  transition: visibility 0s linear 0.34s;
}

/* The way back exists only while there is something to come back from. It is
   the only control left in this slot — the column's and the form's pills are
   gone, see the note where they used to be in `index.html` — so nothing else
   can turn up in it at the same time. */
body.is-open .opened {
  visibility: visible;
  transition: visibility 0s linear 0s;
}

/* The run is visible inside a work now, so it is clickable inside a work too —
   which is the whole point of leaving it up. `main.js#go` already closes
   whatever is open before it shows a section, so a word pressed from inside a
   work is a way out of it as well as a way somewhere. */
body.is-open .index__item,
body.is-book .index__item {
  pointer-events: auto;
}

body.is-open .back {
  pointer-events: auto;
}

/* --- the same way out, out of a service ------------------------------------
   Same box, same corner, same material, different switch. A book is opened out
   of the services column and a work out of the ribbon, and the two states are
   never on at once — but they are held by two different classes, and a single
   rule answering to both is a rule that prints a way out of nowhere the first
   frame the two overlap. So the slot is duplicated and the pill is not.

   Everything under `.back` — the glass, the halo, the arrow, the key, every
   hover and focus state — is shared as written. What is in here is only the
   box and the four lines that say WHEN. */
.bookout {
  position: fixed;
  inset: calc(var(--pad-edge) + 2.9em + 2.4em) auto auto var(--pad-edge);
  /* Over the strip, which stands at 7. The chrome and the crown are given the
     same number for a book, five hundred lines down; this is the third member
     of that corner and it is written here because it is the only one of the
     three that exists for this state alone. */
  z-index: 8;
  display: flex;
  align-items: center;
  pointer-events: none;
  font-size: var(--t-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-weight: 500;
  /* Absent off the book, and absent rather than transparent — see the note on
     `.opened`, which this is the twin of. */
  visibility: hidden;
  transition: visibility 0s linear 0.34s;
}

body.is-book .bookout {
  visibility: visible;
  transition: visibility 0s linear 0s;
}

body.is-book .back {
  pointer-events: auto;
}

/* --- the chrome, over a photograph -----------------------------------------
   `.chrome` rides `mix-blend-mode: difference`, which is the right answer over
   a one-bit ribbon and the wrong one over a photograph running full bleed:
   difference against a mid-tone returns a mid-tone, and the shell would be
   drawn and still be unreadable. So over an opened work the layer stops
   blending and carries its own ink — white, with a shadow under it, which is
   the one pair that holds over any photograph anybody uploads.

   Gated on `is-flooded` and not on `is-open`, because the two are not the same
   instant. `is-open` goes on as the flood LEAVES the card, and for the length
   of that travel what is under the shell is still the ribbon's white paper —
   white ink on it would be white on white. `is-flooded` is main.js reporting
   that the photograph has reached the edges of the viewport, and the shell
   fades out and back in across that switch rather than changing its mind in
   front of the reader.

   The book does not want any of this: its ground is a solid `--ink`, and
   difference against black or white is exactly what difference is good at. */
/* It used to fade the layer out for the length of the flood and bring it back
   once the photograph had reached the edges. It does not any more: the sheet
   is one tone now, so what is under the shell while the ink travels is the
   ground it was already legible on, and a shell that blinks on every open is a
   shell the reader learns not to trust. What still switches at `is-flooded` is
   the INK — difference is right over a one-bit sheet and wrong over a
   photograph — and that switch is a cross-fade of two colours rather than of
   the whole layer. */
body.is-open.is-flooded .chrome {
  mix-blend-mode: normal;
  color: #ffffff;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.55)) drop-shadow(0 0 22px rgb(0 0 0 / 0.4));
  transition: opacity 0.42s var(--ease-out) 0.05s;
}

/* Same argument, same corner, other layer. The crown carries its own ink out
   of `--ink`/`--paper` rather than blending, so over a photograph it is told
   which two colours to be instead of being told to stop blending: a white ring
   with a white mark in it, and a shadow to seat the group. */
/* THE SAME CASE, TWICE, and the second one is the landing's first screen.

   The rule below was written for an opened WORK: a photograph under the
   corner, and neither of the sheet's two colours a safe guess. The run at the
   head of the landing is that case exactly — full bleed, edge to edge, with
   the shell reading white across it — and it was never covered, so the dial
   was a black ring with a black half-disc in it sitting beside a white
   wordmark and a white index.

   `[data-corner]` is not a third opinion about the ground. It is the landing
   saying which of its own two grounds is under the corner, off the crossfade it
   already computes for that block's furniture — see `--reel-k` in
   `home/section.js`. One number spent twice, so the corner cannot fall out of
   step with the type standing beside it. */
body.is-open.is-flooded .crown,
:root[data-corner="photo"] .crown {
  /* TWO shadows and not one, and it is the same pair `.chrome` is given six
     lines up, for the same reason. The wide one seats the corner as a group —
     it is a haze under a cluster of controls, and against a hairline ring it is
     worth almost nothing. The tight one is what a ONE-PIXEL circle needs over a
     photograph: a dark edge directly under the light edge, close enough that
     the two are read as one line rather than as a line and a smudge. Without it
     the dial's ring lands on whatever the top right corner of a photograph
     happens to be, and on about a third of these that is a bright sky.

     The SHADOW is on the group and the PAIR is not — see the rule below. */
  /* THE SHADOW IS GONE and the PAIR is what is left. The pin below is doing
     the whole job now: white ink on a photograph, no halo under it. A haze
     under a cluster of hairline controls was reading as a smudge on the pale
     sheet, which is the corner announcing itself twice. */
  filter: none;
}

/* --- and NOT on the pill ---------------------------------------------------
   The pair used to be pinned on `.crown` itself, which put it on all three of
   the corner's controls. Two of them want it: the menu and the dial are a
   hairline ring and a mark, both drawn straight out of `--ink`, and over a
   photograph they have to be told to be white rather than left to be whatever
   the sheet says.

   The pill does not, and pinning it there was a bug you could state in one
   line: on the PALE sheet the corner said `--paper: #101010`, the pill paints
   its plate out of `--paper`, and the site's one control that asks for
   something came out as a black lozenge on a white page — a button that had
   stopped agreeing with the theme, in the corner, permanently, on every opened
   work. The rule was written for the dial's ring and it reached one control too
   far.

   So the pin is scoped to everything in the corner EXCEPT the pill, and the
   pill goes back to reading the sheet's own pair off `:root`: black plate with
   white type on the ink sheet, white plate with black type on the paper one,
   and the alloy rim around it either way. It does not need the pin to survive a
   photograph the way the ring does — its plate is opaque, which the ring's one
   pixel of hairline is not.

   `> :not(.contact)` and not `.theme, .menu`: the corner is a place things get
   added to, and a rule that names what is exempt keeps working when a fourth
   control turns up — the shape a rule takes when it is about one exception
   rather than about a list of members. */
/* VENTURES IS NO LONGER IN THIS SELECTOR, and that is the whole of its half of
   the theme fix. It was here because the section's two scrim bands were pinned
   black on both sheets, so the corner standing on them had to be pinned too;
   the bands read `--paper` now and the corner can go back to reading the sheet
   like every other control on the site. What is left in here is the case the
   pin was written for — an opened WORK, where what is under the corner is a
   photograph and neither of the sheet's two colours is a safe guess. */
body.is-open.is-flooded .crown > :not(.contact),
:root[data-corner="photo"] .crown > :not(.contact) {
  --ink: #ffffff;
  --paper: #101010;
}

/* There was a rule here lifting the two rings from 38% to 62% for this one
   state, and it has gone for the reason the whole opacity argument has gone:
   an override that raises a ring for one section is a fourth number for a
   control that is supposed to have one. The rings read `--ring` at every
   moment now — see `:root` — and what makes them survive a photograph is the
   pair and the shadow the crown is handed two rules up, which is the same
   protection `.chrome` is handed for the same reason on the same frame.

   Ground first, then weight. Every time it was done the other way round the
   ring came back either invisible over a sky or shouting on the sheet. */

/* --- the crown, over the book ----------------------------------------------
   There were eight lines here turning the crown's pair over for the length of
   an opened service, and both halves of the problem they solved have gone.

   `.book` used to be `background: var(--ink)` — the one ground on the site
   that was the sheet's inverse. Open a service on a pale sheet and the screen
   went black; the crown, every part of which is drawn out of `--ink`, was then
   ink on ink and the corner was simply not there, so the pair was swapped back
   for that layer only. A local inversion, and a local correction for the local
   inversion, both of them existing because one ground disagreed with the
   sheet.

   The book stands on `--paper` now, like everything else that is not a
   photograph — see `.book` at the foot of this file. There is nothing left to
   correct: the crown is the sheet's own pair over the sheet's own ground, in
   every section and in every state, which is what the corner was supposed to
   be the whole time. The `--sheet-ink` / `--sheet-paper` pair that carried the
   swap went with it; nothing else read them.

   The photograph is a different case and it is still handled, above: a work
   flooded full-bleed has no polarity to follow, so the crown is told which two
   colours to be rather than being turned over. */

/* --- the way back ----------------------------------------------------------
   The only control on screen while a work is open, which is the whole argument
   for how large it is. It used to be a hairline cross and the word "cerrar",
   set in difference over whatever photograph happened to be under it — findable
   if you already knew it was there, and invisible if you did not.

   Everything here animates off `body.is-open`, so the entrance re-runs on every
   open without a line of script: the class goes on when the flood starts and
   comes off when it drains. */
.back {
  position: relative;
  display: inline-flex;
  align-items: center;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
}

/* The same material as the cursor's lens, and nothing else on top of it.
   glass.js gives both of them the same displacement filter and the same smoke,
   so what is left here is taking off the two things the disc does not have: the
   rim and the cast shadow.

   `transparent` rather than `border: 0`. The border is a hairline and the box
   is `border-box`, so dropping the declaration would pull two device pixels out
   of the pill's height on the frame it landed — the geometry stays, the line
   goes. The shadow goes altogether, inset bevel included: a lens does not sit
   above the page, it is a hole cut in it. */
.back__glass {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 13px 20px 13px 15px;
  border-radius: 999px;
  border-color: transparent;
  box-shadow: none;
  /* Arrives small and rotated a hair off true, and lands square. A control
     that scales up on a straight line reads as a modal; one that settles reads
     as a thing that was thrown to you. */
  transform: translateX(-18px) scale(0.82) rotate(-4deg);
  opacity: 0;
  transition:
    transform 0.72s var(--ease-pop),
    opacity 0.34s var(--ease-out),
    gap 0.45s var(--ease-out),
    padding 0.45s var(--ease-out),
    background-color 0.4s var(--ease-out),
    border-color 0.4s var(--ease-out);
}

body.is-open .back__glass {
  transform: none;
  opacity: 1;
  /* Past the flood. Controls arriving while the ink is still moving read as
     two unrelated animations that happen to overlap. */
  transition-delay: 0.34s;
}

/* Same arrival, later. The flood is over in a third of a second; the unfold a
   book comes up on runs 860ms (`OPEN_MS`), and a pill landing halfway through
   it is a control arriving into a rectangle that is still growing under it. */
body.is-book .back__glass {
  transform: none;
  opacity: 1;
  transition-delay: 0.62s;
}

/* One ring, once, on the frame the button lands. Pure celebration and it is
   gone in a second — it exists so the eye is told where the way out is before
   it has to go looking. */
.back__halo {
  position: absolute;
  inset: -5px;
  border-radius: 999px;
  border: var(--hair) solid rgba(255, 255, 255, 0.6);
  opacity: 0;
  pointer-events: none;
}

body.is-open .back__halo {
  animation: halo 1.15s 0.46s var(--ease-out) both;
}

body.is-book .back__halo {
  animation: halo 1.15s 0.74s var(--ease-out) both;
}

@keyframes halo {
  0% {
    opacity: 0.85;
    transform: scale(0.9);
  }
  100% {
    opacity: 0;
    transform: scale(1.34);
  }
}

/* The arrow is drawn from two rules rather than set as a glyph: at this size a
   typographic arrow is a smudge, and the shaft has to be able to grow. */
.back__arrow {
  position: relative;
  display: block;
  width: 18px;
  height: 10px;
  transition: transform 0.5s var(--ease-pop-soft);
}

.back__head,
.back__shaft {
  position: absolute;
  background: currentColor;
}

.back__head {
  left: 0;
  top: 50%;
  width: 9px;
  height: 1.5px;
  margin-top: -0.75px;
  border-radius: 1px;
  box-shadow: 0 0 0 0 currentColor;
  transform-origin: left center;
  /* Two halves of a chevron, from one element: the box rotated one way and its
     own ::after rotated the other. */
  transform: rotate(-40deg) scaleX(0.62);
}

.back__head::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: currentColor;
  border-radius: inherit;
  transform-origin: left center;
  transform: rotate(80deg);
}

.back__shaft {
  left: 1px;
  right: 0;
  top: 50%;
  height: 1.5px;
  margin-top: -0.75px;
  border-radius: 1px;
  transform-origin: right center;
  transform: scaleX(0.68);
  transition: transform 0.5s var(--ease-pop-soft);
}

/* The word is gone, and with it the six hand-set letter delays that used to
   stagger it up out of its own clip. What is left is the arrow and the key,
   which is what the control was actually made of. */
.back__key {
  padding: 1px 5px 2px;
  border: var(--hair) solid rgba(255, 255, 255, 0.45);
  border-radius: 5px;
  font-size: 0.82em;
  letter-spacing: 0.12em;
  opacity: 0.72;
}

/* Hover pulls the whole pill a step to the left and lets the shaft run out to
   full length — the gesture the button is about, performed by the button. */
/* The tint comes up and the rim stays gone. Answering a hover by drawing a
   border would put back, for as long as the pointer is on it, exactly the line
   the control was asked not to have. */
.back:hover .back__glass,
.back:focus-visible .back__glass {
  gap: 15px;
  padding-right: 24px;
  background-color: rgba(255, 255, 255, 0.15);
  transform: translateX(-3px);
}

.back:hover .back__arrow {
  transform: translateX(-4px);
}

.back:hover .back__shaft {
  transform: scaleX(1);
}

.back:active .back__glass {
  transform: translateX(-3px) scale(0.955);
  transition-duration: 0.12s;
}

.back:focus-visible {
  outline: none;
}

.back:focus-visible .back__glass {
  outline: 2px solid #ffffff;
  outline-offset: 3px;
}

/* --- the furniture on each work --------------------------------------------
   Number in one corner, what was made in the other. Always there, at every
   speed — the hover is the cursor's promise and nothing else — and carried by
   the work rather than laid over it: furniture.js writes each node the card's
   rectangle and the card's tilt every frame, so these travel, stretch and lean
   with the aperture they belong to.

   Its own ground, like the cursor and the way back, and for the same reason:
   these live on photographs, and `difference` over a photograph is a gamble
   this layer is not allowed to take. Flat smoked pills rather than the
   refracting glass of the disc — there can be fourteen works on screen with
   three chips each, and forty-two backdrop displacement filters is not a
   design decision, it is a frame budget. */
.plates {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  color: #ffffff;
  font-size: clamp(0.46rem, 0.56vw, 0.7rem);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-weight: 500;
}

/* The work's rectangle, to the pixel. The corners are found by padding it, so
   the furniture cannot come out of register with the aperture whatever the
   layout did to it this frame.

   A flat inset in px, from `--pad`, which furniture.js owns: the title along
   the bottom is sized against the room this padding leaves, and a percentage
   of a card that grows with speed is a different number every frame — the
   title would resize as the belt ran. Twenty clears the corner radius at every
   width the ribbon is drawn at. */
.plate {
  position: absolute;
  left: 0;
  top: 0;
  /* The card's own centre. The node is translated to the work's top-left
     corner and then turned, and the turn has to happen about the middle of the
     rectangle for the two to stay in register — which is the default, and is
     written down here because the transform depends on it. */
  transform-origin: 50% 50%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--pad, 20px);
  /* A name longer than the work wraps inside it. Never past it: the furniture
     is printed on the work and nothing printed on a work leaves it. */
  overflow: hidden;
  will-change: transform;
}

/* One row across the top: what it is on the left, which one it is on the
   right. Diagonally opposite, so neither corner has to be read past the
   other. */
.plate__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8%;
}

/* Everything arrives by resolving and rising a little, never by sliding: it is
   printed on the work, and print does not slide.

   No shadows anywhere in this layer, on any of the three parts. A drop shadow
   under white type is a halo of somebody else's black sitting on a photograph
   that was graded by whoever shot it, and twenty-three of them across a run
   read as a UI laid over the work rather than as something printed on it. What
   pays for the contrast instead is the chips' own blur, and nothing else. */
.plate__num,
.plate__tag {
  display: block;
  white-space: nowrap;
  opacity: var(--v, 0);
  transform: translateY(calc((1 - var(--v, 0)) * -7px)) scale(calc(0.82 + 0.18 * var(--v, 0)));
}

/* No pill. One short figure in the corner of a work reads as a plate number
   set on the print; the same figure inside a pill reads as a button. */
.plate__num {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  transform-origin: right top;
  flex: none;
  /* Twenty px of AIR on both edges, not twenty px of box.

     Up by whatever the line box carries over the top of a digit — half-leading
     plus the gap between the ascender the box is cut to and the cap height a
     figure is actually drawn to. furniture.js measures it off the real face and
     writes `--num-trim`; the fallback is zero, which is the old behaviour and
     not a guess at the number.

     Out by exactly the tracking, because `letter-spacing` is added after the
     last digit as well and that trailing sliver is not ink — left in, the
     figure sits a pixel further from its edge than the chips sit from theirs,
     which is the one asymmetry the eye picks up in a corner. */
  margin-top: calc(var(--num-trim, 0) * -1em);
  margin-right: -0.1em;
}

/* In a row, one after the other. `wrap` rather than `nowrap` and rather than
   shrinking them: three of the longest chips in the set — "Art Direction",
   "Editorial", "Print" — fit across a work at every width the ribbon is drawn
   at, but a chip that ran out of room would otherwise squeeze its own text
   instead of moving, and a squeezed chip is a broken one. */
.plate__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  /* Room between the pills, not just inside them. Three chips set tight read
     as one segmented control; set apart they read as three things a work was.
     In `em` off the rail rather than the chip, so the gap does not grow with
     the pill when the pill grows. */
  gap: 0.72em;
  min-width: 0;
}

/* The blur, and only the blur. No border, no shadow: an outline around a pill
   this small is most of what the eye reads, and what it reads is a control. */
.plate__tag {
  /* The right side is the left side minus the tracking. `letter-spacing` is
     added after the final glyph too, so a pill padded evenly is a pill with
     its word sitting a hair to the left inside it — small, and three of them
     in a row is where small becomes visible. */
  padding: 0.56em 0.85em 0.6em 0.98em;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.42);
  transform-origin: left top;
  font-size: 1.02em;
}

/* The name. Not a caption and not set like one: the layer above is uppercase,
   tracked out and half a rem, because that is how a service list is read at
   speed. This is the work saying what it is called, so it takes the lockup's
   treatment instead — sentence case, black, tracked in.

   `--title` is 48 or 32 and furniture.js decides which by measuring the name
   against the room the card has. It is not a media query and it cannot be one:
   `fit` bottoms out at 0.52, so every viewport under 786 px draws the same
   small work, and a breakpoint at 620 leaves a whole band of widths where the
   card is already too narrow and the type has not been told. */
.plate__title {
  margin: 0;
  /* The same face as the line over the run, and for the same reason: this is
     the work answering with its own name, not a caption filed against it.
     Regular, because the family ships Regular and Ultralight and nothing else —
     asking for 700 here would land on a synthesised bold, which is the browser
     smearing a face sideways and calling it a weight. */
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
  font-size: var(--title, 48px);
  line-height: 0.92;
  font-weight: 400;
  letter-spacing: -0.02em;
  text-transform: none;
  overflow-wrap: anywhere;
  transform-origin: left bottom;
  opacity: var(--v, 0);
  /* Two moves in one transform, and they are different kinds of thing.

     `--ink-x` / `--ink-y` are the correction: the name's own bearing on the
     left and whatever the line box carries under its lowest ink at the bottom,
     both measured off THIS name in furniture.js and taken back out, so the
     mark reads twenty from the corner rather than the box reading twenty and
     the mark reading whatever the face felt like. It is per work and it has to
     be — `Salt & Static` has no descender and `Pliegue` does, and before this
     the two of them sat ten px apart on a caption that is supposed to be one
     line along one edge.

     The 10px is the arrival, unchanged: the name lands last and rises the last
     of the way in. It is added to the correction rather than replacing it, so
     a title is at its measured position the moment it has finished arriving. */
  transform: translate(var(--ink-x, 0px), calc(var(--ink-y, 0px) + (1 - var(--v, 0)) * 10px));
}

/* The chips' whole ground. Where the blur is available it does the work and
   the tint steps back out of the way; where it is not, the tint above is left
   to carry it alone. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .plate__tag {
    background: rgba(0, 0, 0, 0.26);
    backdrop-filter: blur(9px) saturate(140%) brightness(0.82);
    -webkit-backdrop-filter: blur(9px) saturate(140%) brightness(0.82);
  }
}

/* A work that is on the belt and cannot be opened yet. One chip, and the only
   one in this layer set in paper instead of smoke: the smoked pills say what a
   work was, and this is not a fourth of those — it is the reason a press on
   the work does nothing. Opaque, so it needs no blur to be read. See `soon` in
   ribbon/projects.js. */
.plate--soon .plate__tag {
  background: rgba(255, 255, 255, 0.92);
  color: #000000;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The work is opening. The furniture is the ribbon's, not the project's. */
body.is-open .plates {
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}

/* --- the two panes the shell is read through -------------------------------
   Difference against white returns `1 - b`, so a glyph reads at `|1 - 2b|`
   against its own surround: full strength at black, full strength at white,
   zero at mid-grey. The chrome cannot fail over this piece's own sheet, which
   is one bit by construction, and it fails completely over the middle of a
   photograph — which is most of a photograph.

   These are the fix, and the whole design of them is that they are NOT a
   navigation bar. `--cg-need` is written by chrome-contrast.js and it is a
   measurement of the failure rather than of the brightness: the mean, over the
   sampled strip, of `1 - |1 - 2b|`. A black-and-white ground averages to zero
   and gets no glass at all; a flat mid-tone averages to one and gets all of
   it. So over the run these are not merely faint, they are absent, and the
   piece is exactly what it was before this file existed.

   `--cg-dir` is which end the correction is heading for — 1 toward light, 0
   toward dark, latched with a dead band so it cannot oscillate through the
   grey it exists to leave. Used here as an interpolation factor rather than as
   a branch, which is the only way a stylesheet can read a number.

   Glass and not a wash. The tone alone would be a bar of colour across the
   head of the sheet with the material vanishing under it; the blur, the
   saturation lift and the hairline along the inner edge are what make it read
   as the sheet passing under something rather than as a panel laid on top —
   the same claim the edge at the foot makes, made where the shell needs it. */
/* --- the glass edge at the foot of the sheet -------------------------------
   The bottom of the viewport is not where the page stops — it is where the
   page goes under an edge, and the last stretch of it is seen through the part
   of that edge where the surface has begun to curve. Four layers, and they are
   four different physical claims stacked bottom to top: the BEND is
   refraction, the HAZE is depth, the WASH is the sheet closing over what is
   left of the run, and the SHEEN is the face of the glass.

   The markup is built by glassedge.js and inserted immediately before the
   shell's own panes, which is where it sits in the z-order too: siblings at one
   layer paint in document order, so the run and the filing printed on it go
   under the edge and the shell stays on top of it. Of everything along the
   foot of the sheet, the things the shell puts there are the only ones that
   are not the material — a control you cannot read is not a control.

   Nothing in here is decoration on a still page. `--ge-flow` is the speed of
   whatever is running underneath, written by glassedge.js, and the edge is a
   flat pane at rest and a lens while the sheet is moving. */
.glassedge {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  /* Deep enough to be a boundary rather than a hairline of blur, short enough
     that it never reaches the reading position of the run. */
  height: clamp(128px, 19vh, 260px);
  pointer-events: none;
  /* 0..1, the speed of the run under the edge. Declared here so the whole
     subtree resolves even on the first frame, before js has written one. */
  --ge-flow: 0;
  /* How much of the band each of the four haze steps covers. */
  --ge-slice: 25%;
  opacity: 0;
  transition: opacity 0.8s var(--ease-out);
}

/* Brought in on the frame the gate opens, the way the index is: the band is
   under the gate until then, and an edge nobody watched arrive is an edge that
   did not arrive. */
.glassedge.is-in {
  opacity: 1;
}

.glassedge > i,
.glassedge__haze > i {
  position: absolute;
  inset: 0;
  display: block;
}

/* --- the haze --------------------------------------------------------------
   Four bands of blur, each masked to its own quarter and each sampling the one
   below it, so what the reader gets is a ramp rather than a line where a blur
   switched on. Compounding is the point and it is why they are four elements
   and not one: `backdrop-filter` has exactly one radius, and a boundary that
   goes from sharp to gone at one radius is a pane of frosted plastic laid on
   the page.

   Blur before the mask, in that order per layer: the mask decides how much of
   this layer is showing, not how much of the page it was allowed to read. */
.glassedge__haze > i {
  --ge-from: calc(var(--ge-step) * var(--ge-slice));
  --ge-to: calc((var(--ge-step) + 1) * var(--ge-slice));
  -webkit-mask-image: linear-gradient(to bottom, transparent var(--ge-from), #000 var(--ge-to));
  mask-image: linear-gradient(to bottom, transparent var(--ge-from), #000 var(--ge-to));
}

/* Roughly doubling, and written out rather than computed off `--ge-step`: the
   ramp is geometric, `calc()` has no exponent, and four numbers a reader can
   see are worth more here than a formula that would have to be faked with a
   multiply anyway. Each one is scaled by the flow — the edge is not merely
   deeper while the run is moving, it reaches further up the sheet.

   Saturation and brightness ride alongside, and they are not decoration. A
   blur is an average, and an average of a photograph is a duller, greyer
   photograph: the colour cancels itself. That is why frosted plastic looks
   like weather and glass does not — real glass loses almost nothing on the way
   through, so what comes out is as saturated as what went in. Putting it back
   in proportion to how hard each layer is blurring is what stops the smear
   reading as fog over the run. The brightness lift is the same argument for
   the specular: a thick edge gathers light as well as bending it, and it is a
   few per cent because this is a surface, not a light source. */
.glassedge__haze > i:nth-child(1) {
  backdrop-filter: blur(calc(2px * (1 + var(--ge-flow) * 0.9))) saturate(1.06) brightness(1.006);
  -webkit-backdrop-filter: blur(calc(2px * (1 + var(--ge-flow) * 0.9))) saturate(1.06) brightness(1.006);
}

.glassedge__haze > i:nth-child(2) {
  backdrop-filter: blur(calc(5px * (1 + var(--ge-flow) * 0.9))) saturate(1.14) brightness(1.012);
  -webkit-backdrop-filter: blur(calc(5px * (1 + var(--ge-flow) * 0.9))) saturate(1.14) brightness(1.012);
}

.glassedge__haze > i:nth-child(3) {
  backdrop-filter: blur(calc(12px * (1 + var(--ge-flow) * 0.9))) saturate(1.26) brightness(1.02);
  -webkit-backdrop-filter: blur(calc(12px * (1 + var(--ge-flow) * 0.9))) saturate(1.26) brightness(1.02);
}

.glassedge__haze > i:nth-child(4) {
  backdrop-filter: blur(calc(26px * (1 + var(--ge-flow) * 0.9))) saturate(1.42) brightness(1.03);
  -webkit-backdrop-filter: blur(calc(26px * (1 + var(--ge-flow) * 0.9))) saturate(1.42) brightness(1.03);
}

/* --- the wash --------------------------------------------------------------
   The sheet, closing over what is left. The only layer down here that carries
   ink, and it takes it from `--paper` — so the bottom edge of the screen is
   the page's own colour in both states, and the frame the sheet inverts is the
   frame this inverts with it.

   Four stops and not two. A straight ramp to the paper reads as a fade over a
   picture; holding it near nothing for the first third and letting it close in
   the last is how the ground of a page arrives — you do not see it start. */
.glassedge__wash {
  background: linear-gradient(
    to bottom,
    transparent 0%,
    color-mix(in srgb, var(--paper) 12%, transparent) 38%,
    color-mix(in srgb, var(--paper) 52%, transparent) 72%,
    color-mix(in srgb, var(--paper) 90%, transparent) 100%
  );
  opacity: calc(0.74 + var(--ge-flow) * 0.26);
}

/* --- the sheen -------------------------------------------------------------
   The one thing the three layers under it cannot produce, and the one the eye
   uses to decide there is a surface at all.

   Refraction says the light is being bent, depth says there is thickness to
   bend it through, and the wash says the sheet closes over what is left. None
   of them says the glass has a FACE. A pane in a room has a face because it
   returns some of the light that falls on it, and what that looks like on a
   rim seen edge-on is a band a shade brighter along the crown of the curve and
   a hairline brighter still where the surface turns over.

   Two shapes and no more. The band is the crown: broad, low, sitting where the
   sag is steepest rather than at the very bottom, because the crown of a
   cylinder seen edge-on is not its silhouette. The hairline is the lip, one
   device pixel, at the top of the band where the page goes under.

   Additive, through `plus-lighter`, and that is the whole reason it works on
   both states of the sheet. An opacity over a dark page is a grey veil and
   over a light one is nothing at all; ADDING light is what a reflection does,
   and it reads the same over ink and over paper without knowing which it is.

   It rides `--ge-flow`, so at rest it is a hint and at speed it is the
   brightest thing on the screen. The edge is a lens that deepens as the run
   goes under it, and a face that did not catch more light as the surface
   turned would be a face painted on. */
.glassedge__sheen {
  mix-blend-mode: plus-lighter;
  opacity: calc(0.5 + var(--ge-flow) * 0.5);
  background:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 16%, transparent) 0,
      transparent 1px),
    linear-gradient(to bottom,
      transparent 34%,
      color-mix(in srgb, var(--ink) 7%, transparent) 68%,
      transparent 92%);
}

/* --- the edge on a phone ---------------------------------------------------
   Half the haze, and glassedge.js has already dropped the refraction — see the
   note there. Four full-width backdrop blurs stacked on a displacement filter
   is a few per cent of a frame on a laptop and the whole frame on a handset,
   and the run underneath is the thing that has to stay at sixty.

   The two that go are the two shallowest. What survives is the part of the
   ramp that carries the boundary; what is lost is the first sixty pixels of
   it, where the difference between two and five px of blur is a difference
   nobody has ever seen on a five-inch screen. */
@media (pointer: coarse) {
  .glassedge__haze > i:nth-child(1),
  .glassedge__haze > i:nth-child(2) {
    display: none;
  }
  .glassedge__haze > i:nth-child(3) {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 60%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 60%);
  }
}

/* --- where the edge does NOT belong ----------------------------------------
   Everywhere except the run, and the reason is what the edge is a claim ABOUT.

   The ribbon is a strip with no end: it goes on in both directions and the
   only thing that ever stops it is the frame. Saying that the frame is a
   material the run passes under is true there, and it is the whole idea.

   Every other section on this site has an end of its own. The landing and
   About finish on a footer, the column settles on a card, the wall is bounded.
   An edge across the foot of any of them is not a boundary being described —
   it is two hundred pixels of ground colour laid over the last block of the
   page, and what is under it is type somebody is trying to read and controls
   they are trying to press.

   The book is out for a third reason: it is a horizontal strip on its own
   axis, and a bend that runs down the screen is a claim about the wrong
   direction. It would be saying the strip goes under the bottom of the
   viewport when what it does is run off both sides.

   Opacity and not `display`, so the band is still measured and its map still
   painted at the right size — walking back onto the run brings it in on the
   transition it arrives with rather than popping it into place. */
body.is-home .glassedge,
body.is-about .glassedge,
body.is-services .glassedge,
body.is-ventures .glassedge,
body.is-contact .glassedge,
body.is-contents .glassedge,
body.is-open .glassedge,
body.is-book .glassedge {
  opacity: 0;
}

.chromeglass {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.chromeglass > i {
  position: absolute;
  left: 0;
  right: 0;
  display: block;
  /* Declared here so the subtree resolves on the first frame, before js has
     written one. Zero is "nothing needed", which is also the correct state for
     a browser that never runs the script at all. */
  --cg-need: 0;
  --cg-dir: 1;
  /* Not `--paper`/`--ink`: those are the sheet's two colours and this is not
     about the sheet's palette. The target is a LUMINANCE — the two ends of the
     range difference can read — and they are black and white on every theme
     this piece will ever have. */
  --cg-tone: color-mix(in srgb, #ffffff calc(var(--cg-dir) * 100%), #000000);
  /* Deep enough to cover the rail and the line of type under it, short enough
     that it is a rim on the screen rather than a band across it. */
  height: clamp(92px, 13vh, 168px);
  opacity: var(--cg-need);
  /* Both scaled by the need, so a strip that wants nothing is not paying for a
     full-width backdrop-filter to do nothing to it. The saturation lift is
     what stops the correction reading as fog: pushing a photograph toward one
     end of the range takes the colour out of it, and putting some back is the
     difference between glass and weather. */
  backdrop-filter:
    blur(calc(var(--cg-need) * 13px))
    saturate(calc(1 + var(--cg-need) * 0.55));
  -webkit-backdrop-filter:
    blur(calc(var(--cg-need) * 13px))
    saturate(calc(1 + var(--cg-need) * 0.55));
  transition: opacity 0.24s linear;
}

/* Three stops, and the middle one is what keeps it off the type. A straight
   ramp puts half the tone at the halfway line, which on the top rail is
   exactly where the index sits: the correction would be at half strength on
   the one row it exists for. Held near full to past the rail, then let go. */
.chromeglass__top {
  top: 0;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--cg-tone) 94%, transparent) 0%,
    color-mix(in srgb, var(--cg-tone) 78%, transparent) 46%,
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
}

.chromeglass__bot {
  bottom: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--cg-tone) 94%, transparent) 0%,
    color-mix(in srgb, var(--cg-tone) 78%, transparent) 46%,
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(to top, #000 58%, transparent 100%);
  mask-image: linear-gradient(to top, #000 58%, transparent 100%);
}

/* The inner edge, as a hairline of the light the pane is bending.
   A pane of glass that ends in a gradient ends nowhere — the eye reads a
   fade, which is an absence, and an absence has no thickness. One device pixel
   of the opposite tone along the boundary is what says there is a surface
   here and that it stops. It arrives with the pane and it is the last thing to
   go, which is why it rides the need on a curve of its own. */
.chromeglass > i::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: color-mix(
    in srgb,
    color-mix(in srgb, #000000 calc(var(--cg-dir) * 100%), #ffffff) 26%,
    transparent
  );
  opacity: calc(var(--cg-need) * var(--cg-need));
}

.chromeglass__top::after { bottom: 0; }
.chromeglass__bot::after { top: 0; }

/* Neither pane belongs to a flooded work or to the contents page. Both of
   those carry their own ground and the shell over them is already told what to
   do — see the `is-flooded` rules below, which take the chrome off difference
   entirely. A correction on top of that correction is two answers to one
   question. */
body.is-open .chromeglass,
body.is-book .chromeglass,
body.is-contents .chromeglass {
  opacity: 0;
  transition: opacity 0.24s var(--ease-out);
}

/* A phone gets the tone and not the lens. Two full-width backdrop-filters on
   top of the four the edge at the foot already runs is the frame, not a few
   per cent of it, and what carries the legibility here is the tone — the blur
   is what makes it beautiful, not what makes it readable. */
@media (pointer: coarse) {
  .chromeglass > i {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* --- chrome ---------------------------------------------------------------
   One stacking context, one blend mode. `isolation: isolate` is deliberately
   absent: difference has to reach the canvas underneath to do its job.

   White, and pinned there rather than tracking `--paper`. Difference against
   white returns the inverse of the ground, which is legible over either half
   of a one-bit ribbon; difference against black returns the ground unchanged,
   which is not a blend, it is a no-op. Following the paper colour meant the
   whole chrome — the wordmark, the index, the lockup, the gauge, the crop
   marks — turned black on the frame the run inverted and then subtracted
   nothing from a black sheet. It did not fade. It stopped existing. */
.chrome {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  color: #ffffff;
  mix-blend-mode: difference;
  font-size: var(--t-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-weight: 500;
}

.chrome a,
.chrome button {
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  pointer-events: auto;
}

.rail {
  position: absolute;
  left: var(--pad-edge);
  right: var(--pad-edge);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}

/* Three columns and not a flex row, and the two outer ones are `1fr` each.
   That is what centres the index on the *sheet* rather than in the room left
   over beside the wordmark: equal side columns put the middle one on the
   centre line of the rail, and the rail is already inset by `--pad` on both
   sides, so the centre of the rail is the centre of the screen. No measuring,
   and nothing to keep in step when the wordmark changes length.

   Baseline, not top: the wordmark and the index are both type, and two runs of
   type across the head of a sheet sit on one line. */
.rail--top {
  top: var(--pad-edge);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  /* Load-bearing. A grid item defaults to stretching across its column, and
     the wordmark is a `display: block` anchor with `pointer-events: auto`: at
     the default it becomes an invisible link the width of a third of the
     screen, sitting over the ribbon and eating the drag that starts under
     it. */
  justify-items: start;
}

/* Three columns, the same way the head of the sheet is three columns and for
   the same reason: equal outer columns put the middle one on the centre line of
   the rail, and the rail is already inset by `--pad` on both sides, so the
   centre of the rail is the centre of the screen. The reading is centred on the
   sheet — not in the room left over beside the number, which is a different
   place and moves every time the number changes width. */
.rail--bottom {
  bottom: var(--pad-edge);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  justify-items: start;
}

.rail--bottom > .gauge {
  justify-self: center;
}

.rail--bottom > .social {
  justify-self: end;
}

/* --- wordmark --------------------------------------------------------------
   A name, in sentence case, one weight down from the black it was set in. The
   rule and the roman numeral that used to follow it are gone: a wordmark needs
   to say the name once, and everything after it was furniture. */
/* `.chrome a` — specificity (0,2,0) — hands every link in the layer
   `font: inherit` and `text-transform: inherit`, which is the whole chrome set
   uppercase at the micro size. The wordmark is the one thing in here that is
   neither, so it has to outrank that rule rather than merely follow it:
   `.chrome a.mark` is (0,2,1) and wins wherever it sits in the file. */
.chrome a.mark {
  display: block;
  text-decoration: none;
  /* Medium. Satoshi is loaded at 300/400/500/700/900 and there is no 600 in
     the set, so one step down from Bold is 500 — asking for 600 would land on
     700 again by synthesis and nothing would appear to change. */
  font-weight: 500;
  letter-spacing: -0.02em;
  text-transform: none;
  font-size: clamp(0.86rem, 1.05vw, 1.3rem);
}

/* --- the head of the sheet, as one item -----------------------------------
   The name and the one key that belongs beside it. A wrapper rather than two
   grid items, because the rail's first column holds ONE thing and the key is
   not a second thing at the head of the sheet — it is a mark on the name.

   `baseline` and not `center`: the rail itself is baseline-aligned, and a flex
   box takes its baseline from its first item, so aligning on it here is what
   keeps the wordmark on the rail's line with the key hanging off it rather than
   the pair being centred as a block against the run. */
.lead {
  display: flex;
  align-items: baseline;
  gap: clamp(7px, 0.72vw, 12px);
}

/* --- the line over the run -------------------------------------------------
   The one sentence on the sheet, and the only thing in `.chrome` that is set
   as writing rather than as a label: sentence case, no tracking, and the face
   the works are named in rather than the one the furniture is set in.

   It is hung off the run and not off the viewport. `--zone-top` is written by
   main.js on every resize and is the top edge of the works, in px, exactly as
   the belt just laid them out — so the line keeps its distance from the run at
   any window, instead of drifting into it on a short one and stranding itself
   near the header on a tall one.

   `translateY(-100%)` lifts it clear of that edge by its own height, and the
   clamp under it is the air between the two. */
.creed {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--zone-top, 30vh);
  /* Its own height, then the air, then sixty more. The sixty is written on its
     own rather than folded into the clamp so it stays legible as what it is: a
     nudge off the run, not part of the proportion. */
  transform: translateY(calc(-100% - 60px - clamp(30px, 4.4vh, 76px)));
  margin: 0;
  text-align: center;
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
  /* Forty-five at every width the piece is designed for — 3.08vw only takes
     over under about 1450px, where 45 would be a line of type as wide as the
     run underneath it. */
  font-size: clamp(26.88px, 3.08vw, 44.8px);
  font-weight: 400;
  line-height: 1.16;
  letter-spacing: -0.01em;
  text-transform: none;

  /* --- the component's own frame, undone ------------------------------------
     `<vs-anim-morph-words>` is written for a card: it reserves 340px, holds a
     16/10 box, rounds its corners, hides its overflow and sets its own display
     face at ten rem. Every one of those lives on `:host`, which loses to any
     selector in the host page — which is the whole reason the component puts
     them there. What is left after this is the part that was wanted: two halves
     of a sentence, melting into one another.

     `overflow: visible` is the load-bearing one. The words blur out to twelve
     pixels and scale past their own box on the way out; clipped to the line,
     the effect would be cut off exactly where it is doing its work. */
  display: block;
  min-height: 0;
  aspect-ratio: auto;
  overflow: visible;
  border-radius: 0;
  /* The line box the two halves rotate inside. Tight, because this is a
     headline that already reserves its own line rather than a card. */
  --morph-line: 1.15em;
}

/* --- the index -------------------------------------------------------------
   Six words on the head of the sheet, centred on the sheet.

   The centring is the rail's three columns, not a `left: 50%` and a translate:
   see the note up there. What is here is the run itself, and it carries no
   ground, no box and no separators. It is a line of type at the micro size the
   rest of the chrome is set in, and the only mark on it is where you are.

   `relative` for one reason: nav.js measures each word with `offsetLeft`, and
   that is read against the nearest positioned ancestor. Without it the offsets
   come back against the rail, the rule is placed in one frame of reference and
   sized in another, and it lands a wordmark's width to the left. */
.index {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: clamp(15px, 1.9vw, 30px);
  white-space: nowrap;
  /* One step under the chrome's micro size, and set solid.

     The tracking goes because of what this run is. `--t-micro` carries 0.09em
     because most of what wears it is a lone label — a key cap, a gauge row, a
     chip on a plate — and a word on its own at ten pixels needs the air. Six words in a
     line are not six labels: tracked out they stop being a sentence the eye
     crosses in one movement and become six separate things, each one asking to
     be read on its own. `normal` and not a negative — the face is drawn to be
     set at zero and this is asking for nothing more than that.

     The size and the tracking come off the same decision: both were making the
     run wider than a line of navigation should be at the head of a sheet that
     has a wordmark on one side of it and two controls on the other.

     Set on the nav and not on the items: `.chrome a` hands every link in the
     layer `font: inherit` and `letter-spacing: inherit`, so the words take
     whatever this says without needing to outrank anything. */
  font-size: clamp(0.56rem, 0.66vw, 0.85rem);
  letter-spacing: normal;
  /* Brought in by nav.js on the frame the gate opens — the index is behind the
     gate until then, and a stagger nobody watches is a stagger that did not
     happen. In a class rather than an inline style, so `body.is-open` can
     still take the whole run out from the stylesheet. */
  opacity: 0;
  transition: opacity 0.5s var(--ease-out);
}

.index.is-in {
  opacity: 1;
}

.index__item {
  position: relative;
  display: block;
  text-decoration: none;
  /* The rule lives in this space, and a word is a bigger target with it than
     without. Padding under the type rather than around it: the rail aligns the
     run by its baseline, and there is nothing above the words to push down. */
  padding-bottom: 9px;
  /* The shell's one resting weight — see `--quiet` in `:root`. This was 0.45,
     which is a run of navigation you have to hunt for on a bright frame. */
  opacity: var(--quiet);
  transition: opacity 0.3s var(--ease-out);
}

.index__item:hover,
.index__item:focus-visible,
.index__item[aria-current="page"] {
  opacity: 1;
}

/* `currentColor`, not `--ink`. This layer is `difference` and its colour is
   pinned white; an outline in ink would be black, and difference against black
   is a no-op — the ring would be drawn and would not appear. */
.index__item:focus-visible {
  outline: var(--hair) solid currentColor;
  outline-offset: 5px;
}

/* The honest answer for a word that is printed and not yet bound to anything.
   The pseudo-element only exists while the class is on, so "Soon" is never
   sitting in the accessibility tree of a link that is not saying it. */
.index__item.is-soon::after {
  content: "Soon";
  position: absolute;
  left: 50%;
  top: 100%;
  font-size: 0.78em;
  letter-spacing: 0.14em;
  opacity: 0;
  animation: soon 1.25s var(--ease-out);
}

@keyframes soon {
  0% {
    opacity: 0;
    transform: translate(-50%, -4px);
  }
  18%,
  70% {
    opacity: 0.6;
    transform: translate(-50%, 0);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -3px);
  }
}

/* Where you are, in one pixel.

   It travels on its two edges rather than as one box: `left` and `right` get
   different durations depending on which way it is going, so the leading edge
   arrives first and the line stretches on its way across before gathering up
   under the word it landed on. A translate and a scale cannot do that — they
   are one animation of one box, and both ends arrive together.

   Both durations are written by nav.js on every move; the value here is only
   what it holds while it is being parked. */
/* Where you are, and it is deliberately not the width of the word.

   A rule that matches the word is an underline, and an underline is what a
   link already looks like; the eye reads it as decoration on the type. A short
   bar is not attached to the type at all — it is a mark on the sheet, sitting
   under whichever word the run is currently about, and at 40% of the word it is
   unmistakably shorter than what it points at. That ratio is nav.js's
   `RULE_SHARE` and this rule only draws what it measures.

   AND IT IS FLUSH LEFT, not centred. Centred, the bar is an ornament with the
   word balanced on top of it and the pair reads as one symmetrical unit — safe,
   and invisible. Set against the stem of the first letter with the rest of the
   word running on past it, the same two pixels become a margin mark: something
   put there, by somebody, beside the word rather than under it. See `#edges` in
   nav.js, which is the only place the geometry is decided.

   Two device pixels and fully rounded: at one it is a hairline and a hairline
   with round ends is a hairline, the radius lands inside the antialiasing and
   does nothing. At two the caps are visible and the bar reads as a lozenge. */
.index__rule {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 100%;
  height: calc(var(--hair) * 2);
  border-radius: 999px;
  background: currentColor;
  /* `opacity` is third for `.is-away` below, and it takes whatever duration the
     travel is currently on: nav.js writes `transition-duration` as a two-value
     list and CSS repeats a short list across a longer property list, so the
     fade runs at the same speed as the edge that leads. That is the right
     answer rather than a lucky one — the mark leaving and the mark travelling
     are the same gesture at two amplitudes, and a fade on a fixed clock beside
     a travel on a measured one is the two disagreeing for no reason. */
  transition-property: left, right, opacity;
  transition-timing-function: var(--ease-out);
  transition-duration: 0s;
}

/* Away: the page is on a section this run does not print. There is one — see
   `away` in nav.js, and the note over the run in index.html for why Contact is
   not up here. The mark cannot say where you are, so it stops saying anything;
   left alone it would rest under whichever word you were on last, which is the
   rail claiming you are somewhere you are not.

   It comes back the moment a pointer or a Tab visits a word, because then it is
   pointing at the thing being pointed at rather than making a claim. */
.index.is-away .index__rule {
  opacity: 0;
}

/* --- the path --------------------------------------------------------------
   Where you are when the run of six words cannot say it: inside one of
   twenty-three works, or inside one of eleven services.

   IT HAS ITS OWN LINE, and it is the second half of a change whose first half
   went in alone.

   The two used to trade one slot: `grid-area: 1 / 2` on both, the run fading
   out on the way into a work and the path rising in the same place. Then the
   run was told to STAY — a reader inside a work who wants Services should not
   have to leave the work to find the word; see `body.is-open .index` up in the
   shell. That decision was made and the trade was never undone, so both ended
   up in the same cell with both at full strength, and the head of the sheet
   printed the run and the path ON TOP OF EACH OTHER: 'Services' and 'Ventures'
   with 'Work / Vuesax' struck through them. Two answers in one line of type,
   which reads as a doubled menu because that is what it is.

   The path is the one that moves, and only the path. Out of the grid, absolute
   against `.rail--top`, one line under it. The run keeps the cell — which
   matters more than it looks: the middle column is `auto`, so the run is what
   MEASURES it. When both were taken out of flow together the column came back
   at zero, the grid collapsed to `1fr 0 1fr`, and the words hung under the
   wordmark on a line of their own. One of the two has to stay in, and it is
   the one that is always there.

   `left: 0; right: 0` with the line centred inside it, rather than a box the
   width of its own contents: the rail is inset by `--pad-edge` on both sides,
   so its full width is centred on the sheet by construction and the path is
   centred on the sheet whatever is in it. Nothing to measure, and the leaf can
   still run out of room and truncate against a real edge.

   Two rows, two meanings — where you can go, and where you are. */
.index {
  grid-area: 1 / 2;
  justify-self: center;
  z-index: 1;
}

.crumbs {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.5em);
  z-index: 1;
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 0.62em;
  white-space: nowrap;
  /* The run's size, not the chrome's. The two are no longer in one slot, but
     they are still one readout in two rows, and a path set at a different size
     from the run above it is a second voice rather than a second line. */
  font-size: clamp(0.56rem, 0.66vw, 0.85rem);
  letter-spacing: normal;
}

.crumbs[hidden] {
  display: none;
}

/* The section you are in, and the way back to it. Held at the run's resting
   strength — the words in the index that are not current sit at 0.45, and this
   is one of them, printed somewhere else. */
/* Neither of the first two ever gives up a pixel. When the line is wider than
   the room it has, the part that truncates is the name at the end of it — the
   section is the half that is a control and a control with three letters
   missing is a control nobody presses. */
.chrome a.crumbs__root,
.crumbs__sep {
  flex: 0 0 auto;
}

.chrome a.crumbs__root {
  display: block;
  text-decoration: none;
  text-transform: inherit;
  opacity: 0.45;
  transition: opacity 0.3s var(--ease-out);
}

.chrome a.crumbs__root:hover,
.chrome a.crumbs__root:focus-visible {
  opacity: 1;
}

.crumbs__root:focus-visible {
  outline: var(--hair) solid currentColor;
  outline-offset: 5px;
}

/* The stroke between them, at the weight the counts use it at: `01 / 23` and
   `01 / 11` both set the divider well under the figures either side of it, and
   this is the same mark doing the same job on words. */
.crumbs__sep {
  font-style: normal;
  opacity: 0.3;
}

/* The clip the name rises through, the way the contents page's words do and
   the way the wordmark does on the gate. `overflow: hidden` on a line of type
   clips descenders, so the box is opened by the leading and the glyph is
   pushed back up by the same amount — the clip is then on the ascender line
   and the tail of a `y` is inside it. */
.crumbs__leaf {
  display: block;
  overflow: hidden;
  /* A flex item will not shrink below the width of its own content unless it
     is told it may, and without this the line simply runs off the sheet
     instead of ending in an ellipsis. */
  flex: 0 1 auto;
  min-width: 0;
  padding-bottom: 0.3em;
  margin-bottom: -0.3em;
  text-overflow: ellipsis;
}

.crumbs__leaf > i {
  display: block;
  font-style: normal;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- the lockup ------------------------------------------------------------
   One slot now: which work of how many. Sized here, populated by labels.js. */
/* A row now, not a column: the control and the number are one line of filing
   at the foot of the sheet, and the button is what the line starts with. */
.lockup {
  display: flex;
  align-items: center;
  gap: clamp(10px, 0.9vw, 15px);
  max-width: 62vw;
}

/* --- the track's control ---------------------------------------------------
   The dial in the crown, in the opposite corner and in the other layer. Same
   circle, same hairline, same nothing inside it but the state.

   `currentColor` and not `--ink`: this one is in `.chrome`, which is white and
   rides `difference`. The ring is mixed toward transparent for the reason the
   dial's is — it is a boundary, not a mark, and the thing carrying the state is
   what is inside it. */
.sound {
  display: grid;
  place-items: center;
  flex: none;
  /* The circle IS the dock — see `--dock` at `:root`. Written as the token and
     not as the clamp it used to be, because the foot of the landing measures
     itself against this height and a second copy of the numbers is a second
     thing to remember to change. */
  width: var(--dock);
  height: var(--dock);
  padding: 0;
  border: var(--hair) solid var(--ring);
  border-radius: 50%;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    border-color 0.4s var(--ease-out),
    transform 0.5s var(--ease-pop-soft);
}

.sound:hover {
  border-color: var(--ring-hi);
}

.sound:active {
  transform: scale(0.93);
}

.sound:focus-visible {
  outline: var(--hair) solid currentColor;
  outline-offset: 4px;
}

/* Four bars, and the box they stand in is a fixed height so the row does not
   change size as they move — they scale inside it. */
.sound__bars {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 42%;
}

.sound__bars > i {
  display: block;
  width: 2px;
  height: 100%;
  border-radius: 999px;
  background: currentColor;
  transform-origin: center;
  /* At rest: level, low, and unmistakably still there. A control that empties
     out when it is off is a control that has stopped saying what it is. */
  transform: scaleY(0.26);
  transition: transform 0.45s var(--ease-out);
}

/* Playing. Four durations that do not divide into each other, so the four bars
   never come back into step and the pattern does not loop visibly — the same
   trick the ribbon's wave uses at a different scale. */
.sound.is-on .sound__bars > i {
  animation: eq 0.62s var(--ease-out) infinite alternate;
}

.sound.is-on .sound__bars > i:nth-child(2) {
  animation-duration: 0.93s;
  animation-delay: -0.31s;
}

.sound.is-on .sound__bars > i:nth-child(3) {
  animation-duration: 0.71s;
  animation-delay: -0.12s;
}

.sound.is-on .sound__bars > i:nth-child(4) {
  animation-duration: 1.09s;
  animation-delay: -0.47s;
}

@keyframes eq {
  from {
    transform: scaleY(0.22);
  }
  to {
    transform: scaleY(1);
  }
}

/* --- where else this is ----------------------------------------------------
   Six marks, no words. They are drawn in `currentColor` inside a layer that is
   white and blended with `difference`, which is the whole of the black-on-paper
   / white-on-ink behaviour: there is no second palette and nothing to keep in
   step when the sheet turns over.

   The box is bigger than the mark on purpose. What the gap between two of these
   has to clear is not the ink, it is the hit area — and a fourteen-pixel glyph
   with a thirty-pixel target around it reads as air rather than as a row of
   buttons pressed together. */
.social {
  display: flex;
  align-items: center;
  gap: clamp(2px, 0.22vw, 4px);
}

.social__item {
  display: grid;
  place-items: center;
  /* The box is the hit area and the gap is the air, and the two are read as one
     distance: what separates two marks on screen is the gap plus whatever room
     each box has around its own glyph. Halving the air meant taking it out of
     both — cutting only the gap would have moved the marks a fifth closer and
     left the boxes doing most of the spacing.

     The floor is 26px and it is not a design number: it is the smallest target
     a pointer can be asked to hit without the row becoming a dexterity test. */
  width: clamp(26px, 2vw, 28px);
  height: clamp(26px, 2vw, 28px);
  border-radius: 50%;
  /* The shell's one resting weight — see `--quiet` in `:root`, which is the
     number the run at the head of the sheet is set at too. This was 0.42, a
     private second opinion about how quiet the shell should be, and it was
     doing the job a smaller mark should have done: it pushed the row back by
     making it faint, which over a photograph or a bright frame of the run is
     the same as not printing it. Hover still takes it to full. */
  opacity: var(--quiet);
  /* Two properties, two clocks. js swaps the durations as the pointer arrives
     and leaves — short and linear on the way in so the mark sits under the
     cursor, long and overshooting on the way out so letting go is elastic. */
  transition:
    transform 0.62s var(--ease-pop-soft),
    opacity 0.35s var(--ease-out);
}

.social__item:hover,
.social__item:focus-visible {
  opacity: 1;
}

.social__item:focus-visible {
  outline: var(--hair) solid currentColor;
  outline-offset: 2px;
}

/* One size for the six, and one correction.

   Five of these marks fill their 24-unit box in both directions. Behance is a
   wordmark: two letters, 24 across and under 16 tall, so at the same width it
   carries a third less ink than the circle next to it and reads smaller than
   everything it is standing in a row with. The bump is optical, not metric —
   what is being matched is how big the mark looks, not how wide its box is.

   As a factor on the shared size rather than a second size, so there is still
   one number to change when the row is resized. The box it sits in does not
   move, so nothing in the spacing shifts with it. */
.social__mark {
  display: block;
  width: calc(clamp(13px, 1.05vw, 16px) * var(--mark, 1));
  height: auto;
  fill: currentColor;
}

.social__item[aria-label="Behance"] .social__mark {
  --mark: 1.15;
}

/* A rolled line is a row of per-character cells. Tabular, so a digit changing
   cannot change the width of the line it is in — which is what lets a single
   cell animate while the rest of the row is not so much as re-rendered.

   Nothing here clips. A mask would make this an odometer, and an odometer is a
   mechanism; what a glyph does instead is thin out as it leaves and resolve as
   it arrives, which is the same liquid the ribbon underneath is made of. It
   also keeps the row's baseline where the browser put it — a box with hidden
   overflow reports its own bottom edge as its baseline, and the year would sit
   half a line off the word beside it. */
.roll {
  display: inline-block;
  white-space: pre;
  font-variant-numeric: tabular-nums;
}

.roll__cell {
  position: relative;
  display: inline-block;
}

/* In flow, invisible: this is what the cell is as wide as, so a glyph in the
   middle of its travel cannot move the ones either side of it. */
.roll__ghost {
  visibility: hidden;
  white-space: pre;
}

/* Laid exactly over the ghost — same face, same line — and the only thing in
   here that is ever animated. */
.roll__glyph {
  position: absolute;
  left: 0;
  top: 0;
  white-space: pre;
  will-change: transform, opacity, filter;
}

/* --- gauge ---------------------------------------------------------------- */
/* --- the fusion reading ----------------------------------------------------
   The digits sit on the end of the rule, flush right, so the figure and the
   fill run out on the same edge. The label and the year that used to bracket
   them are gone: the bar already says what the number is a reading of, and the
   year is on the work's own cover. */
.gauge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  font-variant-numeric: tabular-nums;
  min-width: 148px;
}

.gauge__val {
  display: block;
  text-align: center;
  font-weight: 700;
  letter-spacing: 0.06em;
  /* Wide enough for 0.000 at every digit, so the figure does not shuffle
     sideways as it counts — tabular numerals hold the glyph width, not the
     box. */
  min-width: 4.4em;
}

/* Taller than the hairline it was: at 3px the fill was a line moving inside a
   line, and the two edges read as one thick rule rather than as a level.

   A capsule, and not a rectangle with the corners taken off: the radius is
   larger than the bar is tall, so the two ends are half-circles at any height
   the bar is ever set to and there is no number in here to keep in step with
   one. */
.gauge__bar {
  width: 148px;
  height: 8px;
  border: var(--hair) solid currentColor;
  border-radius: 999px;
  padding: 0;
  position: relative;
  /* `--live` is written by hud.js off the belt's own speed: a fifth of it while
     the run is still, all of it while it is moving. No transition — the number
     arriving here has already been smoothed by the belt, and easing an eased
     value twice is what makes a readout feel like it is lagging the thing it is
     supposed to be reading. */
  opacity: calc(0.2 + 0.8 * var(--live, 0));
  /* What rounds the fill. The fill is scaled, and a radius on a scaled box
     comes out as an ellipse — squashed to a fifth of itself at a fifth of the
     travel. Clipped by the track instead, the ends are the track's own ends,
     which are exactly round, and the only straight edge left is the level
     itself. */
  overflow: hidden;
}

.gauge__bar > i {
  position: absolute;
  /* Flush to the track's inside edge, and carrying no radius of its own. A
     radius here would be the ellipse the note above describes; taken from the
     clip instead, the left end is a true half-circle at 0.001 and at 1.000
     alike, and the only edge that is ever straight is the one doing the
     reading. */
  inset: 0;
  background: currentColor;
  transform-origin: left center;
  transform: scaleX(0);
}

/* --- the crown ------------------------------------------------------------
   Outside `.chrome`, and that is the whole reason it is its own layer: that
   one rides `mix-blend-mode: difference`, and difference against black returns
   the ground untouched — a solid black pill in there subtracts nothing and is
   simply absent. These take their ink from `--ink`/`--paper` instead, so the
   button is black on paper and flips to white on the frame the sheet does. */
.crown {
  position: fixed;
  top: var(--pad-edge);
  right: var(--pad-edge);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.2vw, 18px);
  pointer-events: none;
}

.crown > * {
  pointer-events: auto;
  /* All three wait behind the load screen and arrive with it — see
     `body.is-gate .crown > *` beside `.gate`. Declared here and not there so
     the curve is on the controls rather than on one state of them, and the
     property is `opacity` only: nothing in the corner that MOVES is being asked
     to transition, which matters because the dial's half-disc is turned frame
     by frame off a clock in `main.js` and a transition on it would be a second
     opinion about where it is. */
  transition: opacity 0.5s var(--ease-out);
}

/* The crown does not step aside for an opened work any more — the dial and
   Contact are shell, and they are re-inked for the photograph up where the
   rest of that argument is. Which layer it rides while a work or a book is
   open is settled in one place: the z-order block at the foot of this file. */

/* The key to the contents page used to be the one piece of shell that went,
   on the argument that a way into the index from inside a work is a way out
   dressed as a way in. It stays now, with everything else: the index it opens
   is the site's table of contents, and a reader deep inside a work is exactly
   the reader most likely to want it. Nothing in the shell is conditional on
   where you are — see the note on the run above. */

/* --- invert, as a dial -----------------------------------------------------
   What is left of a checkbox, the word "INVERTIR" and a key cap. A disc half
   filled is the whole state: which half is inked is which way up the sheet
   is, and turning it over is the same gesture the page performs. */
.theme {
  display: grid;
  place-items: center;
  width: clamp(34px, 3vw, 42px);
  height: clamp(34px, 3vw, 42px);
  padding: 0;
  /* The ring is `--ring`, which is the track's button's ring, which is the
     key's ring. One number, in `:root`, read by all three — see the note there.

     The reference was always the control at the foot of the sheet, and the
     mistake was reading "make it as clear as that one" as "make it heavier
     than that one". It went 10% → 38% → the full ink, and full ink is a
     BLACK circle drawn around a black half-disc on a white sheet: two marks of
     the same weight, an eighth of an inch apart, competing for the same glance.
     The reader sees a donut. What they are supposed to see is a dial with the
     sheet's state in it, inside a boundary that says where the control ends.

     A quarter of the ink is that boundary, and the mark inside it is untouched
     — the half-disc is `currentColor`, at full strength, exactly the way the
     track's four bars are. Pale line, solid mark, in both directions: the
     colour is `--ink`, so it is a dark hairline with a dark dial on the pale
     sheet and a light hairline with a light dial on the dark one. Nothing in
     the crown is told which side is up; it reads the one pair the whole site
     reads and turns over with it.

     Over a photograph the crown is handed white and a tight drop-shadow —
     `body.is-open.is-flooded .crown`, up beside the sheet — which is the same
     protection `.chrome` gets in the same state, applied to the same problem.
     That is where a ring over an unknown ground is answered. It was never
     answerable here, by a number: an opacity is a contrast against a ground
     you know, and cranking it only ever traded a control that vanished over a
     sky for a control that shouted on the sheet.

     No halo. There were two `--paper` drop-shadows under this for one revision
     and a one-pixel circle inside a seven-pixel glow is not a circle with a
     glow, it is a smudge with a circle somewhere in it. */
  border: var(--hair) solid var(--ring);
  border-radius: 50%;
  background: none;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* The crease crosses this control on its way past, and the control is a piece
     of the same sheet, so it swells and leans exactly the way the works do —
     the same crest, the same lens, the same numbers, read at this element's own
     centre by main.js. Written per frame, and only while a turn is in the air.

     Independent transform properties on purpose. The transition below names
     `transform` and nothing else, so these two are not covered by it and land on
     the frame they are written, which is what a per-frame value wants. It also
     keeps the note below true to the letter: nothing that carries `--ink` or
     `--paper` is eased here, and neither of these carries either. `:active`'s
     scale composes after them and still reads as a press. */
  translate: calc(var(--fold-px, 0) * 1px) calc(var(--fold-py, 0) * 1px);
  scale: calc(1 + var(--fold-crest, 0) * 0.07);
  /* Transform only, and nothing that carries `--ink` or `--paper`.
     These two flip past each other when the sheet inverts — the ground goes
     black to white while the type goes white to black — so easing them crosses
     both through mid-grey at the same instant and the control disappears in
     the middle of its own transition. Eased or not they would still cross;
     the answer is not to ease. Colour snaps on the frame the sheet turns over,
     which is also the frame it should.

     OPACITY IS EASED, and it is a different property answering a different
     question: the corner arrives with the cover — see `.crown > *` and
     `body.is-gate .crown > *` — and a shorthand that lists transform alone
     throws that away, because a shorthand replaces rather than merges. That is
     how the fade the stylesheet already declared had been dead for a while,
     silently, on all three of the corner's controls. */
  transition:
    transform 0.5s var(--ease-pop-soft),
    opacity 0.5s var(--ease-out),
    border-color 0.4s var(--ease-out);
}

.theme__dial {
  position: relative;
  display: block;
  width: 15px;
  height: 15px;
  border: var(--hair) solid currentColor;
  border-radius: 50%;
  overflow: hidden;
}

/* Half the disc, inked, rotated rather than re-clipped. A clip-path swapped
   between two shapes jumps; the same shape turned over is the sheet flipping.

   Driven from main.js, not from `aria-pressed`, and not eased here.
   `--fold-turn` is the same scalar the shader is handed — the sheet's own
   progress, already on its own curve — so the control and the material perform
   one gesture on one number, instead of a `0.62s` in a stylesheet hand-matched
   to a `0.62` in a loop with nothing in the code linking them. It also buys
   what a transition cannot: caught mid-turn the crease falls back the way it
   came, and so does this. A transition keyed off the attribute only knows where
   the turn ended up.

   The attribute is still written and still carries the state for anything that
   reads this control rather than looks at it. The rule that used to draw the
   rotation from it is gone on purpose — two things writing one rotation is one
   of them losing, silently, on the frames that matter. */
.theme__dial::after {
  content: "";
  position: absolute;
  inset: -1px;
  background: currentColor;
  clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
  rotate: calc(var(--fold-turn, 0) * 180deg);
}

/* THE RING LIFTS. IT DOES NOT FILL IN.

   This used to flood the whole control with `--ink` and flip the mark to
   `--paper` on hover, which over a photograph is a black disc appearing in the
   corner — the loudest thing on the first screen, produced by the mouse
   passing near it. It is also the one control on this site that answered a
   hover that way.

   The reference is the sound control at the foot of the sheet, which is the
   same object in the other corner: same circle, same hairline, same nothing
   inside it but the state. It answers a hover by taking its ring from `--ring`
   to `--ring-hi` and touching nothing else. Same here, on the same two tokens,
   at the same duration — the boundary gets clearer, the mark it is drawn
   around does not move, and nothing on the corner changes area.

   `border-color` is added to the transition rather than replacing it: the
   shorthand already carries the fold's transform and the corner's arrival, and
   a shorthand REPLACES: `border-color` is named in the one `transition` this
   control has, up beside the other two, and not in a second rule down here. */
.theme:hover {
  border-color: var(--ring-hi);
}

.theme:active {
  transform: scale(0.93);
}

/* The sound control's outline, exactly: a hairline in the control's own colour,
   standing off far enough to read as a focus ring and not as a second border. */
.theme:focus-visible {
  outline: var(--hair) solid currentColor;
  outline-offset: 4px;
}
/* --- contact ---------------------------------------------------------------
   The one thing in the corner that ASKS rather than shows, and since Contact
   came out of the run at the head of the sheet, the only way into the section
   it names.

   It is a `<vs-button-chrome>` — the same custom element the send button at the
   foot of the contact form is. That is the whole argument for it: the control
   that STARTS the conversation and the control that SENDS it are one object in
   two places, and the reader meets the second one already knowing what it is.

   Everything below is TOKENS, and that is deliberate. The component reads its
   own dimensions, its plate and its label ink off custom properties with
   literal fallbacks baked in, so the corner tells it what the sheet is and the
   component decides what to do about it. Nothing here reaches into the shadow
   root except the two `::part()` rules at the foot, which are for the two
   typographic facts the host cannot pass down.

   What it replaced was a solid ink pill with a small dot on the end. The pill
   was the only filled shape in the chrome, which was its argument; the dot was
   a mark standing in for an arrow, which is a promise of navigation this
   control does not make. Both are gone. The box is transparent now, so the run
   and the photographs keep going underneath, and the rim IS the control — which
   is the reason it survives a corner this piece cannot re-ink. A hairline of
   `--ink` is a fraction of a colour and dies on the ground it does not know
   about; the alloy is light and dark at once, at every frame, on every ground.
   The dial next to it needs a halo for the same problem. This one does not. */
.contact {
  /* the component's own size scale, given the crown's numbers — the same
     clamps the two discs beside it are on, so the three are one row */
  --ctrl-h-md: clamp(34px, 3vw, 42px);
  --ctrl-px-md: clamp(16px, 1.5vw, 24px);
  --ctrl-fs-md: var(--t-micro);
  /* The plate it floods with on hover and the ink the label flips to: the
     sheet's own pair, in that order, so the fill turns over with the page and
     the label is always the exact opposite of what it is sitting on. Without
     these the component falls back to its own white-on-dark defaults, which is
     right on the dark sheet and inverted on the paper one. */
  --chrome-fill: var(--ink);
  --chrome-fill-ink: var(--paper);
  /* The label at rest, and the focus ring. Both are `--ink`, so the resting
     word is the sheet's type colour rather than the component's default grey. */
  --inp-text: var(--ink);
  --inp-accent: var(--ink);
  /* THE PLATE, and it is the one thing in here that is not the component's
     idea. `vs-button-chrome` keeps its box transparent on purpose — the rim is
     the whole control — and that is right for the foot of a form, where the
     ground behind it is a sheet of one colour and the word on it is set at body
     size. It is wrong here. This corner sits over the RUN: twenty-three
     photographs moving under it, and the word is ten pixels of tracking-out
     capitals. Ink on an unknown ground at that size is a coin toss, and it came
     up tails — the rim survived a photograph, which is what the alloy is for,
     and the label did not.

     The ground is painted on the HOST rather than inside the component, which
     is what makes this two lines and not a fork of the file: the host's own
     background paints behind the shadow tree, and the shadow tree is
     transparent, so the plate simply shows through it. The rim is still drawn
     on top of it and still the border of the control.

     `--paper` and not `--ink`, and that is the whole state machine: the pill
     rests as the sheet's GROUND with the sheet's type on it, and floods to the
     sheet's INK with the ground's colour on it when you point at it. Two
     colours, no third, turning over with the sheet — the same pair the old
     solid pill used, in the other order, so pointing at it now reads as the
     shape inverting rather than as a shape getting lighter.

     The radius has to be here as well. The box the plate paints is the HOST's,
     and the host has never heard of `radius="pill"` — that attribute is a class
     inside the shadow root. Without it the plate is a rectangle behind a pill. */
  background: var(--paper);
  border-radius: 999px;
  /* Transform AND opacity, and still not colour. The two reasons are separate
     and both belong on this line: the ground and the type swap PAST each other
     when the sheet inverts, so easing colour meets in the middle at no contrast
     at all — see the long note on `.theme`; and the corner fades in as the
     cover opens, which is `.crown > *` up there and was being silently thrown
     away by every control that redeclared `transition` with transform alone.
     A shorthand replaces, it does not merge. */
  transition:
    transform 0.5s var(--ease-pop-soft),
    opacity 0.5s var(--ease-out);
}

/* The two facts the host cannot hand down. The component sets `font-weight` and
   its own tracking INSIDE the shadow root, and an inherited value loses to a
   declaration — so the crown's type has to be written on the part itself or the
   word comes out at the component's 500 instead of the chrome's 700. The face
   is not in here because `font-family: inherit` is, and that one does reach. */
.contact::part(button) {
  font-weight: 700;
  letter-spacing: 0.06em;
}

.contact:hover {
  transform: translateY(-2px);
}

.contact:active {
  transform: translateY(0) scale(0.97);
}

/* --- the key to the contents page ------------------------------------------
   The same disc as the invert dial, because it is the same kind of thing: a
   control in the crown, carrying its own ink, that turns over with the sheet.
   Two rules that fold into a cross — the index is a run of rules, and this is
   the index at the size of a button.

   Off screen entirely above the breakpoint: up there the five words are on the
   head of the sheet already, and a key to something in plain sight is a second
   door into the same room. */
.menu {
  display: none;
  place-items: center;
  width: clamp(34px, 3vw, 42px);
  height: clamp(34px, 3vw, 42px);
  padding: 0;
  /* The dial's ring, because it is the dial's ring: one token, in `:root`,
     read by this, by the dial beside it and by the track's button at the foot
     of the sheet. They are one set of controls and a set drawn at three weights
     is not a set — which is exactly how this one got out of step twice, each
     time by somebody editing the strength in one of three places. There is one
     place now. */
  border: var(--hair) solid var(--ring);
  border-radius: 50%;
  background: none;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Transform AND opacity, and still not colour. The two reasons are separate
     and both belong on this line: the ground and the type swap PAST each other
     when the sheet inverts, so easing colour meets in the middle at no contrast
     at all — see the long note on `.theme`; and the corner fades in as the
     cover opens, which is `.crown > *` up there and was being silently thrown
     away by every control that redeclared `transition` with transform alone.
     A shorthand replaces, it does not merge. */
  transition:
    transform 0.5s var(--ease-pop-soft),
    opacity 0.5s var(--ease-out);
}

.menu__bars {
  position: relative;
  display: block;
  width: 15px;
  height: 15px;
}

.menu__bars i {
  position: absolute;
  left: 0;
  right: 0;
  height: var(--hair);
  background: currentColor;
  transition: transform 0.52s var(--ease-snap);
}

.menu__bars i:first-child {
  top: 4px;
}

.menu__bars i:last-child {
  top: 10px;
}

/* Both rules travel to the middle of the box and cross there. */
body.is-contents .menu__bars i:first-child {
  transform: translateY(3px) rotate(45deg);
}

body.is-contents .menu__bars i:last-child {
  transform: translateY(-3px) rotate(-45deg);
}

.menu:hover {
  background: var(--ink);
  color: var(--paper);
}

.menu:active {
  transform: scale(0.93);
}

.menu:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* The crown has to clear the contents page while it is up — the key that
   closes it is in there. Not permanent: the gate is at 4 and has to cover the
   crown for the length of the load, so this only holds while the sheet is
   open. The rule itself is at the foot of this file with the other two that
   move the crown, because the sheet can be opened from inside the services
   section and that section's `6` is written after this point. See the z-order
   block below. */

/* --- the contents page -----------------------------------------------------
   What the index becomes when the head of the sheet runs out of room.

   A full-bleed ground of its own, in paper and ink, so it turns over with the
   sheet like the crown does. `visibility` and not `display`, so the rows have
   a transition to run and `inert` has something to keep a Tab out of. */
/* --- the z-order, in one place ---------------------------------------------
   Every layer that can be on screen at the same time as another one, from the
   bottom up. It is written out here because the shell changed the shape of the
   problem: the chrome used to sit under every full-bleed ground on the page —
   it was hidden whenever one was up — and now it has to sit on top of them.

     0  the ribbon's sheet, the opened work's flood
     1  the probe
     2  the filing plates, the glass edge, the chrome — in that order
     3  the crown, the way out of a work
     4  the tag
     5  the services column
     6  the glass edge and the chrome and the crown, over the column
     7  the book
     8  the glass edge, the chrome, the crown and the way out, over a work
     9  the contents page
    10  the crown, over the contents page — it holds the key that closes it
    11  the gate — not a layer in this order so much as what is in front of all
        of it. It was at 4, which was safe only while nothing could be open
        underneath a load screen; the piece arrives on the landing now, and a
        section is a 5 that lifts the shell to 6. See `.gate`.

   Three of those lines put several things on ONE number, and that is the
   mechanism rather than an accident: siblings at the same layer paint in
   document order, so `.plates` and `.chrome` are ordered by where they are
   written in the markup, and `.crown` and `.opened` are both written after
   the chrome so they clear it without a number of their own.
   A control that must be above the edge is moved in the markup; there is no
   number inside a stacking context that reaches outside it — which is the rule
   the column's own way out kept breaking before it was removed altogether.

   The rules that move the chrome, the crown and the edge are next to the
   sections they are moving over, and each carries a pointer back here. */
.contents {
  position: fixed;
  inset: 0;
  z-index: 9;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(var(--pad) * 2.6) var(--pad) calc(var(--pad) * 2);
  background: var(--paper);
  color: var(--ink);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.42s var(--ease-out),
    visibility 0s linear 0.42s;
}

body.is-contents .contents {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

.contents__list {
  display: flex;
  flex-direction: column;
  gap: clamp(2px, 0.9vh, 12px);
}

.contents__item {
  display: flex;
  align-items: baseline;
  gap: clamp(12px, 3vw, 24px);
  padding: 5px 0;
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.contents__num {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  opacity: 0.32;
  transition: opacity 0.3s var(--ease-out);
}

.contents__item[aria-current="page"] .contents__num,
.contents__item:hover .contents__num {
  opacity: 1;
}

/* The clip each word rises through, the way the wordmark does on the gate. */
.contents__word {
  display: block;
  overflow: hidden;
  font-size: clamp(2.1rem, 11vw, 4.2rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.12;
  opacity: 0.5;
  transition: opacity 0.3s var(--ease-out);
}

.contents__item[aria-current="page"] .contents__word,
.contents__item:hover .contents__word {
  opacity: 1;
}

/* `transform` carries the entrance and `translate` carries the hover, because
   they are separate properties and can therefore be on separate clocks. One
   `transform` for both would mean the hover cancelling the rise it is sitting
   in the middle of. */
.contents__word > i {
  display: block;
  font-style: normal;
  transform: translateY(110%);
  transition:
    transform 0.72s var(--ease-out),
    translate 0.5s var(--ease-out);
  /* Struck one after another, off the row's own index. */
  transition-delay: calc(var(--i) * 52ms), 0s;
}

body.is-contents .contents__word > i {
  transform: translateY(0);
}

.contents__item:hover .contents__word > i,
.contents__item:focus-visible .contents__word > i {
  translate: 10px 0;
}

.contents__item.is-soon .contents__num::after {
  content: " Soon";
  opacity: 0;
  animation: soon-flat 1.25s var(--ease-out);
}

@keyframes soon-flat {
  0% {
    opacity: 0;
  }
  18%,
  70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

.contents__item:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 6px;
}

/* --- probe ---------------------------------------------------------------- */
.probe {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: difference;
  /* Pinned white for the same reason the chrome is: see above. */
  color: #ffffff;
  transform: translate3d(-100px, -100px, 0);
  display: flex;
  align-items: center;
  gap: 9px;
  /* Two handovers, multiplied rather than fought over.

     `--vis` is the disc's. hud.js writes it every frame, and it is exactly the
     amount the lens over a work has *not* arrived by: two cursors on one
     pointer is one cursor too many.

     `--probe-on` is the browser's. Over a control the native cursor is already
     showing a hand, and a crosshair drawn on top of that is the same collision
     a second time — the hand is what says "this is a thing you can press", and
     nothing painted over it says it better. So the readout leaves and the
     pointer the reader already knows how to read is left alone.

     Multiplied rather than overridden, so neither handover has to know the
     other exists. The fade is short enough to land with the cursor changing
     shape rather than after it. */
  opacity: calc(var(--vis, 1) * var(--probe-on, 1));
  transition: opacity 0.12s linear;
}

/* Any anchor, any button, anywhere in the document. `:has` on the body rather
   than a list of the controls this piece happens to have today: whatever is
   added tomorrow is covered by having been written as a link or a button,
   which it was going to be anyway. */
body:has(a:hover),
body:has(button:hover) {
  --probe-on: 0;
}

.probe__cross {
  position: relative;
  width: 15px;
  height: 15px;
  margin: -7.5px 0 0 -7.5px;
  transform: rotate(var(--spin, 0deg)) scale(var(--pop, 1));
  transition: transform 0.4s var(--ease-out);
}

.probe__cross::before,
.probe__cross::after {
  content: "";
  position: absolute;
  background: currentColor;
}

.probe__cross::before {
  left: 0;
  right: 0;
  top: 50%;
  height: var(--hair);
}

.probe__cross::after {
  top: 0;
  bottom: 0;
  left: 50%;
  width: var(--hair);
}

body.is-coarse .probe {
  display: none;
}

/* --- the cursor tag --------------------------------------------------------
   Over a work the pointer is replaced rather than annotated: the crosshair
   goes out and a lens the size of a coin takes its place, centred on the spot
   the tap will land.

   Everything about how it moves is in js — position, the trail it runs at, the
   ellipse it stretches into along its own velocity, the spring it lands on.
   None of that is expressible as a transition, because a transition has to
   know its destination and this one is chasing a pointer. What CSS owns here
   is the material: the lens, the rim, the tint. */
.tag {
  /* A wrapper with no transform of its own, deliberately.
     A backdrop-filter samples what is painted behind it, and a transform on an
     ancestor can promote the subtree into a layer that no longer has the page
     behind it — the glass then refracts nothing and comes out flat. So the
     disc carries its own position, in `translate`, which is a property
     separate from the `rotate` and `scale` the motion needs. */
  position: fixed;
  left: 0;
  top: 0;
  z-index: 3;
  pointer-events: none;
  --tag-size: clamp(70px, 6.16vw, 103px);
}

.tag__disc {
  position: fixed;
  left: 0;
  top: 0;
  width: var(--tag-size);
  height: var(--tag-size);
  /* Pulled back by half its own size, so js can translate to the raw pointer
     and the disc lands centred on it without ever measuring itself. */
  margin: calc(var(--tag-size) * -0.5);
  border-radius: 50%;
  display: grid;
  place-items: center;
  will-change: translate, rotate, scale, opacity;
  translate: -300px -300px;
  scale: 0;
  opacity: 0;
  /* The rim, taken down to a fifth of the shared recipe's.
     At 40% white a hairline reads as an *edge* on a pill, which is what the
     glass block was written for. On a disc this size it reads as a ring drawn
     around the glass — a border on a lens, which is the one thing a lens does
     not have. What is left is enough to keep the circle from dissolving into a
     dark photograph and not enough to be seen as a border. The same goes for
     the bevel: on a circle `inset 0 1px 0` is a bright arc across the top, and
     at 42% it would have outshone the rim it is supposed to sit under. */
  border-color: rgba(255, 255, 255, 0.08);
  /* The cast shadow at a fifth of the shared recipe's. A coin-sized lens
     dropping a 30% shadow onto a photograph reads as a sticker sitting above
     the page; what the disc needs from it is the hair of separation that keeps
     it from dissolving into a dark ground, and that survives the cut. The
     bevel is not a shadow — it is the light on the rim — and is left alone. */
  box-shadow:
    0 14px 34px rgba(0, 0, 0, 0.06),
    inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

/* The lens highlight. Not decoration — it is what keeps the disc readable when
   the ribbon underneath it is solid ink. The smoked backdrop darkens whatever
   it is handed, and black darkened is still black: without this the disc over
   an inverted run would be a rim and nothing else. */
.tag__lens {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(115% 115% at 30% 20%, rgba(255, 255, 255, 0.26), rgba(255, 255, 255, 0.05) 44%, rgba(255, 255, 255, 0) 72%),
    radial-gradient(90% 90% at 72% 88%, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0) 60%);
  pointer-events: none;
}

/* One ring outside the rim, raised on press. The disc is the cursor, so it has
   no hover state to fall back on and the click has to answer somewhere. */
.tag__ring {
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: var(--hair) solid rgba(255, 255, 255, 0.55);
  opacity: var(--halo, 0);
  scale: var(--halo-s, 0.9);
  pointer-events: none;
}

.tag__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* Undoes the disc's own rotation, so the ellipse leans into the direction of
     travel and the word stays upright inside it. Fed the same angle js just
     wrote, negated, and unwrapped rather than normalised — a value that
     wrapped at ±180° would spin the word once per crossing. */
  rotate: var(--counter, 0deg);
  font-size: clamp(0.5rem, 0.6vw, 0.72rem);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* The word is one letter narrower on the right than the tracking implies. */
  text-indent: 0.18em;
}

.tag__arrow {
  width: 12px;
  height: 12px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Over a work that is still coming there is nowhere to go, so the arrow that
   means *in* comes off and the word stands alone. See `tagSoon` in hud.js. */
.tag.is-soon .tag__arrow {
  display: none;
}

/* The displacement map's own svg. In the DOM because a filter has to be, and
   invisible because it is a map and not a picture. */
.glass__filter {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
}

body.is-coarse .tag,
body.is-open .tag {
  display: none;
}

/* --- gate ------------------------------------------------------------------
   The mark melting into the name. See the note over the markup in index.html
   for the trick; what is here is the material and the geometry.

   Two colours and they are the SHEET'S OWN. `--paper` is the ground and `--ink`
   is everything on it, so a white sheet loads white with a black mark and a
   black sheet loads black with a white one — and the sheet can be turned over
   mid-load without a single value in this block being recomputed. There is no
   third colour anywhere in the gate. A loading screen that introduces a palette
   the site does not have is a loading screen for a different site. */
.gate {
  position: fixed;
  inset: 0;
  /* ABOVE EVERYTHING, and it was at 4.

     Four was safe for as long as the load screen was the only thing that could
     be on the page while it was up: nothing was open underneath, the shell sat
     at 2 and 3, and the cover cleared both. That stopped being true the frame
     the piece started arriving on a SECTION rather than on the run — see
     `landing()` in main.js. A section is a full-bleed ground at 5 and it lifts
     the shell to 6 with it, so both would have punched straight through the
     cover for the length of its opening: the landing, the wordmark and the
     corner painted over a screen whose whole job is to be the only thing there.

     Eleven, which is one past the highest number in the block beside
     `.contents` — the contents page at 9 and the crown over it at 10. The cover
     is not a layer in that order, it is what is in front of all of it, and the
     number says so rather than happening to be big enough. Nothing here changes
     what the reader sees at 4: the shell's own numbers were already under the
     cover, so the corner still fades in behind it and arrives through the
     aperture exactly as `openGate()` describes. */
  z-index: 11;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* The aperture's radius, in PIXELS, written per frame by openGate().
     Pixels and not a percentage because two different things read it — a
     gradient stop and a width — and a percentage does not mean the same thing
     to both. In px they are in register by construction. */
  --gate-iris: 0px;
  /* How thick the rim of that aperture is. */
  --gate-rim: clamp(7px, 1.5vmin, 18px);
  --gate-rim-o: 0;
}

/* --- the corner waits for the gate ------------------------------------------
   `body.is-gate` is in the markup and removed in `openGate()`, so this is true
   from the first parse rather than from the first module — which is the whole
   reason it is a class on the body and not something a script switches on.

   THE WHOLE CORNER WAITS, dial included, and the dial is the part that changed.

   It used to be exempt. The argument was that the key and the pill are
   navigation — a load screen with a menu on it is a page pretending to be a
   piece — while the dial is not navigation but which way up the thing you are
   looking at is, and that is a question the reader can answer while it loads.
   The argument is fine and the result was not: a load screen is one cover, one
   lockup and one number, and a hairline circle parked in the corner of it is a
   piece of furniture from a page that has not arrived yet. It read as something
   left behind rather than as something offered.

   So there is nothing in the corner during the load, and the corner arrives
   whole — key, dial and pill together, on the one curve, as the cover opens.
   Which is also the only rule this section has left to remember: everything in
   the crown waits, and nothing in it is a special case.

   `> *` and not `> :not(.theme)`, and the crown keeps its own z-index. The
   override that used to be here lifted it to `5` so the dial could ride OVER
   the cover at `4`; with nothing in the corner to see, there is nothing to lift
   — and a layer number whose reason has been deleted is the next reader's
   half hour.

   The dial is still wired from the first frame, and not by this file:
   index.html carries a delegated handler that turns the sheet over before
   `main.js` has finished building an atlas, and `hud.js` takes it off when it
   takes over. It is no longer about the load screen — nothing is pressable
   there now — but about the seam after it: `is-gate` comes off as the cover
   STARTS opening, so the dial is on screen and can be pressed for the length
   of that half second, which is not guaranteed to be after `hud.js` has run. */
body.is-gate .crown > * {
  opacity: 0;
  pointer-events: none;
}

/* The cover.

   Its own layer, and the reason is the mask: a mask eats everything in the box
   it is on, and the ring that races ahead of the opening sits exactly ON the
   edge of the hole. Inside the masked box, the half of it over the hole would
   be swallowed by the hole it is announcing. So the sheet is masked and the
   ring is its sibling.

   A HOLE and not a lift. The old cover closed to a line across the middle,
   which is two hard edges sweeping inward and meeting — a cut, and the piece
   spends its whole load telling the reader that nothing here has edges. This
   opens from the centre out, which is where the lockup already is: the last
   thing the reader looked at is the first thing the piece comes through.

   Two pixels of feather on the stop, and that is all: enough that the circle is
   not stair-stepped at any size, far too little to read as a fade. The rim is
   what the eye follows, not a gradient.

   The feather runs INWARD — from `iris - 2px` to `iris`, clamped at zero —
   rather than outward from `iris` to `iris + 2px`, and that is not a detail.
   Outward, the two pixels survive at radius nought: the cover sat there for
   the whole load with a two-pixel soft hole punched dead centre, showing the
   ribbon through it. A speck under the lockup, on the one screen that has
   nothing else on it. Inward, both stops collapse onto the same number while
   the aperture is shut and the cover is simply closed; the moment the hole has
   a radius the feather has room to exist again, and it lands just inside the
   rim rather than two pixels outside it, which is where it belonged anyway. */
.gate__sheet {
  position: absolute;
  inset: 0;
  background: var(--paper);
  color: var(--ink);
  display: grid;
  place-items: center;
  -webkit-mask-image: radial-gradient(
    circle at 50% 50%,
    transparent 0 max(0px, var(--gate-iris) - 2px),
    #000 var(--gate-iris)
  );
  mask-image: radial-gradient(
    circle at 50% 50%,
    transparent 0 max(0px, var(--gate-iris) - 2px),
    #000 var(--gate-iris)
  );
}

/* The rim. An annulus whose outer radius IS the aperture's, so it cannot drift
   out of register with the hole — the two are the same number, twice.

   It carries the spectrum, and that is the whole argument for it: an opening
   with a coloured rim reads as light arriving through a gap, and an opening
   with a hard grey edge reads as a shape being deleted. Blurred by a pixel and
   a half, because a rim of light does not have an outline. */
.gate__ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--gate-iris) * 2);
  height: calc(var(--gate-iris) * 2);
  translate: -50% -50%;
  border-radius: 50%;
  opacity: var(--gate-rim-o);
  pointer-events: none;
  background: var(--ink);
  filter: blur(1.5px);
  -webkit-mask: radial-gradient(
    circle,
    transparent calc(100% - var(--gate-rim)),
    #000 calc(100% - var(--gate-rim))
  );
  mask: radial-gradient(
    circle,
    transparent calc(100% - var(--gate-rim)),
    #000 calc(100% - var(--gate-rim))
  );
}

/* Nothing but the pointer. Every part of the leaving is driven from openGate()
   on the sheet's own law — see LAW in main.js — because a transition here and
   a clock there is two halves of one move disagreeing by a frame. */
.gate.is-open {
  pointer-events: none;
}

/* The thresholded container.

   A filter on an element makes it a containing block and rasterizes its whole
   subtree as one image, which is exactly what is wanted: the mark and the word
   are welded by the matrix and not composited over each other. `isolation` is
   what keeps that image from being flattened against the paper first — without
   it the alpha the threshold reads is alpha against the ground, and against an
   opaque ground there is no in-between alpha left to threshold. */
.gate__goo {
  display: grid;
  isolation: isolate;
  place-items: center;
  /* Room for the blur to spread into. The filter region is given the same
     allowance in js; this is the layout half of it. */
  padding: 8vmin;
}

/* Both in ONE cell. They have to overlap for the threshold to have two shapes
   to weld, and a grid area says that without either of them being positioned
   inside a containing block that a filter created. */
.gate__mark,
.gate__word {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Opacity, blur and scale are written per frame from gate-morph.js. These
     three are the only properties this element has that move, and they are
     declared here so the first paint is not a flash of the unfiltered pair. */
  opacity: 0;
  will-change: opacity, filter, transform;
}

/* The mark. Sized off the SHORT side, so a phone in portrait gets the same
   lockup as a laptop rather than a logo running off both edges. */
.gate__mark svg {
  display: block;
  width: auto;
  height: clamp(78px, 19vmin, 168px);
  /* Inherited, so the mark is the sheet's ink and turns over with it. */
  color: currentColor;
}

/* The name, set the way the wordmark is set: lowercase, the second face,
   tracked in, with the ® at the shoulder. Not the black weight — a 900 is a
   poster and this is a signature.

   Sized against the mark rather than against the viewport: the two occupy the
   same cell and the melt only reads if what arrives is about as big as what
   left. Under a threshold, thin type is the one thing that does not survive —
   a hairline's alpha never reaches the cut — so this is set solid. */
.gate__word {
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
  font-size: clamp(2.8rem, 12.5vmin, 8.5rem);
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1;
  white-space: nowrap;
  align-items: flex-start;
}

/* Set at the ascender rather than the baseline, and small enough to read as a
   mark rather than as a ninth letter. */
.gate__word i {
  font-size: 0.2em;
  font-style: normal;
  letter-spacing: 0;
  margin-top: 0.55em;
  margin-left: 0.32em;
}

/* The filter's own svg. In the DOM because a filter has to be, and invisible
   because it is a definition and not a picture. */
.gate__filter {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
}

/* The counter, at the foot and out of the lockup's way. It is the only thing
   in the gate that is not the mark or the name. */
.gate__inner {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(22px, 4.4vh, 46px);
  display: flex;
  justify-content: center;
}

.gate__num {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  will-change: transform, opacity;
}

.gate__num i {
  font-style: normal;
  opacity: 0.5;
}

/* --- narrow ----------------------------------------------------------------
   The chips come down a ladder rather than off a cliff, and the rungs are
   measured rather than picked: at each one the widest set in the list —
   "Design System · Software · Website" — is wider than the room the card's head
   has left after the number and the gap, so something has to give.

   What gives is never the pill. `flex-wrap` is on, so a rail that runs out of
   room takes a second line inside a corner that has room for one, and three
   chips over two rows in the top corner of a work is not a caption, it is a
   paragraph. The setting steps back first, then the third chip goes, then the
   second. */

/* The chips stop growing. Below this the card is shrinking faster than the
   type is — `--t-micro` has bottomed out at its clamp floor and the work has
   not — so the extra room inside the pills is the first thing that has to go
   back. */
@media (max-width: 1280px) {
  .plate__tags {
    gap: 0.5em;
  }
  .plate__tag {
    /* Same tracking correction on the right as the base rule — the shorthand
       would otherwise put the four sides back to even. */
    padding: 0.44em 0.61em 0.48em 0.74em;
    font-size: 0.94em;
  }
}

/* Two. Even at the tight setting the third one no longer fits the head. */
@media (max-width: 1024px) {
  .plate__tag:nth-child(n + 3) {
    display: none;
  }
}

/* Where the run goes behind the key and comes back as the contents page. Two
   conditions, and neither is a guess.

   The width is measured: below `--t-micro`'s clamp floor the five words stop
   shrinking with the window and hold at some 340px, so the run and the crown
   meet at about 700px. 820 is that number with the margin a wordmark is
   allowed to grow by.

   The pointer is the other half, at any width. The words are set at the micro
   size the rest of the chrome is, which is a ten-pixel target with nine
   pixels of padding under it — fine under a cursor, and half of what a thumb
   needs. On a touch screen the index is a page, not a rail.

   Both conditions are in `nav.js` too, which needs them to know when a resize
   has made the sheet redundant. */
@media (max-width: 820px), (pointer: coarse) {
  .index {
    display: none;
  }
  .menu {
    display: grid;
  }
  /* The path stays — on a narrow sheet it is the only thing that says which of
     twenty-three you are looking at, and it is the way back out of it. It is
     already on its own line here as it is at every width; what changes is only
     WHICH line, because the rail underneath it is a different height.

     The offset is the crown's own height — it is the tallest thing on the rail
     — plus the air below it, and it is written as the crown's clamp rather
     than as a number so the two cannot drift apart. The run is gone at this
     width (`.index` is `display: none` above and the menu takes over), so
     there is nothing above the path but the wordmark and the controls.

     `fixed` and not `absolute`: this one is deliberately NOT measured against
     the rail, because the rail's height at this width is the crown's and the
     offset already accounts for it. It resolves against `.chrome`, which is
     the viewport. The centring is the base rule's `justify-content: center`,
     which works the same in either box. */
  .crumbs {
    position: fixed;
    left: var(--pad);
    right: var(--pad);
    top: calc(var(--pad) + clamp(34px, 3vw, 42px) + 0.7em);
  }

  /* Same problem as the path, same answer, and it has to be the same
     expression: the rail under the wordmark is the crown's height here, not
     the run's, so a way out measured against `2.9em` would be printed over the
     menu button's row. The crown's own clamp again, so all three of these move
     together the day it changes.

     The tail of it — the path's own line plus the air under it — is the same
     `2.4em` the base rule carries, and it is written out rather than folded in
     so the two halves of the offset stay readable as what they are: the row
     above the path, and the path. */
  .opened {
    top: calc(var(--pad-edge) + clamp(34px, 3vw, 42px) + 0.7em + 2.4em);
  }
}

/* A short window — a phone on its side is the usual one — has to be able to
   reach the last row, and five words at 11vw is taller than it is. */
@media (max-height: 620px) {
  .contents {
    justify-content: flex-start;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .contents__word {
    font-size: clamp(1.6rem, 7vw, 2.6rem);
  }
}

/* One. The work is now a sixth of the screen, and two pills stacked in the
   corner of it is a corner nobody can read. The inset stays at twenty — it is
   the same twenty the title is measured against, and moving it here would put
   the two out of step. */
@media (max-width: 860px) {
  .plate__tag:nth-child(n + 2) {
    display: none;
  }
  .lockup {
    max-width: 74vw;
  }
  .back__key {
    display: none;
  }
}

@media (max-width: 620px) {
  .rail--bottom {
    grid-template-columns: 1fr;
    justify-items: start;
    gap: 14px;
  }
  .rail--bottom > .gauge,
  .rail--bottom > .social {
    justify-self: start;
  }
  .gauge {
    align-items: flex-start;
  }
  .gauge__val {
    text-align: left;
  }
  /* The way back keeps its size on a phone. It is the only control on the
     screen and a thumb is not a cursor. */
  .back__glass {
    padding: 14px 22px 14px 16px;
  }
}

/* --- the services column ---------------------------------------------------
   A full-bleed section of its own, in four layers: the wall behind everything,
   the liquid column over it, the furniture on the cards, and the sheet the
   pointer tears. `visibility` and not `display`, so the column has a
   transition to fade on and `inert` has something to keep a Tab out of.

   The ground is the WALL and not a colour: the outline marquee behind the
   column paints the whole frame in the sheet's own two colours, so a card
   sitting on paper is a card sitting on a word. */
.services {
  position: fixed;
  inset: 0;
  z-index: 5;
  background: var(--paper);
  color: var(--ink);
  opacity: 0;
  visibility: hidden;
  transform-origin: 50% 50%;
  transition:
    opacity 0.5s var(--ease-out),
    visibility 0s linear 0.5s,
    transform 0.9s var(--ease-out);
}

body.is-services .services {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* --- ventures --------------------------------------------------------------
   The same shell as the column, at the same layer and on the same two
   transitions, because it is the same KIND of thing: a full-bleed section the
   run steps behind. What is inside it is the whole difference — one custom
   element, one canvas, one draw call.

   The ground is `--paper` and not transparent even though the wall carries its
   own alpha, and that is the point of the alpha: the gutters between the tiles
   and everything past the edge of the wall are holes, and what shows through
   them is the sheet's own colour. Invert the sheet and the grid inverts with
   it without a line of script — the wall is not painted on a background, it is
   cut out of one. */
/* --- the one room that is always night ------------------------------------
   THE PAIR IS PINNED HERE, and this is the only place in the project allowed
   to say that. Everywhere else `--paper` and `--ink` come off `:root`, written
   by `__sheet.apply()` in the boot script and by nothing else; the whole site
   turns over on one switch and a section with a second opinion about which way
   up the sheet is was, for a long time, the one bug this codebase was arranged
   to make unrepresentable.

   So this is not a second opinion, it is a different MATERIAL. Every other
   section of this site is type on a ground: a run of words, a column, a page,
   a form — things whose whole legibility is the contrast between the two
   colours, and which therefore have to answer the dial. Ventures is a WALL OF
   PHOTOGRAPHS that runs off all four edges, and the photographs are lit
   screens: dashboards, dark-mode admin panels, product shots on black. What
   `--paper` paints in here is not a page, it is the gap between tiles and the
   air past the edge of the lattice — and on the pale sheet that gap is a white
   grid drawn over sixteen bright screens, with two white scrim bands across
   the top and bottom of them. The section stops being a wall seen through
   glass and becomes a contact sheet on a lightbox.

   The pin is on `body.is-ventures` and NOT on `.ventures`, and the difference
   is the shell. The wordmark, the run of sections, the rails and the crown all
   live outside this section and read the pair from the root — and while the
   wall is up they are standing on this section's bands and nothing else. Pin
   the pair on the section alone and the corner keeps answering a dial for a
   ground that is no longer under it: a black CONTACT pill on a black band.
   One declaration on the body is what keeps the shell and the room it is
   standing in describing the same colour.

   IT LASTS EXACTLY AS LONG AS THE CLASS. `ventures.js` adds `is-ventures` on
   `show()` and takes it off on `close()`, so the moment the reader steps back
   out onto the run the whole site is back on the sheet they chose. Nothing is
   remembered, nothing is written to storage, and `__sheet` is never called:
   the dial's own state is untouched the whole time it is in here, which is
   what makes this a room and not a preference. */
body.is-ventures {
  --paper: #000000;
  --ink: #ffffff;
}

.ventures {
  position: fixed;
  inset: 0;
  z-index: 5;
  /* Both of these still read the pair, and they read it through the pin above
     rather than around it. That is the point of doing this with the two tokens
     instead of with literals in here: the gutters, the bands, the capsule and
     the shell all go on saying `--paper` and `--ink`, and there is still only
     one answer on screen at a time — it is just that in this one room the
     answer is settled by the section rather than by the dial. */
  background: var(--paper);
  color: var(--ink);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.5s var(--ease-out),
    visibility 0s linear 0.5s;
}

body.is-ventures .ventures {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* Full bleed, and NOT inset by `--pad-edge`. The wall is a ground, not a card:
   a picture wall with a margin is a poster of a picture wall, and the piece
   depends on tiles running off all four sides so the lattice reads as endless
   rather than as a block of sixteen. The section's own filing sits over it at
   its own layer, where the wordmark's corner already is. */
.ventures__stage {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* --- the two bands the shell is read against -------------------------------
   THE GROUND COLOUR, NOT A SHADOW. This is the one decision in the block and
   everything else follows from it.

   A dark vignette is the obvious thing to reach for and it is wrong here twice
   over. The sheet has two colours and no third — turn the dial and a black
   haze at the top of the screen stops being a shadow and becomes a stain, on a
   site whose whole argument is that it is one bit. And it does not even work:
   the shell rides `mix-blend-mode: difference`, which returns `|1 - 2b|`, so it
   is at full strength over ink AND over paper and at nothing over the mid-tones
   in between. Darkening a photograph toward grey is walking the backdrop TOWARD
   the value at which the shell disappears. Painting it toward `--paper` walks
   it away from that value, which is the same thing `site/chrome-contrast.js`
   does under the rails and the same thing `.glassedge` does at the foot of the
   run. One vocabulary, three places.

   So: `--paper` at both edges, nothing across the middle. On the pale sheet the
   corners lift out of the photographs; on the dark one they sink into them; on
   both, the wordmark and the rails are read against the site's own ground and
   the wall is untouched where the reading happens.

   SIX STOPS AND NOT TWO. A two-stop gradient over 140px of photograph bands
   visibly — the ramp is linear, the eye is not, and eight bit steps across a
   long soft edge are eight edges. The stops below are a rough ease-out: most of
   the fall happens in the first third and the tail runs out to nothing, which
   is both what stops the banding and what makes it read as air rather than as a
   panel with a soft edge.

   ONE ELEMENT, TWO BACKGROUNDS. Sized and positioned per layer rather than as
   two absolutely-placed children: there is nothing for a second box to carry,
   and a layer that costs no element is a layer nobody has to keep in the z-order
   later.

   TALLER AT THE FOOT than at the head, and it is not a taste. The top strip is
   one rail; the bottom is the rail plus the filter capsule sitting on
   `--pad-edge` above it, so there is more furniture and it needs more to stand
   on. Both are clamped in `vh` so a short window does not spend half its height
   on two gradients.

   `z-index: 2` — over the wall at `1`, under the filing at `5` and the capsule
   at `6`, and under the shell without having to say so: `.ventures` is a
   stacking context (fixed, `z-index: 5`, and an opacity it animates), so
   nothing in here can reach the chrome at `6` whatever number it carries. That
   is the whole of "below the fixed UI, above the animated cards", and it is
   held by the markup order as well as by the number.

   `pointer-events: none`, because the wall is DRAGGED. A transparent sheet over
   the top and bottom of the screen is a drag that dies for no reason the reader
   can see — the same trap `.ventures__filter` avoids by being a capsule rather
   than a bar. */
.ventures__shade {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  --shade-top: clamp(84px, 12vh, 164px);
  --shade-bot: clamp(96px, 15vh, 196px);
  /* THE BANDS ARE THE GROUND, and the ground turns over. They were pinned black
     on both sheets, and the reason given was that difference over a white band
     returns black — so a pale sheet would have handed this section a black
     shell over bright product shots. That is a description of the mechanism and
     it was mistaken for a fault: black type on a white scrim over a photograph
     is legible, and it is what a pale sheet is FOR. What the pin actually
     bought was one section that stayed dark while the reader had asked the
     whole site to be light, which is the single thing this piece is not allowed
     to do.

     So the strips go back to being what the wall's gutters already are: the
     sheet coming up under the shell. `--paper`, at the same eight stops, in
     both directions — a white scrim on the pale sheet and a black one on the
     ink sheet — and the shell over them corrects itself without being told,
     because difference against a KNOWN end of the range is the other end of it
     whichever end you are on.

     `color-mix` and not eight `rgba()` literals: the alphas are the shape of
     the falloff and they must not move when the colour does. One token, eight
     percentages, and the section inverts with everything else. */
  --shade: var(--paper);
  background-image:
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--shade) 80%, transparent) 0%,
      color-mix(in srgb, var(--shade) 64%, transparent) 20%,
      color-mix(in srgb, var(--shade) 42%, transparent) 42%,
      color-mix(in srgb, var(--shade) 22%, transparent) 63%,
      color-mix(in srgb, var(--shade) 8%, transparent) 83%,
      transparent 100%
    ),
    linear-gradient(
      to top,
      color-mix(in srgb, var(--shade) 84%, transparent) 0%,
      color-mix(in srgb, var(--shade) 67%, transparent) 20%,
      color-mix(in srgb, var(--shade) 44%, transparent) 42%,
      color-mix(in srgb, var(--shade) 23%, transparent) 63%,
      color-mix(in srgb, var(--shade) 8%, transparent) 83%,
      transparent 100%
    );
  background-position:
    top center,
    bottom center;
  background-size:
    100% var(--shade-top),
    100% var(--shade-bot);
  background-repeat: no-repeat;
  /* It arrives with the wall and not with the class. Same argument as the
     capsule's below: `.ventures` fades the section in over half a second, and a
     band at full strength on the first frame is ground colour crossing an empty
     screen before there is anything under it to stand on. This is the step ON
     TOP of that one, which is why it is a delay and not a longer duration. */
  opacity: 0;
  transition: opacity 0.42s var(--ease-out);
}

body.is-ventures .ventures__shade {
  opacity: 1;
  transition-delay: 0.2s;
}

/* --- and the panes stand down ----------------------------------------------
   `chrome-contrast.js` corrects exactly these two strips, by measuring what is
   under them and laying a pane of glass that pushes the backdrop to whichever
   end of the range `difference` can read. Over this wall it now has a partner
   doing the same job on the same pixels — and worse, a partner that disagrees
   with it: the bands above are unconditionally BLACK, and the engine picks its
   direction from the average brightness of the tiles, which on a wall of
   product screenshots comes out bright and pushes the strip to WHITE. Two
   corrections, opposite signs, stacked. What lands is the mid-grey both of them
   exist to avoid.

   One of them has to go, and it is the measured one — not because it is worse
   but because it is CONDITIONAL, and this section is not: the reader asked for
   a shadow at the head and a shadow at the foot, and a correction that is only
   there when a photograph happens to be mid-toned is a shadow that comes and
   goes while the wall is dragged. The bands are always on, always the same
   depth, and always the same colour, which is what a shadow is.

   `opacity` and not `display: none`, and no `!important` needed: the pane's own
   rule sets `opacity: var(--cg-need)` at (0,1,1) and this is (0,2,1), so the
   plain declaration wins over the variable one. js goes on writing `--cg-need`
   into a property nothing reads, which costs a custom-property write a few
   times a second and keeps the engine free of any knowledge of this section.
   The filter goes with it — an invisible pane is not worth a full-width blur. */
body.is-ventures .chromeglass > i {
  opacity: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* --- the crown, seated -----------------------------------------------------
   THE SHADOW ONLY, and it is now the only thing this section does to the
   corner. The pair used to be pinned white here as well, through the same
   selector that pins it over an opened work; that pin has gone with the black
   bands it existed for, and every control up there reads the sheet again.

   The shadow stays because the wall does not go away underneath the bands — a
   tile can reach the top of the screen and a hairline ring lands on whatever
   frame of whatever clip happens to be there. `--paper` and not black: it is
   the ground the ring is being seated against, so on the ink sheet it is the
   dark halo it always was and on the pale one it is a white one, which is the
   same correction pointed the other way. Two shadows for the reason the
   flooded rule gives: the tight one is what a one-pixel circle needs, the wide
   one seats the group. */
/* AND IT HAS GONE TOO, for the reason the flooded rule gives: two halos under
   a corner of hairline controls read as a smudge, not as seating. What holds
   the ring against a bright frame is the ring's own contrast, not a haze. */
body.is-ventures .crown {
  filter: none;
}

/* A phone puts the bottom rail in one column and lifts the capsule a row above
   it — see `.ventures__filter` at 620 — so the foot of the screen is carrying
   two lines instead of one and the band has to reach both. The head is left
   alone: the index comes off the rail at this width and what is left up there
   is the wordmark and the crown. */
@media (max-width: 620px) {
  .ventures__shade {
    --shade-top: clamp(72px, 10vh, 120px);
    --shade-bot: clamp(132px, 22vh, 230px);
  }
}

/* --- the other face --------------------------------------------------------
   The whole site is Satoshi and this section is not, which is the point rather
   than a variation: a venture is a thing with its own name on its own domain,
   and sixteen of those set in the studio's own voice read as sixteen more of
   the studio's works. PP Supply Sans is wide, technical and nearly a plate —
   the type a name gets stamped in, not the type a studio writes in.

   Declared on the SECTION and not on each part of it, so anything added in here
   later inherits it without being told. It reaches the filter capsule too: the
   component sets `font-family: inherit` on its root, and inherit through a
   shadow boundary is the host's own face. What it does NOT reach is the wall,
   because a canvas has no typography — the same face goes in there as three
   attributes, see `#mount` in `ventures.js`. */
.ventures {
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
}

/* --- the filter, at the foot of the sheet ----------------------------------
   ON THE RAIL'S LINE, not over it. The bottom rail carries the track's control
   at the left edge and the marks at the right, and between them a middle slot
   that belongs to the ribbon's fusion reading and is faded out for the whole
   time a section is up. The capsule takes that slot: `--pad-edge` off the
   bottom, which is the rail's own inset, and centred on the sheet the same way
   the reading it replaces was.

   THE HEIGHT IS THE SOUND BUTTON'S, and that is the one number in here that is
   copied from somewhere else. `--ctrl-h-sm` is the component's small size and
   `--tbg-pad` is the ring of air around its tabs; 28 + 3 + 3, plus the hairline
   on each side, is the 36px circle at the other end of the rail. Three
   controls, one line, no measuring by eye — and it is why the size is `sm` and
   not `md`: at the medium size the capsule is 44 and the rail reads as having
   something bolted onto it.

   `translateX(-50%)` off `left: 50%` rather than a box stretched across the
   foot, so the hit area is the capsule and not the whole bottom of the screen.
   The wall is dragged, and a transparent bar across the rail is a drag that
   dies for no reason the reader can see. */
.ventures__filter {
  position: fixed;
  left: 50%;
  bottom: var(--pad-edge);
  /* SIX, and it is load-bearing rather than decorative. The capsule is mounted
     at body level — see `#mountFilter` in ventures.js for why — and the layer
     it has to clear is `.srails`, the two refracting bands at the head and the
     foot of the frame. Those are at 5 and they are appended after every
     section, so anything left inside `#ventures` (also 5, also fixed, therefore
     a stacking context of its own) is under them however it is numbered: the
     control that filters the wall was being stretched by the same band that
     stretches the wall. At 6 it is over the rails; being written BEFORE the
     shell in the document, it is still under the nav, the crown and the panes,
     which all sit at 6 as well. */
  z-index: 6;
  /* THE SECTION'S FACE, declared rather than inherited. It used to come down
     from `.ventures` — the component sets `font-family: inherit` on its root
     and inherit through a shadow boundary is the host's own face — and the host
     is no longer inside the section. Same stack as `.ventures`, and it stays
     the same stack: this is the section's control, wherever it is parented. */
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
  /* THE SOUND BUTTON'S HEIGHT, at every width, and written as the button's own
     clamp rather than as the number it happens to resolve to. The capsule is
     its tabs plus the ring of air around them plus a hairline on each side —
     `--ctrl-h-sm` + 2 × `--tbg-pad` + 2 — so the eight comes off the top and
     the two ends of the rail are the same height by construction. A literal
     28 was right at this window and six pixels too tall at eight hundred. */
  --ctrl-h-sm: calc(clamp(30px, 2.5vw, 36px) - 8px);
  --ctrl-fs-sm: 11px;
  --tbg-pad: 3px;
  /* 400, because the face has 400 and 200 and nothing else. The component's own
     default is 500, which this face cannot answer and the browser would fake.
     `letter-spacing` and `text-transform` are inherited properties and reach the
     labels through the shadow boundary on their own; a weight declared inside a
     shadow root does not, which is what the custom property is for. */
  --tbg-weight: 400;
  /* PINNED DARK, in one place, because the component's whole palette hangs off
     this pair — the acrylic is a tint in `--paper`, the blob is `--ink`, the
     label on the blob is `--paper` again and the idle labels are `--ink` at
     44%. Redeclaring the two here turns the entire capsule over at once and
     cannot fall out of step with the component's internals the way naming its
     five private tokens one by one would.

     It is redeclared and not inherited on purpose. This is a leaf: nothing is
     inside it but a shadow root that reads exactly these two names, so a local
     `--ink`/`--paper` reaches everything it should and nothing it should not.

     Dark on both sheets, for the reason written on `.ventures` above — the
     capsule sits on the black band at the foot, and a band that does not invert
     cannot carry a control that does. On the white sheet, left alone, this went
     white-on-white: a white pane, a black blob, and five labels at 44% black
     over a shadow. */
  --ink: #ffffff;
  --paper: #000000;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* It arrives with the section rather than being there the instant the class
     lands: the wall fades in over half a second and a control at full strength
     on the first frame is a control that got there before the thing it filters.
     `.ventures` carries the opacity for the section as a whole, so this is the
     step ON TOP of that one — hence the delay and not a longer duration. */
  opacity: 0;
  transform: translateX(-50%) translateY(10px);
  transition:
    opacity 0.42s var(--ease-out),
    transform 0.62s var(--ease-pop-soft);
}

body.is-ventures .ventures__filter {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  transition-delay: 0.26s;
}

/* Below about seven hundred and sixty the middle of the rail stops being big
   enough to stand in, and it fails in two different ways on the way down.

   FIRST the room runs out sideways. The capsule is a fixed 324px — the labels
   are words and the type is clamped, so it does not shrink with the sheet — and
   it is CENTRED, which means it reaches half its own width past the centre line
   while the marks come in from the right edge. Those two meet at about 676px
   wide: measured, not guessed, and the breakpoint is set clear of it rather
   than on it so the last legible width is not also the tightest one.

   THEN, at 620, the rail stops being three columns at all and stacks into three
   rows — the track's control, the fusion reading and the marks, a hundred and
   eleven pixels of them — and a capsule pinned to `--pad-edge` lands across all
   three.

   One answer covers both: the capsule goes ABOVE the whole rail, and the number
   it clears it by is the rail's own height rather than a guess at it.
   `--rail-h` is written onto the section by a ResizeObserver in `ventures.js`
   every time the rail changes shape — CSS cannot measure a sibling, and every
   hand-written version of this number is one edit to `.rail--bottom` away from
   being silently wrong. The fallback is the tallest the rail gets, so even the
   frame before the observer has reported is clear of it.

   It stays centred and it keeps hugging its tabs. Five of them at ten pixels is
   about three hundred of a phone's three hundred and seventy-five, which leaves
   a margin thin but real; narrower than that and the component's own list turns
   into a scrolling rail, which is the behaviour it was given for exactly this
   and is better than a row of labels squeezed until they stop being words. */
@media (max-width: 760px) {
  .ventures__filter {
    bottom: calc(var(--pad-edge) + var(--rail-h, 118px) + 14px);
  }
}

@media (max-width: 620px) {
  .ventures__filter {
    --ctrl-fs-sm: 10px;
  }
}

/* --- the plate: one card, at size, over the wall ---------------------------
   The press used to be a tab. It is a picture now, and the door to the account
   is a second, separate press inside it — see `#mountPlate` in `ventures.js`
   for that argument. What is here is where the thing stands and what it is
   made of; the flight in and out is written in `#flyPlate`, in the Web
   Animations API, because a FLIP out of a box the GPU measured on the frame of
   the press is not a thing a stylesheet can hold.

   AT BODY LEVEL AND AT SIX, exactly like `.ventures__filter` above and for the
   same reason written there: `#ventures` is fixed at 5 and therefore a stacking
   context, so a plate left inside it cannot clear `.srails` — the two
   refracting bands that stretch whatever is painted under them — and a
   photograph read through that band is not a photograph. At 6, and written
   into the document after the capsule and before the shell, it clears the
   rails and the capsule and stays under the nav, the crown and the panes: the
   wordmark and the run of five words are the way back out of this section and
   they never left.

   THE PAIR IS PINNED DARK, the way the capsule's is. `body.is-ventures` already
   pins it for the room, and this would inherit it — right up to the instant the
   section closes, which takes the class off while the plate is still flying
   home. A card that changes colour on its way out is a card that was reading
   somebody else's ground. Declared here, it cannot. */
.vplate {
  position: fixed;
  inset: 0;
  z-index: 6;
  /* Nothing at all until a card is pressed. Not `opacity: 0` — the scrim is a
     backdrop filter over a WebGL canvas, and one that is merely transparent is
     one the compositor is still blurring a wall through on every frame of a
     section nobody has clicked. */
  display: none;
  --ink: #ffffff;
  --paper: #000000;
  color: var(--ink);
  /* The section's own face, declared and not inherited: this is a sibling of
     `.ventures`, not a child of it. Same line `.ventures__filter` carries. */
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
}

.vplate.is-on { display: block; }

/* THE GROUND OPENS AS A CIRCLE from the middle of the tile that was pressed —
   the clip path is animated in JS, because its origin is a pixel the wall
   reported and not a number a stylesheet can know. What is declared here is
   what it is made of.

   `--paper` at 88%, and the twelve per cent is load-bearing: the wall is still
   there behind the plate, out of focus and a stop down, so the picture reads as
   having come forward off it rather than as having replaced it. Black at 100%
   is a lightbox on any site; this is the same ground the two bands at the head
   and the foot of the section are painted in, turned up.

   The blur is a real defocus and not a wash, which is what lets the ground stay
   this light without the lattice reading through it as clutter. It costs one
   compositor pass over a canvas that is genuinely STILL while the plate is up —
   the wall does not redraw an unchanged frame — so it is paid once. */
.vplate__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(0.82);
  backdrop-filter: blur(18px) saturate(0.82);
  cursor: pointer;
}

.vplate__stack {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--pad-edge);
  /* The stack is a layout, not a surface: the scrim underneath it is the way
     out, and a grid cell spanning the viewport would swallow every click that
     was meant for it. The column takes its own events back. */
  pointer-events: none;
}

/* ONE COLUMN, AND THE PICTURE SETS ITS WIDTH. The caption is not a panel beside
   the photograph, it is the line under it, so it inherits the picture's measure
   for free and the two edges line up at every window without a second number.

   Three caps and the tightest wins: 940px so it never outgrows the type it is
   set with, 78vw so there is air at the sides on a wide screen, and 76vh —
   which is 57vh of picture at 4:3 — so a laptop in landscape still has room for
   the caption and for the two shade bands the shell is read against. */
.vplate__col {
  width: min(940px, 78vw, 76vh);
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 2.4vh, 26px);
  pointer-events: auto;
}

.vplate__pic {
  position: relative;
  width: 100%;
}

/* THE PICTURE, at the sheet's own 4:3 — the cell the wall bakes is 640x480 and
   every still behind it is 800x600, so the card resolves into the proportion it
   was always in and the flight is a straightening rather than a crop.

   `overflow: hidden` with a radius that is ANIMATED: the card leaves the wall
   with the corner the shader cut for it — tight on the crushed middle tile,
   wide out at the bowed rim — and opens out to this one on the way in. */
.vplate__frame {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 10px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  /* An edge, on the work that has none of its own. Half the account is a dark
     product screenshot and the ground behind the plate is the sheet's own
     black: without a hairline the card dissolves into the room at two corners
     and stands square at the other two. Inset, so it is the picture's own
     edge and not a frame around it — and inset is also what keeps it a
     hairline through the flight, where an outer ring would be scaled with the
     box and arrive on the wall three pixels thick. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 13%, transparent);
  transform-origin: 50% 50%;
  will-change: transform;
  /* It is a picture, not a control: nothing here closes the plate, and a reader
     who clicks the thing they came to look at should not lose it. */
  cursor: default;
}

.vplate__shot {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* THE WALL'S OWN GUTTERS, EXTENDED. The lattice is straight ruled lines printed
   on the wall and the lens is the only thing that bends them; drawing the
   card's top and bottom edges out to the sides of the screen is the same line,
   said once more, now that the card is off the wall. They grow from the middle
   out, after the picture has most of its size — see `#flyPlate`.

   `100vw` and not `100%`: they are the frame's edges leaving the frame. */
.vplate__rule {
  position: absolute;
  left: 50%;
  width: 100vw;
  height: 1px;
  /* It runs out before it reaches the sides. A hairline that arrives at the
     edge of the screen at full strength is a border on a dialog; one that
     fades away is the gutter it came from, carrying on until it stops being
     about the card. Strongest where it leaves the picture, gone by the frame. */
  background: linear-gradient(
    to right,
    transparent 0%,
    color-mix(in srgb, var(--ink) 26%, transparent) 34%,
    color-mix(in srgb, var(--ink) 26%, transparent) 66%,
    transparent 100%
  );
  transform: translateX(-50%) scaleX(0);
  pointer-events: none;
}

.vplate__rule--top { top: -1px; }
.vplate__rule--bot { bottom: -1px; }

/* THE CAPTION, on the picture's own measure: what it is at the left, what to do
   about it at the right, and the same two voices the wall label is under — the
   title in the head's micro register, the blurb in prose, separated by leading
   rather than by size. */
.vplate__cap {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(16px, 3vw, 44px);
}

.vplate__words {
  display: flex;
  flex-direction: column;
  gap: 0.62em;
  max-width: 54ch;
}

.vplate__title {
  margin: 0;
  font-size: clamp(14px, 1.2vw, 18px);
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.92;
}

.vplate__blurb {
  margin: 0;
  font-size: calc(var(--t-micro) * 1.06);
  line-height: 1.72;
  letter-spacing: 0.005em;
  opacity: 0.5;
  text-wrap: pretty;
}

.vplate__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0.2em 0 0;
  padding: 0;
  list-style: none;
}

/* The same capsule the sheet bakes onto the tile, in the DOM this time. It is
   an outline and not a fill for the reason the baked one is: a filled chip on a
   photograph is a label stuck to it, and these are what the picture IS. */
.vplate__chip {
  border: 1px solid color-mix(in srgb, var(--ink) 26%, transparent);
  border-radius: 999px;
  padding: 0.34em 0.72em;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.7;
}

.vplate__act {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 22px);
  flex: 0 0 auto;
}

/* The way out, in the caption's own row rather than in the corner of the frame:
   the top right of this site is the theme dial and the CONTACT pill, and a
   third mark landing on those is a close nobody can press. Quiet, because it is
   the thing the reader does when they are finished and the door beside it is
   the thing they might do next. Escape and the ground behind the picture do the
   same job for anyone who never looks at it. */
.vplate__close {
  appearance: none;
  border: 0;
  background: none;
  padding: 0.4em 0.2em;
  color: inherit;
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.42;
  cursor: pointer;
  transition: opacity 0.22s var(--ease-out);
}

.vplate__close:hover,
.vplate__close:focus-visible { opacity: 0.92; }

/* Under about seven hundred the caption cannot be a row: the words and the door
   are each about as wide as the picture and putting them side by side gives the
   blurb a four-word measure. It stacks, and the door goes full width — on a
   phone the door is the second thing the reader wants after the picture, and a
   pill floating at the right edge of a stack is a pill they have to aim at. */
@media (max-width: 700px) {
  .vplate__col { width: min(92vw, 64vh); }
  .vplate__cap {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
  }
  .vplate__act { justify-content: space-between; }
}

/* THE CAPSULE STANDS DOWN while a card is up. It filters a wall nobody is
   reading, it is `inert` from `#openPlate`, and left at full strength it is a
   control floating over the photograph the whole plate exists to show. It is
   not hidden: the reader can see the section is still underneath. Same
   specificity as `body.is-ventures .ventures__filter` above and written after
   it, which is what makes it win. */
body.is-plate .ventures__filter {
  opacity: 0.12;
  transition-delay: 0s;
}

/* A reader who has asked for no motion still gets the card — it is the content,
   not the effect. What goes is the flight: `#flyPlate` collapses every duration
   to a frame, and the two hairlines, which are pure motion, never draw. */
@media (prefers-reduced-motion: reduce) {
  .vplate__rule { display: none; }
  .vplate__scrim {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: color-mix(in srgb, var(--paper) 95%, transparent);
  }
}

.ventures__wall {
  display: block;
  width: 100%;
  height: 100%;
}

/* The native cursor STAYS, over a tile as well as off one.

   It used to go while the coin was up, on the argument that two cursors on one
   pointer is one cursor too many. That argument holds for the ribbon, where
   the thing replacing the pointer is a filled glass lens that is unmistakably
   an object being moved around. It does not hold here, because the coin has no
   coin in it: the disc was taken out — see the note on `.svc-cursor__disc` —
   and what is left is two words and an arrowhead in `difference`. That is a
   LABEL, not a cursor, and taking the pointer away to show a label leaves the
   reader with a wall that has stopped answering the hand. On a plane you drag
   and throw, losing the `grab` is losing the only thing that says you can.

   So the element keeps its own `grab` / `grabbing` throughout and the coin
   rides with it, which is what it always was: the promise of what a click will
   do, attached to the pointer rather than standing in for it. */

/* --- the wall -------------------------------------------------------------
   Behind the column and behind everything else in the section. It is opaque —
   the component paints its own ground — so it IS the section's paper. */
/* --- the wall --------------------------------------------------------------
   ONE band across the middle of the screen, and it was three stacked to fill
   it edge to edge.

   The element is the band. That is the whole implementation and it is why the
   component is still asked for one row rather than three: row height inside
   there is `canvasHeight / rows`, so a full-bleed box at three rows and a
   one-third-height box at one row give the SAME row — the same type size, the
   same hairline, the same tile. Nothing about the lettering changed; two thirds
   of the screen simply stopped having any on it.

   Masking the middle third of a full-bleed field would have got to the same
   picture and paid for three rows of shader to throw two of them away.

   `overflow` for the short-window case: the component carries a 180px
   `min-height`, so under about a 540px viewport the band would otherwise grow
   past its own third and reach the cards. */
.services__wall {
  position: absolute;
  left: 0;
  right: 0;
  top: 33.333%;
  height: 33.334%;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Ninety per cent off. It is the furthest thing back on this screen and it
     stands behind photographs; at full strength it was reading as a second
     subject. The component's own ground is `--paper` and the section behind it
     is `--paper`, so dropping the whole layer takes the type down and leaves
     the ground exactly where it was — there is no seam at the band's edges. */
  opacity: 0.1;
}

.services__wall > * {
  display: block;
  width: 100%;
  height: 100%;
}

.services__field {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
}

/* The deck owns every gesture in the section. It is over the field and under
   nothing, which is why the sheet the pointer tears is INSIDE it: a wheel over
   that sheet still has to reach the run underneath, and an event only bubbles
   to an ancestor. */
/* --- the card's type, sized off the CARD ------------------------------------
   These were `2vw` and `1.4vw` on the section root, and the card they are set
   inside is not a share of the viewport — it is `clamp(viewW * 0.25, 150, 480)`
   with a second clamp against the height, and it is written in px by
   column.js. Two different functions of the same window, so the two agreed
   near the middle of the range and came apart at both ends: past 1920 the card
   stopped growing at 960 and the name kept going until its own cap, and on a
   mid-size window the card was still 426 px wide while the chips had fallen to
   seven pixels — the size the floor was set at for a phone. Seven-pixel chips
   on a 426-pixel card is the thing that reads as "the text got tiny".

   So the card hands its own half-width down as `--svc-hw` and everything in
   here is a share of that. One function now, and the proportion is exact at
   every width instead of at one.

   The clamps that remain are legibility, not layout: type has a size under
   which it stops being type whatever it is standing on, and a ceiling so that
   a name on a very large screen stays a name rather than becoming a headline.

   The fallback is the half-width at the reference viewport, so the first frame
   — before the column has measured anything — is the right size rather than a
   guess that is then corrected in front of the reader. */
.services__deck {
  position: absolute;
  inset: 0;
  z-index: 2;
  touch-action: none;
  /* --- ONE CURSOR AT A TIME, AND NEVER ZERO -------------------------------
     This block has been wrong in both directions and this is the third answer.

     It started as `grab` / `grabbing` / `pointer`. Then it was `none`
     everywhere, on the argument that the coin IS the cursor and two cursors on
     one screen is an arrow announcing a click while a disc beside it announces
     the same click. That argument was sound while the coin was up for the
     whole section — but the coin is now latched to the card at the reading
     position (see `#aim` in services/section.js), and `cursor: none` under a
     coin that is not there leaves the reader with NO pointer at all. Half the
     section became a screen you cannot find your own hand on, which is worse
     than two cursors by a wide margin.

     So the native cursor is the floor and the coin is what replaces it. `grab`
     off a card, `grabbing` on the press — the column is a thing you pull — and
     the moment the coin comes up over the reading position, `is-coin` on the
     section takes the arrow away. One cursor at every instant, and the handover
     is driven by the same latch the coin's own scale is. */
  cursor: grab;

  /* The name on a card, at the reading position. Everything in the card is a
     multiple of it times `--k`, the card's own scale — so a card that recedes
     takes its type with it at exactly the rate the picture goes. */
  /* TWICE what it was — 0.09 of the card's half-width, and 0.18 now, with both
     ends of the clamp doubled with it so the proportion is the same at every
     window. The name is what the card is FOR, and at a tenth of the half-width
     it was a caption on a photograph; at a fifth it is the thing the picture is
     the ground for. It wraps on the longer disciplines, which is why the title
     carries `text-wrap: balance` — two even lines rather than four words and a
     widow. */
  --svc-name: clamp(38px, calc(var(--svc-hw, 378px) * 0.18), 104px);
  --svc-pad: clamp(14px, calc(var(--svc-hw, 378px) * 0.062), 32px);
  /* The chips and the plate number. Its floor is the one number in this block
     that is not a proportion of anything: eleven pixels is where uppercase set
     at 0.13em tracking stops being readable, and a chip nobody can read is a
     chip that is only texture. */
  --svc-micro: clamp(11px, calc(var(--svc-hw, 378px) * 0.03), 17px);
}

.services__deck:active {
  cursor: grabbing;
}

/* The coin is up. It is standing in for the pointer, so the pointer goes.
   Written on the SECTION rather than on the deck, because the shave layer and
   the card nodes are all inside it and all three have to hand over on the same
   frame — a card that kept its arrow while the deck under it lost one would
   flicker the cursor every time the hand crossed an edge. */
.services.is-coin .services__deck,
.services.is-coin .services__deck:active,
.services.is-coin .svc,
.services.is-coin .services__shave {
  cursor: none;
}

/* --- a card ---------------------------------------------------------------
   The node is the card's rectangle to the pixel, and it takes the card's
   stretch with it — same contract the ribbon's furniture has with its
   apertures. It draws no ground of its own: the photograph is in the shader
   underneath, and what is in here is what the work is filed under.

   The furniture is the ribbon's, deliberately. A service and a work are the
   same kind of object on this sheet — a thing the studio made or makes — and
   filing the two of them two different ways would be the section announcing
   that it is a different website.

   `translate3d` and not `left`/`top`: this is written every frame for five
   nodes, and a transform is the only one of the three the compositor can take
   without a layout pass. */
.svc {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 4;
  /* Grid and not a column flex any more: the name and its chips are ONE thing
     in the middle of the card, and the number is a plate mark in the corner
     that is not in the flow at all. See the note over #node in column.js for
     why this section files a service differently from how the ribbon files a
     work. */
  display: grid;
  place-items: center;
  text-align: center;
  padding: calc(var(--svc-pad) * var(--k, 1));
  /* Square. The rule is drawn twenty pixels outside this edge and a radius is
     a soft frame inside a hard one. `--r` is still read rather than hard-zeroed
     so the corner remains one number away, in column.js, from coming back. */
  border-radius: var(--r, 0px);
  overflow: hidden;
  color: #ffffff;
  /* The card's own micro size, off the card. See the block on
     `.services__deck` for why this is not a `vw` clamp any more. */
  font-size: var(--svc-micro);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-weight: 500;
  /* NO SHADOW, and it is declared rather than omitted so nothing inherits one
     from further up the sheet.

     There were two here — a tight one for the edge of a glyph and a wide weak
     one for a halo around it. What the pair did was lay a grey bloom around
     every letter, and a letter with a bloom around it is a letter printed on
     fog. It is gone and it is not coming back.

     A CONTOUR replaces it, on the type itself — see `.svc__title`. The two are
     not the same thing and the difference is the whole point: a shadow is a
     second, blurred, offset copy of the word lying on the photograph beside
     it; a contour is the EDGE of the word, on the word, half a pixel wide and
     perfectly sharp. One says the type is floating above the picture. The
     other says it is printed on it. */
  text-shadow: none;
  will-change: transform;
  /* Inherited from the deck, so a card is `grab` when the coin is down and
     `none` when `is-coin` takes it away. See the block on the deck. */
  cursor: inherit;
  -webkit-tap-highlight-color: transparent;
}

/* NOTHING IS PAINTED OVER THE PHOTOGRAPH.

   There was a fall in the MIDDLE of the picture here — a 64% × 52% ellipse at
   60% black, plus a flat 16% wash over the whole card — put there so the name
   had something to sit on. The argument for it was that an ellipse with no
   edge cannot read as a container. It could not, and it read as something
   worse: a dark blurred blob in the middle of every card, which is the one
   thing a photograph never does on its own. Twenty-three pictures were being
   dimmed to hold up four words.

   And nothing is painted on the TYPE either, now — see `text-shadow` above.
   The name is legible because of WHERE it is read rather than because of what
   has been put behind it: it exists on one card at a time, at full size and
   full opacity, on the picture the reader has stopped on. The chips keep the
   dark pill they already had, which is a ground the width of a word rather
   than of a card. */

.svc > * {
  position: relative;
  z-index: 1;
}

.svc[hidden] {
  display: none;
}

.svc:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 5px;
}

/* The middle of the card: the name, and under it what it is made of.

   One block and not two, so the pair is centred as a unit — a title centred in
   the card with chips centred under it would put the optical centre of the
   pair below the middle, and on a sixteen-by-nine card that is visible. The
   gap is in em of the card's own type, so it recedes with everything else. */
.svc__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Opened with the name. The gap is in em of the card's MICRO size — the chips'
     size — so doubling the title left the pair looking crowded: a 1.35em gap
     under a caption is air, the same gap under a headline is a collision. */
  gap: calc(2em * var(--k, 1));
  /* And widened, because the name now wraps. At 88% a two-line title broke
     where the box ran out rather than where the sentence did. */
  max-width: 94%;
  min-width: 0;
}

.svc__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.72em;
  min-width: 0;
}

/* The blur, and only the blur. No border, no shadow: an outline around a pill
   this small is most of what the eye reads, and what it reads is a control. */
.svc__tag {
  display: block;
  white-space: nowrap;
  padding: 0.56em 0.98em 0.6em;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.42);
  font-size: calc(1.02em * var(--k, 1));
}

/* No pill. One short figure in the corner of a card reads as a plate number
   set on the print; the same figure inside a pill reads as a button.

   Out of the flow entirely now that the card's content is centred: it is not
   part of the name, it is a mark ON the print, and the corner it is set in is
   the corner of the CARD rather than the corner of a row of chips. */
.svc__num {
  position: absolute;
  top: calc(var(--svc-pad) * var(--k, 1));
  right: calc(var(--svc-pad) * var(--k, 1));
  z-index: 1;
  flex: none;
  display: block;
  white-space: nowrap;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  font-size: calc(1.02em * var(--k, 1));
}

/* The name. Not a caption and not set like one: the layer above is uppercase,
   tracked out and half a rem, because that is how a list of disciplines is
   read at speed. This is the card saying what it is called, so it takes the
   lockup's treatment instead — sentence case, tracked in, in the second face,
   which is the same face every work on the ribbon answers with. */
.svc__title {
  margin: 0;
  font-family: "PP Supply Sans", "Satoshi", system-ui, sans-serif;
  font-size: calc(var(--svc-name) * var(--k, 1));
  line-height: 0.94;
  font-weight: 400;
  letter-spacing: -0.02em;
  text-transform: none;
  overflow-wrap: anywhere;
  /* Centred type that breaks 4/1 is centred type that looks broken. `balance`
     is the one line in this file that is purely about how a name in the middle
     of a card reads, and it costs nothing where it is unsupported. */
  text-wrap: balance;
  /* --- NOTHING AROUND THE LETTERS ------------------------------------------
     No shadow, no halo, no contour, no plate. The name is white type, and the
     photograph under it is whatever the photograph is.

     Both of the obvious rescues have been in here and both are out. A pair of
     drop shadows laid a grey bloom around every glyph, which is a word printed
     on fog. A stroke under the fill — `paint-order: stroke fill` — was sharper
     and honest about being a sign painter's trick, and it still put a dark
     line around a face at the middle of the card, which is an OUTLINED name
     rather than a name.

     What is left is the card doing the work instead of the type. The name is
     up on exactly one card at a time now — see TYPE_OUT in column.js — at full
     size and full opacity, in the middle of the frame the reader has stopped
     the column on. That is the ground: attention, not ink. A picture that
     fights it is a picture to re-crop or re-deal, and that is a decision about
     the photograph rather than a treatment bolted onto the letters. */
}

/* The keystone is built wider than the cards under it, so the name on it is
   set larger — the proportion is the card's, not the type's. */
.svc.is-keystone .svc__title {
  font-size: calc(var(--svc-name) * var(--k, 1) * 1.18);
}

/* The chips' whole ground. Where the blur is available it does the work and
   the tint steps back out of the way; where it is not, the tint above is left
   to carry it alone. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .svc__tag {
    background: rgba(0, 0, 0, 0.26);
    backdrop-filter: blur(9px) saturate(140%) brightness(0.82);
    -webkit-backdrop-filter: blur(9px) saturate(140%) brightness(0.82);
  }
}

/* --- THE CHIPS, WHEN THE MACHINE CANNOT AFFORD THEM ------------------------
   Seventy-two of them are in the document at once — forty-two on the ribbon's
   plates and thirty on the services cards — and every one is its own
   compositing layer with its own read of whatever is painted behind it. That
   is fine on a machine that can pay for it and it is the single largest thing
   on this page that a machine which cannot pay for it is being charged for:
   the blur is re-taken on every frame the ground under a chip moves, and the
   ground under these chips is a ribbon that never stops moving.

   So the lens comes off under pressure and the TINT the same rule already
   ships takes over — 0.42 against 0.26, which is the exact pair the
   `@supports` fallbacks above are built on. This is not a new look. It is the
   look this page already wears in every browser without `backdrop-filter`,
   asked for on purpose instead of by absence.

   `is-thrifty` is set from the governor's verdict in main.js and from nowhere
   else: it comes on when the measured frames say the resolution had to be
   walked down, and it goes off again when they say it did not. A machine that
   holds its budget never sees this rule at all.

   One block for both chips rather than one beside each: it is one decision
   about one material, and a shared weight declared twice is a shared weight
   that drifts. --------------------------------------------------------- */
html.is-thrifty .plate__tag,
html.is-thrifty .svc__tag {
  background: rgba(0, 0, 0, 0.42);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* --- the sheet the pointer tears ------------------------------------------
   One element, re-pointed at whichever card is at rest. Never eleven — see the
   note at the head of section.js.

   Under the card's furniture and over its picture: the name and the chips keep
   painting while the photograph is being torn open, because they are what the
   card IS and the tear is only what is under it.

   Up only while the column has stopped. `pointer-events` goes with the
   opacity, so a fading sheet is never eating a click meant for the card. */
.services__shave {
  position: absolute;
  z-index: 3;
  overflow: hidden;
  border-radius: var(--radius-card, 24px);
  --stage-ground: var(--ink);
  /* Inherited, like the card's. See the deck. */
  cursor: inherit;

  /* --- THE SECOND CARD AT THE CORNERS, and why there was one ---------------
     This sheet is a RECTANGLE. The card under it is not: its silhouette is
     domain-warped in the shader and breathes by about six pixels at rest, so
     the true edge of the card is somewhere inside a twelve-pixel band that is
     never in the same place twice. Both of them are painting the same
     photograph, cover-fitted into the same box.

     So wherever the card's edge happened to be INSIDE the sheet's, the sheet
     kept painting past it — a hard straight edge of picture standing outside
     the wobbling one. At the corners, where both axes deviate at once, it read
     as a second card behind the first. It was not a second card; it was this
     one, refusing to have the shape of the thing it is lying on.

     The fix is not an inset. Insetting the sheet would put its box out of step
     with the card's, and a cover fit into a box two per cent smaller is the
     same photograph at a slightly different scale — which is a ghost, and a
     worse artefact than the one being fixed.

     The box stays EXACTLY the card's rectangle, so the two crops are identical
     to the pixel, and the sheet's own edge is dissolved instead: an alpha ramp
     wider than the breathing is tall. At the furthest the card's edge can wander
     inward, the sheet is already down to about a third and still falling, so
     there is no straight edge left anywhere to read as an outline. Under the
     ramp, both layers are the same picture at the same scale, so what the ramp
     is cross-fading is a photograph with itself. */
  --feather: 18px;
  -webkit-mask-image:
    linear-gradient(to right, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent);
  mask-image:
    linear-gradient(to right, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--feather), #000 calc(100% - var(--feather)), transparent);
  /* Intersect, not stack: two ramps over one another give a cross, and what is
     wanted is the region inside BOTH — a rectangle with all four sides soft. */
  -webkit-mask-composite: source-in;
  mask-composite: intersect;

  opacity: 0;
  pointer-events: none;
  /* 0.72s, and it was 0.34s.

     This layer is what arrives when the column stops and leaves when it starts,
     and at a third of a second it did not read as arriving — it read as a
     POPUP: the sheet was simply there one moment and not the next, so the
     handover between the flat card and the cuttable one landed as a state
     change instead of as a settle. Twice the travel on the same curve, which is
     the one this section already decelerates on. */
  transition: opacity 0.72s var(--ease-out);
}

.services__shave.is-live {
  opacity: 1;
  pointer-events: auto;
}

.services__shave > * {
  display: block;
  width: 100%;
  height: 100%;
}

/* --- the grid --------------------------------------------------------------
   Four rules and four dots, welded to the card at the reading position. The
   geometry is in grid.js; what is here is the material.

   OVER the cards, at z-index 5, and it was under them at 3.

   The argument for under was that a rule which crosses a card is a rule drawn
   ON it, and this one is drawn AROUND it — so it should disappear behind every
   slab it meets. That argument only holds for the card at the reading
   position, which the frame never touches: it is twenty pixels outside it on
   all four sides, by construction. Every OTHER card is somewhere else on the
   screen and travelling, and under the old order those slabs painted over the
   frame the moment they crossed it — which is a frame that is on screen and
   not on screen depending on where the column happens to be. A measurement
   that is intermittent is not a measurement.

   So the slabs pass UNDER it now. The frame is the one fixed thing in the
   section and everything else moves through it, which is what it was always
   claiming to be. The twenty pixels are air, not a border.

   The colour is the ink at a tenth, which on paper is a printed hairline and
   on the turned sheet is the same hairline in the same relationship. It is not
   `mix-blend-mode: difference` like the chrome, because the ground behind it
   is the wall — an outline word in the sheet's own two colours — and a
   difference blend over that is a line that changes value every time it
   crosses a letter. */
.services__grid {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.42s var(--ease-out);
}

.services__grid.is-live {
  opacity: 1;
}

.services__rule {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  /* Twenty-two per cent, and it was eleven. Eleven was a hairline sharing the
     job with a cast shadow under every slab; the shadow is gone — see "there is
     no shadow" in `shaders/column.js` — and a line that is holding the whole
     stack together on its own has to be a line somebody can see it doing it
     with. Still ink and still a printed hairline, not a border. */
  background: color-mix(in srgb, var(--ink) 22%, transparent);
  will-change: transform;
  /* A transition, and it can have one now for the reason it could not before:
     the frame is no longer FOLLOWING anything. It is drawn on the resting
     rectangle — see `#place` in services/section.js — so it is written when the
     card at the reading position changes and at no other time, which is a
     destination a transition can know. While it was chasing the stretched
     rectangle sixty times a second a transition would have been sixty
     interpolations fighting each other. */
  transition: transform 0.46s var(--ease-out);
}

.services__rule--v {
  width: 1px;
  height: 100%;
}

.services__rule--h {
  width: 100%;
  height: 1px;
}

/* Where a rule meets a rule. Three pixels, square, in full ink.

   Square and not round, and it took a round one on screen to be sure: a circle
   at 3px is two grey pixels and a suggestion, and it reads as a dot that
   happened to land there. A square sits exactly on the two lines that made it
   and reads as the CROSSING — which is what it is, and the only reason it is
   drawn at all. Full ink against the rules' eleven per cent, because a
   crossing that is the same value as the lines is not a crossing, it is a
   thicker bit of line. */
.services__dot {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: 3px;
  height: 3px;
  background: var(--ink);
  will-change: transform;
  /* The same travel as the rules that made it. A crossing that arrives after
     the two lines it is the crossing of is a crossing of nothing. */
  transition: transform 0.46s var(--ease-out);
}

/* --- the section's cursor --------------------------------------------------
   A coin that says what the click will do. Flat, not glass — see the head of
   cursor.js for why this section has a second cursor rather than the ribbon's.

   Every number that moves is written per frame in js. What is here is the
   material and nothing else. */
.svc-cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 7;
  pointer-events: none;
  /* Twenty per cent off every stop of the clamp, twice over. 66/5.6/94 was the
     size it needed while it was a filled disc with a word knocked out of it;
     53/4.5/75 was that less a fifth, once the plate had gone and it was two
     lines of type and an arrow. This is a fifth off again, for the reason the
     ring below it changed at the same time: the ring is now up for the whole
     hover rather than only under a press, so the coin is a permanent object on
     the picture instead of a flash on a click, and a permanent object has to
     be smaller than a flash to be worth its room. */
  --coin: clamp(42px, 3.6vw, 60px);
}

.svc-cursor__disc {
  position: fixed;
  left: 0;
  top: 0;
  width: var(--coin);
  height: var(--coin);
  /* Pulled back by half its own size, so js can translate to the raw pointer
     and the coin lands centred on it without ever measuring itself. */
  margin: calc(var(--coin) * -0.5);
  border-radius: 50%;
  display: grid;
  place-items: center;
  /* NO COIN. There was a filled disc here — 88% ink with the word knocked out
     of it in paper — and a filled disc is a plate: it covers the photograph at
     exactly the spot the reader is pointing at, which on a card that is now
     being cut open under the pointer is the one place nothing may be covering.

     What is left is the promise itself, two words tall, and the piece's own
     answer for putting type on a photograph without a ground under it:
     `difference`. Drawn in white and differenced against whatever is beneath —
     over the dark half of a picture it returns light, over the light half it
     returns dark, and over the negative the blade has just opened it flips with
     it on the same frame. Nothing to keep in step, and no plate. */
  color: #ffffff;
  mix-blend-mode: difference;
  will-change: translate, rotate, scale, opacity;
  translate: -300px -300px;
  scale: 0;
  opacity: 0;
}

/* One ring outside the rim, and it does not blink.

   It used to be raised on press and gone the rest of the time, which made it
   the answer to a click and nothing else. That left the cursor, for the whole
   time the reader is actually pointing at something, as two words floating on
   a photograph with no edge anywhere — a label, not an object. The ring is the
   only thing that gives the coin a boundary, and a boundary that exists for
   the 120 ms of a press is a boundary nobody sees.

   So it is now up for as long as the coin is: it comes in with the pop, holds
   at a little over a third while the hand is simply over the card, and goes to
   full and swells under a press. Written from `--halo` in cursor.js, which
   folds both of those into one number. */
.svc-cursor__ring {
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: var(--hair) solid color-mix(in srgb, var(--ink) 52%, transparent);
  opacity: var(--halo, 0);
  scale: var(--halo-s, 0.9);
}

.svc-cursor__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* Undoes the disc's own rotation, so the ellipse leans into the direction of
     travel and the word stays upright inside it. Fed the same angle js just
     wrote, negated, and unwrapped rather than normalised — a value that
     wrapped at ±180° would spin the word once per crossing. */
  rotate: var(--counter, 0deg);
  font-size: clamp(0.5rem, 0.6vw, 0.72rem);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* The word is one letter narrower on the right than the tracking implies. */
  text-indent: 0.18em;
}

.svc-cursor__arrow {
  width: 12px;
  height: 12px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A finger has nothing to replace. The node is never built on a coarse
   pointer — cursor.js is not constructed at all — and this is the belt to that
   pair of braces, for a hybrid machine that reports coarse after the section
   has already been up once. */
body.is-coarse .svc-cursor,
body.is-book .svc-cursor {
  display: none;
}

/* The native cursor stays here too, and for the reason written beside the
   ventures wall: the coin has no disc in it, so it is a label attached to the
   pointer rather than a thing standing in for one. The deck keeps `grab` off a
   card and the card keeps `pointer` on one, which between them are the only
   two things on screen that say what the hand can do. */

/* --- the section's furniture ---------------------------------------------- */

/* Both in the same corner, stacked, and NOT one in each. The crown lives in
   the opposite corner at a higher layer — a count set against `--pad` on the
   right is a count printed underneath the contact button, which is a figure
   nobody can read on top of a control nobody can see through.

   Hung under the wordmark rather than against `--pad`, since the shell stayed:
   the top left corner is the site's name in every section, and the section's
   own filing goes under it. `2.9em` is at this element's size, which is the
   micro — about the wordmark's line plus the air the rail leaves under it. */
/* The corner slot, and it is VENTURES' now. The services column had it too —
   a kicker over a count, stacked under the wordmark — and has moved its filing
   to the centre line at the foot; see `.services__filing` below. The wall kept
   the corner because what it hangs there is a kicker and nothing else: there is
   no count on a wall where every card is on screen at once, so there is nothing
   to make large and nowhere it would rather be than in the corner with the
   site's name. The class names stayed as they were written — one section's
   furniture named after another's is worth less than a rename touching two
   files for no change on screen. */
.services__head {
  position: absolute;
  top: calc(var(--pad-edge) + 2.9em);
  left: var(--pad-edge);
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.55em;
  pointer-events: none;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.services__kicker {
  opacity: 0.45;
}

/* THE WALL LABEL — what the tile under the pointer is, in words.

   A LABEL ON THE WALL AND NOT A CAPTION ON THE PICTURE, which is the only
   reason it is allowed to exist at all: the head of
   `vs-anim-pinch-tile-wall.js` argues that type in the DOM over the canvas
   sits flat on the glass while the tile it names is crushed underneath it, and
   that argument is why a card's NAME and CHIPS are baked into the sheet. This
   is the other thing. It is pinned to the corner of the room — under the
   section's own kicker, in the filing block, where the site puts "what you are
   looking at" in every other section — it never moves, and what it carries is
   the one sentence a screenshot cannot say about itself.

   In `.services__head`, so it inherits the corner, the layer, the micro scale
   and the `pointer-events: none` instead of inventing four of its own. That is
   also what keeps it off the foot of the screen, where the rail and the filter
   capsule already are.

   TWO VOICES AND NOT ONE. The title keeps the head's own register — micro,
   tracked out, upper — because it is the same kind of word as the kicker above
   it. The blurb undoes all three: a sentence set in tracked-out capitals is a
   sentence nobody reads. What separates them after that is LEADING, not size
   and not opacity: the title is one line and the blurb is three, and three
   tight lines under a heading is a block of texture rather than a paragraph. */
.ventures__label {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.7em;
  /* About forty characters at the blurb's own size — a measure, so the label is
     a column in the margin rather than a line running under the wall. */
  max-width: 34ch;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 0.26s ease, transform 0.26s ease;
}

.ventures__label.is-on {
  opacity: 1;
  transform: none;
}

.ventures__label-title {
  opacity: 0.92;
}

.ventures__label-blurb {
  /* All three of the head's inherited settings, undone. A blurb is prose. */
  text-transform: none;
  letter-spacing: 0.005em;
  font-weight: 400;
  font-size: calc(var(--t-micro) * 1.06);
  /* The whole separation from the title. See the note above. */
  line-height: 1.72;
  opacity: 0.5;
  text-wrap: pretty;
}

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

/* THE FILING, AS A SPINE IN THE LEFT MARGIN.

   It has been three things. Two micro lines under the wordmark, which is where
   a section's own business goes and which made an odometer of eleven cards
   into a footnote. Then a page number set large on the centre line at the
   foot, which fixed the size and broke the meaning: the centre line is the
   CARD'S line, a large figure under the middle of a photograph is a caption,
   and *07* read as the number of the picture rather than as the reader's place
   in a ring of eleven.

   Now it stands up and moves to the margin. Turned a quarter, climbing from
   the foot, at the size of a running head. Nothing shares that gutter, so it
   does not have to be loud to be found — and standing beside the column rather
   than under it, it goes back to describing the READING instead of the card.

   --- TWO BOXES, AND THE OUTER ONE IS THE MARGIN --------------------------
   `.services__filing` is the whole left margin, full height, and it does one
   thing: centre the spine on a flex line. `.services__spine` is the rule of
   type, and it spins about its own middle. Nothing is positioned by transform
   arithmetic — which is what a rotated box pinned to an edge otherwise costs,
   against a width that changes with the copy and the type scale. */
.services__filing {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  /* The gutter, and its MIDDLE is where the spine stands. Tracks `--pad-edge`
     so the rule sits on the same margin the rest of the section is set to,
     with a floor and a ceiling for the two ends of the viewport range: under
     the floor the spine hangs off the edge of the screen, over the ceiling it
     has walked into the column. */
  width: clamp(34px, calc(var(--pad-edge) + 1.5em), 74px);
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  font-size: var(--t-micro);
  font-weight: 500;
  text-transform: uppercase;
}

/* THE RULE OF TYPE ITSELF, laid out as a row and stood on its end.

   `rotate` and not `transform: rotate()`, and it is the reason the wrapper
   exists at all: the independent property leaves `transform` free, so the
   entrance below can slide the spine into place without a keyframe having to
   restate the quarter turn on every step — and get it wrong the one time
   somebody edits a percentage.

   COUNTER-CLOCKWISE, so the row's left-to-right becomes the screen's
   bottom-to-top: the hairline, the figure, the second hairline and the word,
   climbing the margin. That is how a title is set on the spine of a book, and
   the direction is not a preference — a left-hand spine reads UP, a right-hand
   one reads down, and this one is in the left margin.

   The row is far wider than the gutter before it turns, and it overflows the
   flex line equally on both sides, which is exactly what centres it. Nothing
   clips it: `.services` is `overflow: visible` and the turned rule is a
   fraction of the gutter wide. */
.services__spine {
  display: flex;
  align-items: center;
  column-gap: clamp(8px, 0.8vw, 14px);
  white-space: nowrap;
  rotate: -90deg;
}

/* The two hairlines the count is set between. A page number in a book has
   them, and for the same reason: a figure alone on a wide empty rail is a
   figure that has drifted there, and two rules pointing at it are what make it
   a POSITION.

   Mirrored, and the gradient is what does the mirroring — each fades from
   nothing at the outer edge to the ink at the inner one, so the pair reads as
   one line the count has been cut out of rather than as two dashes.

   --- AND THE MIRROR IS A DIRECTION, NOT A HALF TURN -------------------------
   The second wing used to be `rotate: 180deg` — one gradient declared once and
   the second one turned over, which is the tidier idea and is silently wrong.
   `transform-origin` governs the individual `rotate` property exactly as it
   governs `transform`, and this element's origin is its own left edge, set
   there for the `scaleX` below. A half turn about a left edge does not turn a
   thing over in place: it lands it one full width to the LEFT of where it was
   laid out. The wing was drawing itself on top of the count.

   It survived on the centre line because there it was `1fr` of a wide grid and
   the misplaced copy landed under the other wing, at a length nobody could
   measure by eye. Stood up in a margin at forty pixels it is unmistakable —
   the upper rule was simply missing and the lower one was two rules deep.

   So the pair is mirrored by the gradient's DIRECTION, and the thing that is
   declared once is the ink: `--wing-ink` is the colour, both rules read it,
   and the only difference between them is which end it is at. */
.services__wing {
  --wing-ink: color-mix(in srgb, var(--ink) 62%, transparent);
  /* A LENGTH AND NOT `100%` NOW. On the centre line these were `1fr` in a grid
     and took whatever the screen had left; in the margin the spine is as long
     as the sum of its parts, so the hairlines are what SET that length. In
     `vh`, because the rule is vertical on screen and its proportion is against
     the height of the sheet rather than the width. */
  width: clamp(18px, 3.2vh, 44px);
  flex: none;
  height: var(--hair);
  /* THE INNER END IS DARKER THAN IT WAS, and the two changes are one change.
     On the centre line a wing was a third of a wide screen and its fade had a
     couple of hundred pixels to happen in; at a third of that length the same
     ramp reaches nothing you can see before it has to stop, and the pair read
     as smudges rather than as rules pointing at the figure. Shorter line, more
     ink at the end that is pointing. */
  background: linear-gradient(to right, transparent, var(--wing-ink));
  transform-origin: 100% 50%;
  /* The column's own speed, written by `#filing` — 0 at rest, 1 in flight. The
     wings pull IN as the run gets going and settle back out when it stops, so
     the furniture is part of the same material as the cards rather than a
     static frame they happen to be moving behind. */
  transform: scaleX(calc(1 - var(--flight, 0) * 0.34));
  opacity: calc(1 - var(--flight, 0) * 0.45);
  transition:
    transform 0.5s var(--ease-out),
    opacity 0.5s var(--ease-out);
}

/* The far side of the count. Only two declarations differ from the rule above,
   and both of them are the same fact said to two different consumers: the ink
   is at the OTHER end. `transform-origin` is that fact for the `--flight`
   pull-in, `to left` is that fact for the gradient. Nothing here moves the
   element — see the half-turn note above for why that matters. */
.services__wing:last-of-type {
  transform-origin: 0 50%;
  background: linear-gradient(to left, transparent, var(--wing-ink));
}

.services__count {
  display: flex;
  /* CENTRED AND NOT ON A BASELINE. A baseline is the right alignment for two
     figures of different sizes sitting in a line of prose, because the line of
     prose has a baseline and they are standing on it. A spine has no baseline:
     it has an AXIS, one rule's thickness wide, and everything in the margin is
     read against that. Aligned on baselines the total sat visibly off the
     axis, which at this width is the difference between a rule and two things
     that happen to be near each other. */
  align-items: center;
  /* THE GAP THE TWO FIGURES NEVER HAD. `07` and `11` were siblings with no
     space and no separator between them, and at the old size the eye read the
     kerning as a gap. Set small in a margin they were one four-digit number.
     A gap and not a slash: the pair is a count over a total, the total is at a
     third the ink, and a glyph between them is a third thing to letterspace. */
  gap: 0.55em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* The one figure on this screen that is allowed to be a figure — which in a
   margin is a smaller allowance than it was on the centre line. A shade over
   the micro everything else here is set at: enough to be the thing the eye
   lands on in the gutter, not enough to be a display number standing sideways,
   which is a figure asking to be read as an ornament. */
.odo {
  display: flex;
  font-size: clamp(15px, 1.05vw, 21px);
  letter-spacing: 0.01em;
  line-height: 1;
}

.services__count em {
  font-style: normal;
  margin: 0 0.5em 0 0.62em;
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  opacity: 0.26;
}

.odo__of {
  font-style: normal;
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  opacity: 0.34;
}

/* --- one barrel per digit --------------------------------------------------
   A window the height of one figure with two figures in it: the one leaving
   and the one arriving. Not a strip of 0-9 translated, which is the other way
   to build this — a strip cannot go from 9 to 0 without winding back through
   eight digits nobody asked to see, and it cannot be told which WAY the column
   is travelling. Two children and a direction can do both. */
.odo__d {
  position: relative;
  display: block;
  width: 0.62em;
  height: 1em;
  overflow: hidden;
}

.odo__d > i {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-style: normal;
  will-change: transform, opacity, filter;
}

/* Its own class and NOT `.services__kicker`, which the ventures wall is still
   wearing in the corner. Same word, different job: that one is a label at the
   top of a section, this one is the footing under a figure. */
.services__foot {
  font-size: var(--t-micro);
  /* Wide, because this line is not a label above a number — it is the far end
     of the same rule, and at this tracking it reads as a rule made of letters
     rather than as a word competing with the figure below it. Narrower than it
     was on the centre line: at 0.62em in a margin the word is longer than the
     count and the two hairlines together, and the spine stops being centred on
     anything. The indent is what stops the tracking hanging an empty letter
     space off the top of the rule. */
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  opacity: 0.38;
}

/* --- the filing arriving ---------------------------------------------------
   FOUR MOVES ON ONE CLOCK, AND THEY ALL RUN UP THE MARGIN.

   The order is the argument, and it is the order the rule is built in rather
   than the order it is read in: the spine RISES into the gutter, the two
   hairlines draw themselves out of the count, the figure lands in the gap they
   left, and the word is wiped open at the top last. Built out, not faded in —
   a block that arrives at opacity is a block that was always there and got
   brighter.

   EVERY ONE OF THEM IS WRITTEN AS IF THE SPINE WERE STILL FLAT, and that is
   the whole benefit of turning the rule with `rotate` instead of `transform`.
   A child's `translateX` is along the rule; on screen it is vertical. The
   wings' `scaleX` is along the rule; on screen they draw upward and downward
   out of the number. The odometer's barrels roll on `translateY`, which is
   across the rule, so a digit still turns over relative to its own baseline
   rather than sliding along the margin. None of that is stated twice and none
   of it has an angle in it: the keyframes describe a line of type, and the one
   declaration that stands it up is on the parent.

   Off `body.is-services`, so it re-runs on every entry into the section
   without a line of script, which is the deal `.back` has with `is-open`. */
body.is-services .services__spine {
  animation: filing-spine 0.94s var(--ease-out) backwards;
}

body.is-services .services__wing {
  /* `backwards` and not `both`. An animation that fills FORWARDS wins over
     every declaration for the rest of the session, and the thing it would be
     winning over here is `--flight` — the wings would draw themselves out once
     and then never breathe with the run again. Filling backwards holds the
     first frame until the clock starts and lets go the moment it stops. */
  animation: filing-wing 0.86s 0.1s var(--ease-out) backwards;
}

body.is-services .odo {
  animation: filing-num 0.72s 0.26s var(--ease-out) backwards;
}

body.is-services .services__count em,
body.is-services .odo__of {
  animation: filing-foot 0.7s 0.4s var(--ease-out) backwards;
}

/* The word is WIPED and not faded, and at this tracking the wipe is very
   nearly a letter at a time — which is what it should look like, because the
   letters are what the far end of the rule is made of. Along the line of type,
   so on screen it opens upward, away from the figure and out of the gutter. */
body.is-services .services__foot {
  animation: filing-word 0.82s 0.44s var(--ease-out) backwards;
}

/* The rule arrives out of the foot of its own margin, which is the direction
   the reader is about to travel and the direction the rule itself reads. Along
   the line, so `translateX` — see the note above the block. */
@keyframes filing-spine {
  0% {
    transform: translateX(-1.5em);
    opacity: 0;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes filing-wing {
  0% {
    transform: scaleX(0);
    opacity: 0;
  }
  100% {
    transform: scaleX(1);
    opacity: 1;
  }
}

@keyframes filing-num {
  0% {
    transform: translateY(0.42em);
    opacity: 0;
    filter: blur(7px);
  }
  100% {
    transform: translateY(0);
    opacity: 1;
    filter: blur(0);
  }
}

@keyframes filing-foot {
  0% {
    opacity: 0;
  }
}

/* NO `opacity` IN THE LAST FRAME, deliberately. The word sits at 0.38 at rest,
   and a keyframe that ends at 1 would end the entrance by snapping the label a
   third dimmer on the frame after it landed. Left out, the property
   interpolates from 0 to whatever the cascade says — which is the figure that
   was written for it, once, next to the type. The negative inset at the end is
   the tracking's trailing letter space and the last glyph's overhang: clipped
   at exactly 0 the S loses a hair on the frame the wipe finishes. */
@keyframes filing-word {
  0% {
    clip-path: inset(0 100% 0 0);
    opacity: 0;
  }
  100% {
    clip-path: inset(0 -0.5em 0 0);
  }
}

/* The reader who asked for no motion still gets the whole block, in place. */
@media (prefers-reduced-motion: reduce) {
  body.is-services .services__spine,
  body.is-services .services__wing,
  body.is-services .odo,
  body.is-services .services__count em,
  body.is-services .odo__of,
  body.is-services .services__foot {
    animation: none;
  }
}

/* Where you are in the ring: the index's rule, stood on its end. The track is
   a hairline at a tenth strength and the bar is the same colour at full — no
   filled chip, for the same reason the run at the head of the sheet has none. */
.services__rail {
  position: absolute;
  top: 22vh;
  bottom: 22vh;
  right: calc(var(--pad) * 0.72);
  z-index: 5;
  width: var(--hair);
  background: color-mix(in srgb, var(--ink) 14%, transparent);
  pointer-events: none;
}

.services__rail > i {
  position: absolute;
  left: -1px;
  width: 3px;
  height: 9%;
  border-radius: 999px;
  background: var(--ink);
  will-change: transform;
}

/* --- the way out ----------------------------------------------------------
   NOTHING, and this block is the note rather than the rules.

   There were two pills: the column's, in the bottom left, and the book's, in
   the middle of the bottom rail. The column's went first for being the fourth
   control on one screen saying "back" — the wordmark, the run of sections and
   Esc all said it already. The book's has followed it, and the reasoning is
   the same one arriving a screen later: a lookbook opened full-bleed is a
   PICTURE, and the shell's run is up over it the whole time, so a glass key
   parked on its bottom rail was a control drawn over the thing it was meant to
   let you look at.

   Esc closes the book (`#onKey` in services/section.js) and SERVICES in the
   run walks out of it. Neither needs a rule in here.

   The ventures wall never had one, and for the reason this section has now
   adopted: the shell never leaves, so the way out is already on screen. */

/* --- the ribbon's own furniture, while the column is up --------------------
   Only the readings OF the run step aside — the sentence over it, the fusion,
   the count of twenty-three, the filing plates, the cursor lens and its tag.
   There is no twenty-three and no fusion in here, so a rail still printing
   them would be a caption for a page that is not on screen.

   The shell stays: the wordmark, the run of sections, the track's control, the
   marks, and the crown. That is the point of a shell — walking into a section
   is not leaving the site, and the way to the next section has to be on screen
   in this one or the section is a dead end with a glass key in the corner.

   The layer has to come up with it. `.chrome` sits at `z-index: 2`, under the
   ribbon's own furniture and well under this section's full-bleed ground at
   5 — see the z-order note beside `.contents`. */
body.is-services .creed,
body.is-services .lockup__slot,
body.is-services .gauge,
body.is-services .plates,
body.is-services .probe,
body.is-services .tag,
body.is-ventures .creed,
body.is-ventures .lockup__slot,
body.is-ventures .gauge,
body.is-ventures .plates,
body.is-ventures .probe,
body.is-ventures .tag {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.32s var(--ease-out);
}

/* The two glass layers come up with the shell, one step behind it, and they
   come up for the same reason it does: the sections are full-bleed grounds and
   the default `2` is under every one of them.

   All four on ONE number rather than a ladder. Siblings at a single layer
   paint in document order, and the order they are written in is already the
   order they have to be in — the edge, then the shell's panes, then the shell
   — so a ladder here would be three numbers restating what the markup already
   says, and each of them would be a thing to keep in step with the next
   section that needs to move.

   The panes are the load-bearing half of this rule. They are a correction
   applied to whatever is behind the shell, and behind the shell in these two
   sections is a photograph at `5`: left at `2` they would be under it, which
   is not a subtle failure — it is the index disappearing into a sky with the
   fix sitting invisibly beneath the thing it was supposed to fix. */
body.is-services .chrome,
body.is-services .crown,
body.is-services .chromeglass,
body.is-ventures .chrome,
body.is-ventures .crown,
body.is-ventures .chromeglass {
  z-index: 6;
}

/* Over a work or a book. After the rule above and not before it, because the
   two have the same specificity and a book is always also a services: written
   the other way round the column's `6` would win and the shell would be under
   the book's full-bleed ground at 7. See the z-order block beside
   `.contents`. */
body.is-open .chrome,
body.is-book .chrome,
body.is-open .crown,
body.is-book .crown,
/* The way out of a work comes up with them. A body-level sibling that sits
   AFTER the chrome in the markup, so at one layer it paints over it — which is
   what a way out has to do with anything sharing its corner. */
body.is-open .opened {
  z-index: 8;
}

/* And last of all, the contents page. It is the top of the z-order and the
   crown is above even that, because the crown is where the key that closes it
   is. Written down here rather than up beside the sheet it belongs to for the
   same reason as the rule above: the contents page can be opened from inside
   the services section, and the section's `6` is later in the file than the
   sheet's own rules are. See the z-order block beside `.contents`. */
body.is-contents .crown {
  z-index: 10;
}

/* --- the opened service ----------------------------------------------------
   The strip is unfolded OUT OF THE CARD, so the layer is clipped to the card's
   rectangle on the frame it is mounted and that clip is opened to the frame by
   section.js. Everything in here exists to keep that one move readable.

   `clip-path` and not a scale on the layer: a scaled strip is a strip whose
   type is the wrong size for most of the transition, and the whole argument of
   the move is that what was inside the card IS the screen. The clip changes
   what you can see of it and nothing about what it is. */
.book {
  position: fixed;
  inset: 0;
  z-index: 7;
  /* `--paper`, like every other ground in the piece.

     It was `var(--ink)` — the one layer on the site that inverted the sheet on
     purpose. Opening a service on a pale sheet dropped the reader onto a black
     screen, which is the crossing `:root` spent seven paragraphs removing from
     the landing, reintroduced by a single declaration; and it cost the corner
     as well, because a crown drawn out of `--ink` over a ground of `--ink` is
     not there at all. There was a second rule further up correcting for that,
     and it has gone with this one.

     The strip inside is handed `paper-color` / `ink-color` off the root by
     `services/section.js` and clears to `uPaper` — `mix(uPaper, uInk, sil)` in
     `vs-anim-forge.js` — so the canvas and the layer it sits on are now the
     same colour by construction rather than by two files agreeing. What shows
     in the gutters between panels is the sheet, which is what was under the
     card the reader pressed. */
  background: var(--paper);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.28s var(--ease-out),
    visibility 0s linear 0.28s;
}

/* Two classes and not one. `is-book` is what pushes the column away, and it
   comes off the instant the fold starts so the section is already on its way
   back; `is-folding` is only what keeps the strip painted while the clip
   closes. One class would either hide the fold or hold the section back until
   it had finished. */
body.is-book .book,
body.is-folding .book {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

.book__stage {
  position: absolute;
  inset: 0;
  will-change: transform;
}

.book__stage > * {
  display: block;
  width: 100%;
  height: 100%;
}

/* The section goes INTO the card rather than stepping back from it: it pushes
   forward and dissolves while the strip unfolds out of the middle of it, so
   the two are one move and not a cut between two screens.

   After `body.is-services .services`, and that order is load-bearing — the two
   rules have the same specificity and this one has to win. */
body.is-book .services {
  transform: scale(1.09);
  opacity: 0;
}

/* --- the column on a narrow sheet ------------------------------------------
   The chips are what runs out of room first: three of them across a card that
   is now most of the screen wide is still three, but the card gets narrower
   faster than the type does. */
@media (max-width: 860px) {
  .svc__tag:nth-child(n + 3) {
    display: none;
  }
  .services__rail {
    top: 16vh;
    bottom: 16vh;
  }
}

@media (max-width: 620px) {
  .svc__tag:nth-child(n + 2) {
    display: none;
  }
}

/* --- reduced motion --------------------------------------------------------
   The piece is about a material moving, so this cannot be a no-op. What it
   does turn off is authored in js: the entry plays as a cut, the ribbon holds
   still, and the counters cut to the new digits instead of rolling. */
@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.01ms !important;
  }
  /* Transitions collapse on their own above; a keyframe animation does not,
     and the halo is the only one in the file. */
  .back__halo {
    animation: none !important;
  }
}

/* --- the stretch rails ------------------------------------------------------
   Two bands, fixed to the top and the bottom of the frame, in which the page
   is STRETCHED out of view rather than faded out of it. Built by
   `site/stretchrails.js`; everything about the shape of the pull lives in that
   file's displacement map and everything about where the bands are lives here.

   Fixed and not sticky, and not part of any section: the landing is three and
   a half viewports on a transformed track, and a boundary that travels with
   the content is a boundary the content passes THROUGH — which is the one
   thing a frame edge cannot be. These stay welded to the viewport and the run
   crosses them.

   Inserted immediately before the shell's own panes, so document order does
   the z-work: the run goes under the rails and the nav, the gauge, the counter
   and the filing stay over them, crisp. That is the correction the edge at the
   foot of the sheet never got — see the note where `edge` used to be mounted
   in `main.js`.

   Nothing in here paints. There is no wash, no sheen and no tint at either
   end: two full-width bands standing over a page that turns over would need a
   colour per polarity, and a boundary that has to be re-tuned per theme is a
   boundary that is painting rather than refracting. */
.srails {
  position: fixed;
  inset: 0;
  /* The landing's own layer, and the rails sit after it in document order. A
     backdrop reads what is already painted UNDER it, and the sections are fixed
     layers at 5 over a run at 2 — down there these bands would sample the
     ribbon and never see the page they stretch. The shell is at 6 and stays
     over them, which is the condition the whole thing is wired on. */
  z-index: 5;
  pointer-events: none;
  /* How deep each band is. Deep enough to be a boundary rather than a hairline
     of blur, short enough that neither band ever reaches the reading position
     in the middle of the frame. */
  --sr-rail: clamp(96px, 15vh, 210px);
  /* 0..1, the speed of the page under the rails, written by
     `stretchrails.js`. Declared here so the whole subtree resolves on the
     first frame, before js has written one. */
  --sr-flow: 0;
  opacity: 0;
  transition: opacity 0.8s var(--ease-out);
}

/* Brought in on the frame the gate opens, the way the index is — and now in
   every section the reader can actually be standing in.

   IT WAS THE LANDING ALONE, on an argument about axes: the column and the wall
   are full-bleed grounds, the contact sheet never leaves the frame, and a
   boundary saying the page is accelerating off the TOP and the BOTTOM is a
   claim about the wrong axis on a horizontal belt. That reading was too
   literal about what the bands measure. They are not a scroll indicator. They
   are what the EDGE OF THE FRAME is made of on this site — the statement that
   the sheet does not stop where the screen does — and that is as true of a
   wall of ventures and a photograph filling the frame as it is of a page of
   type. What changes between sections is only how hard the boundary breathes,
   and that is `--sr-flow`, which each section now hands over off its own
   speed: the landing and About off their `Flow`, the column off its panel
   velocity, the wall off its plane. A section that is standing still simply
   gets the resting depth, which is the depth the boundary has because it IS a
   boundary.

   Contact is the one that stays out, and for the reason the old note gave: it
   is a single screen that never travels, so there is no leaving the frame to
   describe. An opened WORK is in, and it is the strongest case on the site —
   the photograph is full-bleed and edge to edge, so the two bands are the only
   thing between it and a hard rectangular crop.

   All five carry `.is-in`, so they all outrank the one rule below that takes
   the bands away. */
body.is-home .srails.is-in,
body.is-about .srails.is-in,
body.is-services .srails.is-in,
body.is-ventures .srails.is-in,
body.is-open .srails.is-in {
  opacity: 1;
}

/* The contents overlay is the one thing that takes them out.

   It is a sheet of its own at `z-index: 9`, four layers over the rails, and
   what it covers it also replaces: a boundary describing the acceleration of a
   page nobody can see any more is two full-width backdrops being recomputed
   for nothing.

   Written with the same specificity as the rules above and placed AFTER them,
   which is what makes it win — `.is-in` is on the element in every one of
   those states and a shorter selector here would silently lose to all five. */
body.is-contents .srails.is-in {
  opacity: 0;
}

.srails__rail {
  position: absolute;
  left: 0;
  right: 0;
  height: var(--sr-rail);
  display: block;
}

.srails__rail--t { top: 0; }
.srails__rail--b { bottom: 0; }

.srails__pull,
.srails__frost {
  position: absolute;
  inset: 0;
  display: block;
}

/* --- the frost --------------------------------------------------------------
   The second half of a stretch, and it is not decoration on the first.

   Something moving fast enough to be pulled apart is also something you cannot
   quite resolve, and a pull with no softening reads as a lens rather than as
   speed — the page looks bent by glass instead of shifted by motion. The blur
   is small on purpose: it is the smear of a thing leaving, not a scrim.

   Masked with a ramp and not switched on across the band, because a blur that
   arrives at one radius draws the line the whole quadratic profile in the map
   exists to avoid. The three stops are that same curve by eye — nothing until
   past the middle of the rail, most of it in the last quarter.

   It samples the PULL, which is already painted beneath it: `backdrop-filter`
   reads everything under the element, siblings included, so the two compose
   into one surface rather than stacking as two. The saturate is not a look —
   a blur is an average and an average of a photograph is a greyer photograph,
   so a few per cent back is what stops the smear reading as fog. */
.srails__frost {
  backdrop-filter:
    blur(calc(1.2px + 2.6px * var(--sr-flow)))
    saturate(calc(1 + 0.12 * var(--sr-flow)));
  -webkit-backdrop-filter:
    blur(calc(1.2px + 2.6px * var(--sr-flow)))
    saturate(calc(1 + 0.12 * var(--sr-flow)));
}

.srails__rail--t .srails__frost {
  -webkit-mask-image: linear-gradient(to top, transparent 0%, rgba(0, 0, 0, 0.22) 58%, #000 100%);
  mask-image: linear-gradient(to top, transparent 0%, rgba(0, 0, 0, 0.22) 58%, #000 100%);
}

.srails__rail--b .srails__frost {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.22) 58%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.22) 58%, #000 100%);
}

/* Coarse pointers, and engines that do not resolve an SVG filter inside a
   backdrop — WebKit and Firefox parse the declaration and render nothing
   through it. Both get no rails at all rather than a frost with no pull: the
   blur on its own is the fade this effect was built instead of. */
.srails.is-flat {
  display: none;
}

/* And the same when it is the PULL that could not be built: `Glass` flags its
   own element rather than the container, so the frost has to be taken out from
   beside it. A frost with no pull under it is a blur at the edge of the frame,
   which is the fade this whole file was written instead of. */
.srails__pull.is-flat ~ .srails__frost {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .srails__frost {
    backdrop-filter: blur(1.2px);
    -webkit-backdrop-filter: blur(1.2px);
  }
}
