/* ------------------------------------------------------------
   stage.css, the composition layer

   site.css builds the workspace and hud.css draws the instrument on top
   of it. Both were doing their jobs and the result still photographed
   like a wireframe, because what was missing was not components. It was
   composition: the handful of moves that separate a page things were
   placed on from a page things were composed on.

   There are four of them here and nothing else.

     1. Value.   Somewhere dark to stand. A page made entirely of one
                 tonal band has no figure and no ground, so the eye has
                 nowhere to land and everything reads as equally
                 important, which is the same as reading as unimportant.
     2. Crop.    Objects that run off the edge of the frame. An object
                 centred inside its box has been placed. An object cut by
                 the frame has been composed, because something decided
                 where the cut falls.
     3. Overlap. Type that sits on the image rather than above it. The
                 moment two layers occupy the same square inch, the page
                 acquires depth it cannot get from a drop shadow.
     4. Grain.   The whole thing on one sheet of paper. Vector flatness is
                 the single loudest signal that nothing physical was ever
                 involved in making a page.

   None of these are decoration and none of them are a component. They are
   applied to what is already here.
   ------------------------------------------------------------ */


/* The mix percentages below are a floor, not a taste.

   A token mixed to 44% of the ink looks like a quiet label and measures
   4.1:1 on the darker skins, which is under AA for anything at body size.
   The bands suite found eighty-six runs of text in that state across the
   four files, none of which the token-level contrast suite could see,
   because it was checking token values rather than what actually lands on
   a ground. These are the values that clear 4.5:1 on the darkest of the
   four skins, which is the one that governs. */

/* --- 1. value: the inverted ground ----------------------------------

   .invert is a whole block that moves to the dark end of the skin. It is
   not a card and it does not sit on the page, it replaces the page for
   its own height, edge to edge, so the eye reads it as a different sheet
   rather than a widget.

   It re-points the surface tokens rather than setting colours directly.
   Everything inside it, headings, rules, mono labels, the hud's leader
   lines, keeps using the same token names it always did and simply comes
   out light on dark. That is the whole trick: one class, no component
   knows it is inside it. */
.invert{
  background:var(--ground);
  color:var(--on-ground);
  --bg:var(--ground);
  --bg-raised:var(--ground-2);
  --bg-sunken:#00000033;
  --bg-hover:var(--ground-2);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 68%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 15%, transparent);
  --line-strong:color-mix(in srgb, var(--on-ground) 28%, transparent);
  --grid:color-mix(in srgb, var(--on-ground) 6%, transparent);
  --hud-stroke:color-mix(in srgb, var(--on-ground) 22%, transparent);
  /* The accent has to come up to meet the dark or it goes muddy. Mixed
     with white rather than with a warm tint: the warm tint was fine for
     a vermilion accent and turned the dossier's phosphor green a sickly
     olive, and a utility that only works for one hue is not a utility. */
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  --accent-soft:color-mix(in srgb, var(--accent-base) 22%, transparent);   /* the base, not the lifted accent: a tint of the lifted one lightened the chip under its own text below AA */
  color-scheme:dark;
}

/* Run it to the edges of the document sheet. The sheet has padding so the
   prose has margins; an inverted block is not prose and should not. */
.invert.full{
  margin-inline:calc(var(--pad) * -1);
  padding-inline:var(--pad);
}
@media (min-width:921px){
  .doc .invert.full{
    margin-inline:calc(var(--docpad, 40px) * -1);
    padding-inline:var(--docpad, 40px);
  }
}


/* --- 2. crop: things that run off the frame --------------------------

   .bleed lets a figure leave the measure on the trailing side. The value
   is deliberately more than the padding it is cancelling, so the object
   is genuinely cut by the viewport rather than landing neatly against it.
   A figure that stops exactly at the edge looks like it fits. A figure
   that is cut looks like it continues. */
.bleed{margin-inline-end:calc(var(--bleed-amt, 12vw) * -1)}
.bleed-start{margin-inline-start:calc(var(--bleed-amt, 12vw) * -1)}
@media (max-width:920px){
  .bleed,.bleed-start{--bleed-amt:var(--pad)}
}


/* --- 3. overlap: type on the image -----------------------------------

   .comp is a single grid cell that several layers share, so the headline
   and the thing it is a headline for occupy the same square inch instead
   of queueing. Children opt into depth with .under and .over; nothing is
   positioned absolutely, so the tallest layer still sets the height and
   the block still reflows like a block. */
.comp{display:grid; isolation:isolate}
.comp > *{grid-area:1 / 1}
.comp > .under{z-index:0}
.comp > .over{z-index:2; pointer-events:none; align-self:var(--comp-align, end)}
.comp > .over :where(a,button){pointer-events:auto}

/* Type that lands on an image needs to survive whatever is behind it
   without a scrim, because a scrim is an admission that the composition
   did not work. This gives the glyph edges somewhere to end. */
.comp > .over .bigtype{
  text-shadow:0 1px 0 color-mix(in srgb, var(--bg) 55%, transparent);
}
@supports (paint-order: stroke){
  .comp > .over .bigtype{paint-order:stroke fill}
}


/* --- 4. grain: one sheet of paper ------------------------------------

   A single fixed turbulence tile over the entire viewport. It is doing
   more for how this site feels than any other forty lines in the project,
   which is slightly humbling: the difference between a page that looks
   rendered and a page that looks printed is mostly just noise.

   feTurbulence rather than an image because it is about nine hundred
   bytes of markup instead of a request, it scales to any viewport, and
   the frequency is a number I can tune rather than an asset I have to
   regenerate. stitchTiles keeps the seams invisible where it repeats.

   mix-blend-mode:overlay rather than a flat opacity so the grain darkens
   the lights and lifts the darks, the way real grain sits in an emulsion,
   instead of fogging the whole page evenly grey. */
.grain{
  position:fixed;
  inset:-120px;                 /* room to jitter without showing an edge */
  z-index:9000;
  pointer-events:none;
  opacity:.5;
  mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='.46'/%3E%3C/svg%3E");
  background-repeat:repeat;
}

/* The jitter is gone and the texture stays.

   House rule three says motion only when a hand causes it, and a
   full-viewport noise field crawling forever is the most unprompted
   motion it is possible to put on a page. It also runs headlong into
   WCAG 2.2.2, which gives anything that blinks or moves for more than
   five seconds a way to stop. Writing an exemption for my own grain
   would have been amending the rule to fit the thing that broke it.

   Nothing is really lost. The comment above this block already said the
   texture was doing the work and the jitter was the flourish, and a
   still grain is what a printed sheet has: film moves because film is
   twenty-four separate photographs a second, and this is one sheet of
   paper. It also stops a compositing pass running forever on every page
   in the site. */
[data-theme="dark"] .grain{opacity:.42; mix-blend-mode:soft-light}

/* and it is a screen effect, not an ink effect.

   This rule was dead for as long as it has existed. A stray closing brace sat
   between it and the rule above, left behind when the jitter keyframes came
   out, and a `}` with nothing open is a parse error the browser recovers from
   by discarding what follows it: this @media block. So every printed page on
   the site carried the full-viewport noise tile. On the CV that is a grey
   wash over the text and a 4.5MB PDF where 0.2MB will do, which is the
   difference between a file an application form accepts and one it rejects. */
@media print{ .grain{display:none} }


/* --- the kinetic width axis ------------------------------------------

   Bricolage carries a width axis from 75 to 100. motion.js writes --wdth
   from scroll velocity, so the display type narrows as the page moves and
   opens back out as it settles. It is not a transition on a hover, it is
   the type responding to the reader's hand, which is the only kind of
   motion on this site that has ever been worth having.

   Only the loudest type takes it. Narrowing body copy as somebody scrolls
   past it would be unreadable and obnoxious, in that order.

   Since v7.54 the type that wraps takes it as a scale from its left edge
   (--wscale, written beside --wdth), not on the axis: on the axis a
   heading could change its number of lines mid-scroll, and with scroll
   anchoring and Lenis that became a loop that shook 002 with no hand on
   it. Only the section number, which cannot wrap, keeps the true axis. */
.bigtype,
.cvbig,
.pane .overview h2,
.stripcard b{
  scale:var(--wscale, 1) 1;
  transform-origin:0 50%;
  font-optical-sizing:auto;
}

/* Nothing to respond to, so sit still at full width. */
@media (prefers-reduced-motion: reduce){
  :root{--wdth:100 !important;--wscale:1 !important}
}
@media print{ :root{--wdth:100 !important;--wscale:1 !important} }


/* ============================================================
   THE LANDING STAGE
   ============================================================

   The hero used to live inside .overview, which is capped at 1120px and
   padded on both sides. On a 1440 viewport that left a 320px strip of
   empty gridded desk down the right of the loudest thing on the site,
   and the drawing sat politely inside its margins like a figure in a
   report. It was a figure in a report.

   It is now a direct child of the pane, so it spans the whole view, and
   it is inverted, so it is the dark the rest of the page is light
   against. The three moves at the top of this file, all on one element:
   it is the value, the object inside it is cropped, and the headline is
   over it rather than above it. */

.herostage{
  position:relative;
  background:var(--ground);
  overflow:hidden;               /* this is what makes the crop a crop */
  border-bottom:1px solid var(--line);
  min-height:clamp(440px,72vh,760px);
  display:grid;
  grid-template-rows:auto 1fr auto;
  isolation:isolate;
}

/* The object runs off the right edge and off the bottom. Pushing it out
   of the frame is the whole point: a form that ends inside its box is an
   illustration of a form, and a form that leaves the box is an object
   that happens to be photographed here. The left column stays clear for
   the type, so the two are not fighting over the same pixels even though
   they share the same cell. */
.herostage .fieldbox{
  position:absolute;
  inset:0 -16% -14% 22%;
  z-index:0;
  pointer-events:none;
  --plate-ink:var(--on-ground);
  --plate-accent:var(--accent);
}
/* The canvas never takes part in layout.

   field.js's own comment says it: ogl's setSize writes width and height
   onto the canvas as inline pixels, which beat anything a stylesheet
   says, so the box has to be a wrapper the stylesheet owns and the
   canvas has to be sized from that. At desktop .fieldbox is absolutely
   positioned, so that held by accident. The mobile rule below put the
   box back in normal flow, which handed the canvas's inline width
   straight to the grid column: it measured, grew, re-measured, and grew
   again, and the hero's rail and caption ended up 2997px wide inside a
   390px frame. The clock and half of both captions were on the page and
   unreadable, clipped by the stage's own overflow.

   Absolute in both layouts closes the loop for good: the box is laid
   out, the canvas is painted inside it, and nothing the canvas does can
   change the size of what it is measuring. */
.herostage canvas.field{
  position:absolute;inset:0;
  display:block;width:100%;height:100%;
}

/* The ground is not flat. A single very soft radial off to the right
   where the form is, so the object looks lit from somewhere rather than
   printed on. One gradient; two would be a sunset.

   The light is the paper colour, not the accent. Lighting a green ground
   with an orange lamp makes brown, which is what the first version of
   this did: the petrol went to mud and the form washed out into it. The
   accent is a pigment on this page, not a light source, and the only
   place it touches the hero is the full stop. */
.herostage::before{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(112% 88% at 70% 30%,
    color-mix(in srgb, var(--on-ground) 11%, transparent) 0%,
    transparent 58%);
}

.herostage .hudrail{
  grid-row:1;position:relative;z-index:3;
  min-width:0;flex-wrap:wrap;
  margin:0;padding:12px clamp(20px,3vw,44px);
  border-bottom:1px solid var(--line);
}

.herostage .headline{
  grid-row:2;position:relative;z-index:2;
  align-self:end;
  /* Top padding since v7.30: the value line made the column taller than
     the band's middle row, and with none the kicker sat on the rail. */
  padding:clamp(18px,3vh,30px) clamp(20px,3vw,44px) clamp(18px,3vh,34px);
  max-width:min(16ch, 62%);
}
.herostage .bigtype{
  margin:0;
  /* Bigger than the old hero by a third, and allowed to be: it has a
     whole band to itself now instead of sharing one with a diagram. */
  font-size:clamp(50px,9.6vw,142px);
  line-height:.82;
  letter-spacing:-.035em;
  color:var(--on-ground);
  text-wrap:balance;
}
/* The full stop is the only hot mark in the band. One, so it reads as a
   mark rather than as colour. */
.herostage .bigtype .dot{color:var(--accent)}

/* Who, above the slogan. The name and title are the left rail's first
   line, and the rail is closed by default on a desk, so the first screen
   said "Design that proves itself" and nothing about whose. On a phone the
   rail's name line is the first thing on the page, so this one goes.
   max-content, because the headline box is measured in ch of its own small
   font and is narrower than the words in it; the slogan is allowed to
   overflow it a word per line, a kicker is not. */
.herostage .herokicker{
  margin:0 0 clamp(14px,2.2vh,26px);
  width:max-content;max-width:none;
  font:500 13.5px/1.4 var(--ui);letter-spacing:.004em;
  color:color-mix(in srgb, var(--on-ground) 78%, transparent);
}
.herostage .herokicker b{font-weight:650;color:var(--on-ground)}
/* On a phone the rail's name line sits directly above the hero and now
   carries the name and the slot-able title, so the kicker keeps only what
   that line does not: the role. */
@media(max-width:920px){
  .herostage .herokicker{font-size:12.5px;margin:0 0 12px;width:auto;max-width:100%}
  .herostage .herokicker b,
  .herostage .herokicker .sep,
  .herostage .herokicker .slot{display:none}
}
@media print{.herostage .herokicker{color:var(--text)}}

/* The value line under the slogan (v7.30). It is measured in its own ch,
   like the kicker, because the headline box is narrower than the words in
   it. It shares the band's left column with the edge of the token field,
   so it carries a halo of the ground colour: the points behind it dim
   instead of crossing the letters. */
.herostage .herodeck{
  margin:clamp(16px,2.4vh,26px) 0 0;
  width:min(50ch, 46vw);max-width:none;
  font:450 clamp(15px,1.2vw,18px)/1.5 var(--ui);
  letter-spacing:.002em;
  color:color-mix(in srgb, var(--on-ground) 88%, transparent);
  text-wrap:pretty;
  text-shadow:0 0 14px var(--ground),0 0 6px var(--ground),0 0 2px var(--ground);
}
@media(max-width:860px){
  .herostage .herodeck{width:auto;font-size:15px;margin-top:14px}
}
@media print{.herostage .herodeck{color:var(--text);text-shadow:none;width:auto}}

/* A single caption, bottom right, set like a plate number. Five floating
   callouts round a drawing is not annotation, it is an argument with the
   reader about where to look, and the reader loses. The other four
   claims all already exist further down the page, attached to the files
   that prove them, which is where a claim belongs. */
.herostage .herocap{
  grid-row:3;position:relative;z-index:3;
  min-width:0;
  display:flex;gap:18px;align-items:baseline;justify-content:space-between;
  flex-wrap:wrap;
  margin:0;padding:11px clamp(20px,3vw,44px);
  border-top:1px solid var(--line);
  font:400 max(10.5px,var(--floor))/1.5 var(--mono);
  letter-spacing:.06em;color:var(--text-2);
}
.herostage .herocap > span{min-width:0}
.herostage .herocap b{font-weight:600;color:var(--on-ground);letter-spacing:.09em;text-transform:uppercase}
.herostage .herocap a{color:var(--accent-text)}

@media(max-width:860px){
  .herostage{min-height:0;grid-template-rows:auto auto auto auto}
  .herostage .fieldbox{
    position:relative;grid-row:3;inset:auto;
    height:clamp(220px,40vh,300px);
    margin:0 -6% -4% 0;
    /* belt and braces: a grid item that may not outgrow its column,
       whatever ends up inside it */
    min-width:0;overflow:hidden;
  }
  .herostage .headline{grid-row:2;max-width:none;padding-top:22px}
  .herostage .herocap{grid-row:4}
}
@media print{
  .herostage{min-height:0;background:none;color:var(--text);border-bottom:1px solid #999}
  .herostage::before{display:none}
  .herostage .bigtype{color:var(--text);font-size:40px}
}


/* --- the case files, in the line grid (v7.45) ----------------------

   From v6.0 to v7.44 the case files were a track you pushed sideways, on
   the argument that a strip running off the screen says there is more
   than fits. Shad's note on 21 Oct 2026 was the better argument: this is
   where a recruiter decides which file to open, and the track showed one
   and a half files at a time and said little about what was in them. So
   the five files sit in one grid at every width, set on the desk's lines
   with room between them, under a question that marks the files that
   show what the reader is hiring for (app.js; a file's data-tags are its
   own claims and never more).

   Each file is a card with two faces (Shad's second note, the same day).
   The front is what a reader scans: the product, whose work it was, the
   title, the outcome and the time it takes. The back is what decides a
   click: what the work was, what is inside the file, which roles it
   speaks to, and the one link to it. On a desk the card turns over when
   the pointer rests on it; on a touch screen a tap turns it and a tap on
   its back opens the file; from the keyboard, its button turns it and
   the link on the back holds it turned. Both faces sit in one grid cell,
   so a card is as tall as its taller face and the back can never run
   past the card. Without a script there is nothing to turn the card, so
   the faces simply stack.

   v7.46 (Shad's next note): the grid comes directly under the hero band,
   ahead of the overview's text, and the three internal products join it
   under a row of their own, each flagged as one. The turn got softer: an
   ease that starts as gently as it lands, a little longer, and a longer
   breath before a resting pointer starts it. */

.work{margin:clamp(36px,4.6vw,64px) clamp(20px,3vw,44px) clamp(28px,3vw,48px)}
/* a link to #work lands clear of the sticky bar: directly under the hero
   (v7.46) it landed with its top under the bar on a desk and above the
   screen on a phone (deeplink) */
.work,.work [id]{scroll-margin-top:calc(var(--viewhead-real,var(--viewhead)) + 12px)}
.workgrid{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(16px,1.7vw,26px);
}
.workgrid > *{min-width:0}

/* the first row: the question, and the files that answer it. It spans
   the grid and is ruled off from the cards below, so the five files start
   level with each other under it. */
.workpick{
  grid-column:1 / -1;padding:0 0 clamp(18px,2vw,26px);border-bottom:1px solid var(--line);
  display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);gap:14px clamp(24px,3vw,48px);align-items:end;
}
.workpick .wp-q,.workpick .wp-a{display:flex;flex-direction:column;gap:10px}
.work .wp-k{margin:0;font:500 max(10px,var(--floor)) var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}
.workpick h2{margin:0;font:700 clamp(28px,2.8vw,40px)/1.02 var(--display);letter-spacing:-.025em;color:var(--text);text-wrap:balance}
.workpick .wp-lede{margin:0;font:400 14px/1.55 var(--ui);color:var(--text-2);max-width:48ch}
.workfilter{display:flex;flex-wrap:wrap;gap:6px}
.workfilter[hidden]{display:none}
.workfilter button{
  height:30px;padding:0 11px;border:1px solid var(--line);border-radius:var(--r-s);background:var(--bg-raised);
  display:inline-flex;align-items:center;
  font:600 max(10.5px,var(--floor))/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-2);cursor:pointer;
  transition:background-color var(--snap),border-color var(--snap),color var(--snap);
}
.workfilter button:hover{border-color:var(--text-3);color:var(--text)}
.workfilter button[aria-pressed="true"]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent-text)}
.workfilter button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* With no script there is nothing to press, so the row gives the same
   answer as a list; app.js swaps the list for the buttons. */
.pickmap{list-style:none;margin:6px 0 0;padding:0;display:grid;gap:7px;font:400 12.5px/1.4 var(--ui);color:var(--text-2)}
.pickmap b{font-weight:600;color:var(--text);margin-right:6px}
.workgrid[data-ready] .pickmap{display:none}
.workpick .wp-say{margin:0;font:500 max(10.5px,var(--floor))/1.45 var(--mono);color:var(--text-3);min-height:1.45em}

/* the last cell: what every file holds, so a reader knows the shape of
   whichever one they open */
.workhow{
  padding:clamp(20px,2.2vw,30px);display:flex;flex-direction:column;gap:12px;
  border:1px dashed var(--line-strong);border-radius:var(--r-m);
}
.workhow p{margin:0;font:400 13.5px/1.6 var(--ui);color:var(--text-2)}

/* the internal products' row (v7.46): ruled off from the case files above
   it, as the question is from the files below it */
.workgroup{
  grid-column:1 / -1;margin-top:clamp(6px,1vw,14px);padding-top:clamp(18px,2vw,26px);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 22px;
}
.workgroup p{margin:0;font:400 14px/1.55 var(--ui);color:var(--text-2);max-width:72ch}

/* a case file: two faces in one cell */
.stripcard{position:relative;display:grid;perspective:1800px}
.stripcard .flip{
  position:relative;display:grid;
  transform-style:preserve-3d;
  /* an ease that starts as gently as it lands, with no overshoot (v7.46:
     the v7.45 curve left at full speed, which read as a snap), and the
     turn's layer made before it is needed, so its first frame does not
     stall */
  transition:transform .9s cubic-bezier(.4,0,.2,1);
  will-change:transform;
}
.stripcard .face{
  grid-area:1 / 1;min-width:0;position:relative;
  display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:var(--r-m);background:var(--bg-raised);
  overflow:hidden;
  -webkit-backface-visibility:hidden;backface-visibility:hidden;
  transition:border-color var(--snap),box-shadow var(--snap);
}
.stripcard .back{transform:rotateY(180deg);padding:clamp(18px,1.7vw,24px);gap:13px}

/* turned: a pointer resting on it at a desk (after a breath, so a pointer
   crossing the grid does not set every card spinning), a tap on a touch
   screen (app.js), or keyboard focus on its back */
@media (hover:hover) and (pointer:fine){
  .workgrid[data-flip] .stripcard:hover .flip{transform:rotateY(180deg);transition-delay:.12s}
  .workgrid[data-flip] .stripcard:hover .front{pointer-events:none}
  .workgrid[data-flip] .stripcard:hover .back{pointer-events:auto}
}
.workgrid[data-flip] .stripcard[data-flipped] .flip,
.workgrid[data-flip] .stripcard:has(.back :focus-visible) .flip{transform:rotateY(180deg)}
/* only the face that is showing takes a hand */
.workgrid[data-flip] .stripcard .back{pointer-events:none}
.workgrid[data-flip] .stripcard[data-flipped] .front{pointer-events:none}
.workgrid[data-flip] .stripcard[data-flipped] .back,
.workgrid[data-flip] .stripcard:has(.back :focus-visible) .back{pointer-events:auto}
/* with reduced motion the faces trade places without turning */
@media (prefers-reduced-motion: reduce){
  .stripcard .flip,
  .workgrid[data-flip] .stripcard:hover .flip,
  .workgrid[data-flip] .stripcard[data-flipped] .flip,
  .workgrid[data-flip] .stripcard:has(.back :focus-visible) .flip{transform:none;transition:none}
  .workgrid[data-flip] .stripcard .back{transform:none;opacity:0;transition:opacity .2s linear}
  .workgrid[data-flip] .stripcard .front{transition:opacity .2s linear}
  .workgrid[data-flip] .stripcard[data-flipped] .back,
  .workgrid[data-flip] .stripcard:has(.back :focus-visible) .back{opacity:1}
  .workgrid[data-flip] .stripcard[data-flipped] .front,
  .workgrid[data-flip] .stripcard:has(.back :focus-visible) .front{opacity:0}
}
@media (prefers-reduced-motion: reduce) and (hover:hover) and (pointer:fine){
  .workgrid[data-flip] .stripcard:hover .back{opacity:1}
  .workgrid[data-flip] .stripcard:hover .front{opacity:0}
}
/* no script, no turning: the back sits under the front. Keyed on the
   scripting media feature, not on the attribute app.js sets when it has
   wired the turn (v7.49): keyed on the attribute, a desk whose first paint
   landed before app.js ran drew every card twice as tall, both faces
   stacked, and the grid collapsed when the script arrived (a layout shift
   of 0.27 on a throttled desk; Lighthouse never saw it only because the
   grid sits under the hero). A browser that does not know the feature
   treats the query as false and gets the turning layout, which is the
   right one wherever scripts run. */
@media (scripting:none){
  .workgrid .stripcard .flip{display:flex;flex-direction:column}
  .workgrid .stripcard .face{grid-area:auto;-webkit-backface-visibility:visible;backface-visibility:visible}
  .workgrid .stripcard .back{transform:none;border-top:0;border-radius:0 0 var(--r-m) var(--r-m)}
  .workgrid .stripcard .front{border-radius:var(--r-m) var(--r-m) 0 0}
  .workgrid .stripcard .wc-turn,.workgrid .stripcard .wc-back{display:none}
}

/* the front */
/* site.css gives every .bthumb a frame and a margin, and every .meta a
   margin and a grid; inside a face the face is the frame */
.stripcard .bthumb{display:block;position:relative;aspect-ratio:16/10;margin:0;background:var(--bg-sunken);overflow:hidden;border:0;border-bottom:1px solid var(--line);border-radius:0}
.stripcard .bthumb .bn{
  position:absolute;top:0;left:0;z-index:3;
  padding:5px 9px;background:var(--ground);color:var(--on-ground);
  font:600 max(10px,var(--floor)) var(--mono);letter-spacing:.12em;
}
/* The NHS card says it has phone apps before a word is read (v7.26): a
   flag in the thumbnail's other corner, and the diary app itself peeking
   over the dashboard, a still at the card's own scale. */
.stripcard .flag{
  position:absolute;top:0;right:0;z-index:3;
  padding:5px 9px;background:var(--accent);color:#fff;
  font:600 max(10px,var(--floor)) var(--mono);letter-spacing:.12em;text-transform:uppercase;
}
/* ink on the accent flips with it (v7.36, the phone cards' rule): white on
   the dark setting's pale accents read 2.7:1 */
[data-theme="dark"] .stripcard .flag{color:#0b1020}
/* an internal product's flag is set in ink, not the accent, so it reads as
   a different kind of thing from the case files' product flags */
.stripcard .flag-int{background:var(--text);color:var(--bg-raised)}
[data-theme="dark"] .stripcard .flag-int{color:var(--bg-raised)}
.stripcard .peek,.pane .cover .peek{
  position:absolute;right:6%;bottom:-5%;z-index:2;height:84%;width:auto;
  border-radius:9% / 4.2%;box-shadow:0 0 0 2px #15171b,0 14px 30px -12px rgba(0,0,0,.55);
  pointer-events:none;
}
/* the cover's own img rule gives every picture in it the cover's 16:7.6
   shape and a cover crop; the phone keeps its own */
.pane .cover .peek{right:3%;bottom:-7%;height:76%;aspect-ratio:390/844;max-height:none;object-fit:fill}

.stripcard .meta{margin:0;padding:16px 18px 16px;display:flex;flex-direction:column;gap:11px;flex:1}
.stripcard b{font:700 clamp(18px,1.5vw,22px)/1.12 var(--display);letter-spacing:-.02em;color:var(--text)}
.stripcard p{margin:0;font:400 13px/1.55 var(--ui);color:var(--text-2)}
.stripcard .bm{font:500 max(10px,var(--floor))/1.4 var(--mono);letter-spacing:.07em;color:var(--text-3);text-transform:uppercase}
/* whose work it was: the mark, then the role and the span */
.stripcard .wc-client{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;min-height:22px}
.wc-logo{display:block;height:18px;width:auto;flex:none}
:root:not([data-theme="dark"]) .wc-logo.inv{filter:brightness(0)}
[data-theme="dark"] .wc-logo.inv{filter:brightness(0) invert(1)}
.wc-word{font:700 14px/1 var(--ui);letter-spacing:-.01em;color:var(--text)}
.wc-role{font:500 max(10px,var(--floor))/1.3 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
.wc-title{margin:0}
/* what an internal product is, where a case file states its outcome */
.stripcard .wc-line{font:500 12.5px/1.45 var(--ui);color:var(--text);text-wrap:pretty}
.wc-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:10px 14px;flex-wrap:wrap}
/* the turn: on a desk the pointer turns the card anyway, so this is the
   keyboard's way in and a touch screen's sign that there is a back */
.wc-turn,.wc-back{
  flex:none;display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;
  border:1px solid var(--line-strong);border-radius:var(--r-s);background:var(--bg-raised);
  font:600 max(10.5px,var(--floor))/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-2);cursor:pointer;
  transition:border-color var(--snap),color var(--snap);
}
.wc-turn::after{content:'\21BB';font:500 14px/1 var(--ui);letter-spacing:0}
.wc-turn:hover,.wc-back:hover{border-color:var(--accent);color:var(--accent-text)}
.wc-turn:focus-visible,.wc-back:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* the back */
.stripcard .wc-back-k{display:flex;align-items:center;gap:10px;min-height:30px;padding-right:84px;font:600 14px/1.2 var(--ui);color:var(--text)}
.wc-back-k .bn{padding:5px 8px;background:var(--ground);color:var(--on-ground);font:600 max(10px,var(--floor)) var(--mono);letter-spacing:.12em}
.wc-back{position:absolute;top:clamp(18px,1.7vw,24px);right:clamp(18px,1.7vw,24px);z-index:3}
/* a pointer that turns the card back by leaving it needs no button for it */
@media (hover:hover) and (pointer:fine){ .wc-back{display:none} }
.stripcard .back .wc-sum{color:var(--text);font-size:13.5px;line-height:1.55}
/* what the file holds, as a list with real markers (a hung dash on a
   positioned li is the pattern that scrambled the CV's PDF) */
.wc-k{display:block;margin-bottom:6px;font:500 max(10px,var(--floor)) var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}
.wc-inside ul{margin:0;padding-left:15px;list-style:square;display:grid;gap:4px}
.wc-inside li{font:400 12.5px/1.45 var(--ui);color:var(--text-2)}
.wc-inside li::marker{color:var(--accent)}
.wc-tags{display:flex;flex-wrap:wrap;gap:5px}
.wc-tags i{
  font:500 max(10px,var(--floor))/1 var(--mono);font-style:normal;letter-spacing:.06em;text-transform:uppercase;
  color:var(--text-2);padding:5px 7px;border:1px solid var(--line);border-radius:var(--r-s);
  transition:background-color var(--snap),border-color var(--snap),color var(--snap);
}
.wc-tags i[data-on]{background:var(--accent-soft);border-color:var(--accent);color:var(--accent-text)}
/* the one link to the file, a primary button stretched over the back so a
   click anywhere on it opens the file; not .btn.primary, whose magnet
   would follow the pointer across the whole back */
.stripcard .wc-open{
  position:static;margin-top:auto;align-self:flex-start;
  display:inline-flex;align-items:center;height:38px;padding:0 18px;
  border-radius:var(--r-s);background:var(--btn,var(--accent));color:#ffffff;
  font:600 13.5px/1 var(--ui);text-decoration:none;transition:background-color var(--snap);
}
.stripcard .wc-open::after{content:'';position:absolute;inset:0;z-index:2}
/* darker on hover, never a filter: a filter makes the link the containing
   block of its own ::after, and the click target shrank to the button */
.stripcard .back:hover .wc-open{background:color-mix(in srgb,var(--btn,var(--accent)) 86%,#000)}
.stripcard .wc-open:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* A pressed question marks the files that answer it and quiets the
   pictures of the rest. Only pictures fade: text keeps its computed ink
   (house rule, v7.36), it just stops shouting. */
.workgrid[data-pick] .stripcard[data-hit] .face{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.workgrid[data-pick] .stripcard[data-tags]:not([data-hit]) .bthumb{filter:grayscale(1);opacity:.45}
.workgrid[data-pick] .stripcard[data-tags]:not([data-hit]) .wc-title b{color:var(--text-2)}
.workgrid .bthumb{transition:filter var(--snap),opacity var(--snap)}

@media screen and (max-width:1199px){
  .workgrid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media screen and (max-width:1000px){
  .workpick{grid-template-columns:minmax(0,1fr);align-items:start}
}
@media screen and (max-width:760px){
  .work{margin:26px var(--pad) 0}
  .workgrid{grid-template-columns:minmax(0,1fr)}
  /* A card is as tall as its taller face, and on a phone the back is the
     taller one: the front's text sat with 85px of nothing in it (002).
     The picture takes the slack instead, from its top left as the still
     is framed, and the text keeps together under it (v7.54). */
  .stripcard .front .bthumb{flex:1 0 auto}
  .stripcard .front .bthumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top left}
  .stripcard .front .meta{flex:0 0 auto}
}
@media print{
  .workgrid{grid-template-columns:1fr 1fr}
  .workfilter,.workpick .wp-say,.wc-turn,.wc-back{display:none}
  .workgrid[data-ready] .pickmap{display:grid}
  .stripcard .flip{display:flex;flex-direction:column;transform:none!important}
  .stripcard .face{grid-area:auto;-webkit-backface-visibility:visible;backface-visibility:visible;opacity:1!important}
  .stripcard .back{transform:none!important}
}


/* ============================================================
   THE CASE FILE HEADER
   ============================================================

   A case file opened as a white page with a kicker, a title, a lede, a
   four-column definition list and three equal columns headed The
   problem, The move, The proof. Every one of those columns had the same
   width, the same type size, the same spacing and the same weight, which
   is a layout that has decided all three are equally important. They are
   not. The problem is context, the move is the work, and the proof is
   the only part a hiring panel is actually reading for.

   So the header becomes a band, like the landing hero, in the file's own
   ground colour; the title goes to display size; the drawing is cut by
   the right edge instead of sitting politely in a 300px column; and the
   three columns stop pretending to be triplets. */

.doc > header.casehead{
  position:relative;
  margin:calc(var(--docy) * -1) calc(var(--docx) * -1) 34px;
  padding:22px var(--docx) 24px;
  background:var(--ground);
  overflow:hidden;
  border-bottom:1px solid var(--line);
  display:grid;
  grid-template-columns:minmax(0,1fr);
  column-gap:40px;
}
@media(min-width:1000px){
  .doc > header.casehead{grid-template-columns:minmax(0,1fr) minmax(240px,.62fr)}
  .doc > header.casehead > .kicker,
  .doc > header.casehead > h1,
  .doc > header.casehead > .lede{grid-column:1}
  .doc > header.casehead > .meta,
  .doc > header.casehead > .arc{grid-column:1 / -1}
}

.doc > header.casehead > h1{
  margin:10px 0 0;
  font:700 clamp(36px,5.6vw,82px)/.9 var(--display);
  letter-spacing:-.035em;
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
  color:var(--on-ground);
  max-width:17ch;text-wrap:balance;
}
/* The accent half of the title stops being a colour change and becomes a
   second line of the same sentence, set lighter. Forty accent-coloured
   marks on a page is not emphasis, it is wallpaper. */
.doc > header.casehead > h1 .hl{
  color:color-mix(in srgb, var(--on-ground) 66%, transparent);
  font-weight:500;
}
.doc > header.casehead > .kicker{color:var(--accent-text);margin:0}
.doc > header.casehead > .lede{
  margin-top:16px;max-width:52ch;
  font-size:15px;line-height:1.56;color:var(--text-2);
}
.doc > header.casehead > .lede em{font-style:normal;font-weight:600;color:var(--on-ground)}

/* Cut by the frame. The drawing used to be capped at 300px and parked in
   the right-hand column with air around it; now it runs past the edge of
   the sheet, which is the difference between an illustration of the idea
   and the idea photographed. */
.doc > header.casehead > .casemark{
  margin:0;max-width:none;align-self:center;
}
@media(min-width:1000px){
  .doc > header.casehead > .casemark{
    grid-column:2;grid-row:1 / 4;
    /* Wider than the column it is in, and pushed past the sheet edge by
       more than the padding it is cancelling. Cancelling the padding
       exactly lands the drawing flush against the edge, which looks like
       it happens to fit; overshooting it makes the cut deliberate.
       The negative margin and the extra width have to agree, or the grid
       column simply grows to fit and nothing overflows at all. */
    width:calc(100% + var(--docx) + 7%);
    margin-inline-end:calc((var(--docx) + 7%) * -1);
  }
}
@media(max-width:999px){
  .doc > header.casehead > .casemark{margin:22px 0 0;max-width:340px}
}

.doc > header.casehead > .meta{margin-top:26px;border-top:1px solid var(--line);padding-top:16px}
.doc > header.casehead > .meta dt{color:color-mix(in srgb, var(--on-ground) 62%, transparent)}
.doc > header.casehead > .meta dd{color:var(--on-ground)}

/* --- problem, move, proof: no longer triplets --------------------- */

.doc > header.casehead > .arc{
  margin-top:26px;
  grid-template-columns:minmax(0,.82fr) minmax(0,1.05fr) minmax(0,1.13fr);
  gap:30px;
  align-items:start;
}
@media(max-width:860px){
  .doc > header.casehead > .arc{grid-template-columns:minmax(0,1fr);gap:22px}
}
.doc > header.casehead > .arc .k{color:color-mix(in srgb, var(--on-ground) 62%, transparent)}
.doc > header.casehead > .arc b{color:var(--on-ground)}
.doc > header.casehead > .arc span{color:color-mix(in srgb, var(--on-ground) 72%, transparent)}

/* The outcome is the loud one and it comes first. It is what the case file
   exists to deliver; it was set at the same 15px as the other two, and then
   it was third in the reading order, after the problem and the move, which
   is the order the work happened in and not the order a reader needs it. */
.doc > header.casehead > .arc > div:first-child b{
  font:700 clamp(20px,2.3vw,30px)/1.08 var(--display);
  letter-spacing:-.028em;
  color:var(--accent-text);
  margin-top:7px;
}
.doc > header.casehead > .arc > div:first-child::before{background:var(--accent);height:3px}

@media print{
  .doc > header.casehead{background:none;border:0;border-bottom:1px solid #999;margin:0 0 20px;padding:0 0 16px}
  .doc > header.casehead > h1{color:#111;font-size:30px}
  .doc > header.casehead > h1 .hl{color:#444}
  .doc > header.casehead > .meta dd,
  .doc > header.casehead > .arc b{color:#111}
  .doc > header.casehead > .casemark{margin:0;max-width:260px}
}


/* ============================================================
   THE CASE FILE BODY
   ============================================================

   The header became a ground and then the direction stopped dead at its
   bottom edge. Below it the page went back to what it was: two and a
   half thousand words of cream on cream, every section the same tone,
   the same width and the same weight, scrolling for eight screens. A
   reader has no idea where they are in that, and nothing tells them
   which of the eight sections is the one that matters.

   So the body gets a rhythm, and it is the same rhythm on all four
   files, because they already share the spine it hangs on: every case
   has exactly one pull quote and exactly one outcome with a stats block
   in it. Dark header, light evidence, dark turn, light evidence, dark
   close. Five movements, the same five every time, and a reader who has
   seen one case file knows how the next one is shaped.

   Nothing here adds a word or moves a section. It is the same content,
   given somewhere to breathe and somewhere to land. */


/* --- the turn -------------------------------------------------------

   The pull quote is the sentence each case turns on, and it was set as
   indented prose a size and a half up from the body: a paragraph in
   italics, essentially. It is a band now, full width of the sheet, so
   the one line worth stopping at is the one place the page stops. */
.doc .sec .pull{
  margin:34px calc(var(--docx) * -1);
  padding:clamp(28px,4.4vh,46px) var(--docx);
  background:var(--ground);
  color:var(--on-ground);
  border:0;border-radius:0;
  --text-2:color-mix(in srgb, var(--on-ground) 66%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  font:600 clamp(21px,2.9vw,38px)/1.18 var(--display);
  letter-spacing:-.024em;
  /* no width axis on the pulled quote (v7.54): it is a paragraph on a
     full-bleed band, so narrowing reflowed its lines while the page moved
     and a scale would open a gap at the band's edge */
  max-width:none;
  text-wrap:balance;
}
.doc .sec .pull small{
  display:block;margin-top:14px;
  font:500 max(10px,var(--floor)) var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:color-mix(in srgb, var(--on-ground) 64%, transparent);
}
.doc .sec .pull em{font-style:normal;color:var(--accent-text)}

/* --- a link inside a sentence --------------------------------------------

   site.css opens with a{color:inherit;text-decoration:none}, which is the
   right default for a site that is mostly chrome: topbar items, nav rows,
   cards and buttons all carry their own affordance and would be spoiled by
   an inherited underline. The cost only shows up in running text, where a
   link inherits everything from the sentence around it and becomes
   genuinely invisible, failing WCAG 1.4.1 and, more plainly, never getting
   clicked. Fifteen of them had accumulated, including every cross
   reference between a case file and its deck, which is the one trip across
   this site the whole structure exists to support.

   A link that carries a class has been styled on purpose and is left
   alone. This is for the unclassed ones, the ones written inside a
   paragraph. .sec.ground relights --accent-text, so one rule covers paper
   and ground both; the underline is held off the baseline and kept pale
   until hover, so a sentence with two references in it still reads as a
   sentence.

   A .shelf-list item is excluded because its link is not written inside
   a paragraph: it is a card of three stacked lines, a title, a summary
   and a figure, and underlining it draws one line under all three. A
   block a reader clicks as a whole is not prose with a reference in
   it. The landing page never showed this because its shelf sits outside
   .doc; the 404 list put the same markup inside one. */
.doc p > a:not([class]),
.doc li:not(.shelf-list > li) > a:not([class]),
.doc td > a:not([class]),
.doc dd > a:not([class]),
.doc figcaption > a:not([class]),
.doc .ref > a:not([class]),
.herocap a:not([class]),
.herolede p > a:not([class]){
  color:var(--accent-text);
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:.18em;
  text-decoration-color:color-mix(in srgb, var(--accent-text) 42%, transparent);
  transition:text-decoration-color .15s ease, color .15s ease;
}
.doc p > a:not([class]):hover,
.doc li:not(.shelf-list > li) > a:not([class]):hover,
.doc td > a:not([class]):hover,
.doc .ref > a:not([class]):hover,
.herocap a:not([class]):hover,
.herolede p > a:not([class]):hover,
.doc dd > a:not([class]):hover,
.doc figcaption > a:not([class]):hover{
  text-decoration-color:currentColor;
}
.doc p > a:not([class]):focus-visible,
.doc li:not(.shelf-list > li) > a:not([class]):focus-visible,
.doc td > a:not([class]):focus-visible,
.doc dd > a:not([class]):focus-visible,
.doc figcaption > a:not([class]):focus-visible{
  outline:2px solid var(--accent);outline-offset:3px;border-radius:2px;
}
/* Print has no hover and no colour to spare, so the underline carries it
   alone and the href is already footnoted by the print stylesheet. */
@media print{
  .doc p > a:not([class]),
  .doc li:not(.shelf-list > li) > a:not([class]),
  .doc td > a:not([class]),
  .doc dd > a:not([class]),
  .doc figcaption > a:not([class]){
    color:inherit;text-decoration-color:currentColor;
  }
}


/* --- the close ------------------------------------------------------

   The outcome is the last thing anybody reads and it looked exactly like
   the seven sections before it. It is the page's conclusion, so it gets
   the page's second ground, and the numbers in it finally get to be the
   size the numbers deserve. */
.doc .sec#outcome{
  /* No negative bottom margin: outcome is the last numbered section but it
     is not the last thing in the sheet. Three of the four files put a live
     artifact under it, and all four carry source notes and a page footer,
     so pulling the band down to the sheet edge would slide it under them. */
  margin:40px calc(var(--docx) * -1) 34px;
  padding:clamp(30px,5vh,52px) var(--docx) clamp(26px,4vh,40px);
  background:var(--ground);
  color:var(--on-ground);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 68%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  --line-strong:color-mix(in srgb, var(--on-ground) 28%, transparent);
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  --bg-raised:color-mix(in srgb, var(--on-ground) 5%, transparent);
  color-scheme:dark;
}
.doc .sec#outcome > h2{
  font:700 clamp(26px,3.6vw,52px)/1.0 var(--display);
  letter-spacing:-.03em;
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
  color:var(--on-ground);max-width:20ch;
}
.doc .sec#outcome > h2 .hl{color:color-mix(in srgb, var(--on-ground) 66%, transparent);font-weight:500}
.doc .sec#outcome .plateno .pn{color:color-mix(in srgb, var(--on-ground) 42%, transparent)}

.doc .sec#outcome .stats{margin-top:28px;border-top:1px solid var(--line);padding-top:22px}
.doc .sec#outcome .stats > div{border:0;background:none;padding:0 20px 0 0}
.doc .sec#outcome .stats b{
  font:700 clamp(32px,4.4vw,60px)/.98 var(--display);
  letter-spacing:-.04em;
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
  color:var(--on-ground);
}
.doc .sec#outcome .stats span{color:color-mix(in srgb, var(--on-ground) 70%, transparent)}
.doc .sec#outcome .stats i{color:var(--accent-text)}
.doc .sec#outcome .prose p{color:color-mix(in srgb, var(--on-ground) 74%, transparent)}

/* The folio that closes the file sits on the ground with it. */
.doc .sec#outcome .secfoot{border-top-color:var(--line)}
.doc .sec#outcome .secfoot .fo,
.doc .sec#outcome .secfoot .to{color:color-mix(in srgb, var(--on-ground) 64%, transparent)}

@media print{
  .doc .sec .pull,
  .doc .sec#outcome{
    background:none;color:#111;margin-inline:0;padding-inline:0;
    border-top:1px solid #999;
  }
  .doc .sec#outcome > h2,
  .doc .sec#outcome .stats b{color:#111;font-size:22px}
  .doc .sec .pull{font-size:15px}
}


/* ============================================================
   THE CV
   ============================================================

   The CV is the one page on this site with a second audience that is not
   a person. It gets printed, saved as a PDF, and fed through applicant
   tracking systems that read the text and throw the design away. A dark
   ground prints as a slab of ink, costs somebody a toner cartridge, and
   in a few parsers takes the text with it.

   So the CV takes the family resemblance on screen and hands it back at
   the printer. Same ground, same display name, same accent; then the
   print stylesheet below strips every ground on the site back to paper,
   which protects this page and every other one at the same time. */

/* Written as .doc > header.cvhead, not .cvhead.

   site.css carries `.doc>header{background:none}` at (0,1,1), and a bare
   .cvhead is (0,1,0), so the ground was losing and the page rendered cream
   type on cream paper with the name all but invisible. This is the same
   trap that had .bigtype rendering at 24px for weeks. Matching the
   element's own shape rather than reaching for !important keeps the next
   rule that outranks it findable. */
.doc > header.cvhead{
  margin:calc(var(--docy) * -1) calc(var(--docx) * -1) 28px;
  padding:clamp(26px,4vh,40px) var(--docx) clamp(20px,3vh,28px);
  background:var(--ground);
  color:var(--on-ground);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 72%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  --line-strong:color-mix(in srgb, var(--on-ground) 30%, transparent);
  --bg-hover:color-mix(in srgb, var(--on-ground) 8%, transparent);
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  color-scheme:dark;
  align-items:flex-end;
}
.doc > header.cvhead .id h1,
.doc > header.cvhead .id h1.cvbig{
  color:var(--on-ground);
  /* Bricolage's width axis stops at 100, so the 106% this inherited was
     being clamped and quietly doing nothing. The live axis does the job
     instead, which at least means it moves with the page. */
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
}
.doc > header.cvhead .id .sub{color:color-mix(in srgb, var(--on-ground) 76%, transparent)}
.doc > header.cvhead .co{color:color-mix(in srgb, var(--on-ground) 72%, transparent)}


/* ============================================================
   PAPER
   ============================================================

   One rule rather than five. Every ground on the site reverts to paper
   at the printer: the landing hero, the case headers, the pull quotes,
   the closing bands, the observation panel and the CV. Left alone, each
   one of those would print as a filled rectangle, and a reader who hits
   command-P on a case file would get eight pages of solid colour.

   This sits last in the file so it outranks the per-component print
   rules above it rather than racing them. */
@media print{
  .invert,
  .herostage,
  .watch,
  .doc > header.cvhead,
  .doc > header.pagehead,
  .doc > header.casehead,
  .doc .sec .pull,
  .doc .sec.ground,
  .doc .sec#outcome{
    background:none !important;
    color:#111 !important;
    --bg:#fff;--bg-raised:#fff;--bg-sunken:#fff;
    --text:#111;--text-2:#333;--text-3:#555;
    --on-ground:#111;
    --line:#bbb;--line-strong:#888;
    color-scheme:light;
    margin-inline:0 !important;
    padding-inline:0 !important;
    box-shadow:none !important;
  }
  .invert *,
  .herostage *,
  .watch *,
  .doc > header.cvhead *,
  .doc > header.pagehead *,
  .doc > header.casehead *,
  .doc .sec .pull *,
  .doc .sec.ground *,
  .doc .sec#outcome *{
    color:inherit !important;
    background-image:none;
  }
  /* the one thing that should keep its colour is nothing: this is ink */
  .herostage::before{display:none}
}


/* --- the plain page header -----------------------------------------

   The last page without a ground. With the landing hero, About's panel,
   the CV and the four case files all opening on one, the colophon was
   the only file in the drawer that started on bare paper, and a set of
   seven where six agree and one does not reads as the odd one being
   unfinished rather than as variety.

   Same shape as the CV's, written at the same specificity and for the
   same reason: .doc>header{background:none} is (0,1,1) and would
   otherwise win. */
.doc > header.pagehead{
  margin:calc(var(--docy) * -1) calc(var(--docx) * -1) 32px;
  padding:clamp(28px,4.4vh,46px) var(--docx) clamp(22px,3.4vh,34px);
  background:var(--ground);
  color:var(--on-ground);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 72%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  --line-strong:color-mix(in srgb, var(--on-ground) 30%, transparent);
  --bg-hover:color-mix(in srgb, var(--on-ground) 8%, transparent);
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  color-scheme:dark;
}
.doc > header.pagehead .bigtype{color:var(--on-ground)}
.doc > header.pagehead .kicker{color:var(--accent-text)}
.doc > header.pagehead .lede{
  color:color-mix(in srgb, var(--on-ground) 76%, transparent);
  max-width:56ch;
}
.doc > header.pagehead .lede em{font-style:normal;font-weight:600;color:var(--on-ground)}


/* ============================================================
   THE CHROME
   ============================================================

   Everything below the top bar got rebuilt and the top bar itself stayed
   exactly what it was on day one: a wordmark, four nav links, a tinted
   rounded pill marking the active one, a grey search field with a
   magnifier and a key-cap badge, a filled button and a circular theme
   toggle. That is the default header of every piece of software shipped
   since about 2021. It is the most-looked-at ninety pixels on the site
   and the only part that still announced what it was made with.

   The fix is not decoration, it is to make the bar agree with the thing
   it sits on top of. This site is a case-file workspace, so the bar is
   an instrument panel: ruled into zones, labelled in the mono, and
   marking its state with a hard rule rather than a soft tint. */

/* Zones, divided by hairlines. A row of controls floating in even
   whitespace is a toolbar; a row of controls in ruled compartments is a
   panel, and the rules cost nothing but say which things belong
   together. */
.topbar{gap:0}
.topbar > .wordmark{padding-inline-end:16px;margin-inline-end:16px;border-inline-end:1px solid var(--line)}
.topbar .right{gap:0}
.topbar .right > *{padding-inline:12px}
.topbar .right > * + *{border-inline-start:1px solid var(--line)}
.topbar .right > *:last-child{padding-inline-end:0}

/* The nav speaks the same language as every other label on the site.
   Inter at 13px/500 is the voice of the interface; the mono with
   tracking is the voice of the instrument, and this bar is part of the
   instrument. */
.topbar .topnav{margin-left:0;gap:0}
.topbar .topnav a{
  position:relative;
  font:500 11px/1 var(--mono);
  letter-spacing:.11em;
  text-transform:uppercase;
  color:var(--text-3);
  padding:0 14px;
  height:var(--topbar);
  display:inline-flex;align-items:center;
  border-radius:0;
  background:none;
  transition:color var(--snap);
}
.topbar .topnav a:hover{color:var(--text);background:none}

/* The active file is marked, not highlighted.

   A tinted rounded rectangle behind the current item is the softest
   possible way to say where you are, and it is the one every framework
   ships. A rule sitting in the bar's own bottom edge is how a tab reads
   on a real panel: it is the same gesture the section folios and the
   .arc marks already make, so the bar stops being a separate idea. */
.topbar .topnav a[aria-current]{
  color:var(--text);
  background:none;
}
.topbar .topnav a[aria-current]::after{
  content:'';position:absolute;left:10px;right:10px;bottom:0;height:2px;
  background:var(--accent);
}

/* The command field. Same information, none of the costume: no sunken
   fill, no magnifying glass, no key cap with its own border radius
   pretending to be a physical key. A hairline box and the shortcut in
   the mono, which is what the status bar at the bottom of every page has
   been doing all along. */
.topbar .cmdk-btn{
  height:28px;border-radius:0;background:none;
  border:1px solid var(--line);
  font:500 max(10.5px,var(--floor))/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-3);
  transition:color var(--snap),border-color var(--snap);
}
.topbar .cmdk-btn:hover{color:var(--text);border-color:var(--line-strong)}
.topbar .cmdk-btn svg{display:none}
.topbar .cmdk-btn kbd{
  border:0;border-radius:0;padding:0;
  font:500 max(10.5px,var(--floor))/1 var(--mono);color:var(--text-3);letter-spacing:.06em;
}

/* Buttons and the theme switch: squared, hairline, labelled in the mono.
   The accent fill stays on the one primary action per view, because that
   is the rule and because a filled vermilion button is the loudest thing
   the chrome is allowed to do. */
.topbar .btn{
  border-radius:0;
  font:500 max(10.5px,var(--floor))/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
}
.topbar .iconbtn{border-radius:0}

/* transition:all animates layout properties too, so a control that
   changes padding or border width on hover reflows while it highlights.
   Four controls had it; these are the properties that actually change. */
.topbar .iconbtn{transition:color var(--snap),border-color var(--snap),background-color var(--snap)}
.topbar .btn{transition:color var(--snap),border-color var(--snap),background-color var(--snap)}

/* A phone has no Command key. The top bar offered ⌘K as its only search
   affordance, which on a touch screen is a piece of furniture naming a
   key the reader does not have: it reads as decoration, and the one
   control that opens the file search looked like a label. The magnifier
   is hidden on a desk because the shortcut says it better there; on a
   touch screen that swaps, and the glass comes back. */
@media(hover:none),(pointer:coarse){
  .topbar .cmdk-btn svg{display:block}
  .topbar .cmdk-btn kbd{display:none}
}

/* "Open to UK roles" was hidden below 920px, which is to say hidden from
   almost everyone who opens this link, since a portfolio sent to a
   recruiter is read on a phone. It is the single fact this whole site
   exists to deliver, and it appeared nowhere else on the page. The bar
   has the room: at 390px its contents come to 144 of 390 pixels. */
@media(max-width:920px){
  .topbar .status{display:inline-flex;align-items:center;gap:5px;font-size:max(10px,var(--floor));letter-spacing:.05em;white-space:nowrap}
}

@media(max-width:920px){
  /* The bar wraps onto two rows on a phone, so the vertical rules would
     be dividing things that are no longer beside each other. */
  .topbar > .wordmark{border-inline-end:0;padding-inline-end:0;margin-inline-end:12px}
  .topbar .right > * + *{border-inline-start:0}
  .topbar .right > *{padding-inline:6px}
  .topbar .topnav a{height:auto;padding:9px 11px}
  .topbar .topnav a[aria-current]::after{left:11px;right:11px}
}


/* ============================================================
   THE LANDING BRIDGE
   ============================================================

   Between the hero band and the strip sat three columns of roughly equal
   text: the claim, a list of four threads, and a bio panel, all the same
   size, all on the same cream, all starting on the same line. It is the
   same mistake the case headers made with problem / move / proof, in the
   one place on the site where a reader has just been hit by a 142px
   headline and is deciding whether to carry on.

   They are not equal. The claim is the argument, and it ends with the
   sentence the whole site is built to support. The threads and the bio
   are the supporting cast. So the claim goes across the top at a size
   between the hero and the body, and the other two sit under it. One
   decision, no new content, and the page gets a middle instead of a gap.

   The grid is re-declared at the same breakpoint site.css uses, so this
   replaces that rule rather than fighting it at a different width. */
@media(min-width:1181px){
  .herogrid{
    grid-template-columns:minmax(0,1.1fr) minmax(0,.86fr);
    column-gap:44px;
  }
  .herogrid > .herolede{grid-column:1 / -1;max-width:none}
}

.herolede .lede{
  font:400 clamp(17px,1.85vw,25px)/1.42 var(--ui);
  letter-spacing:-.011em;
  color:var(--text-2);
  max-width:46ch;
}
/* The emphasised runs are the claim itself; everything round them is the
   setting. Weight rather than colour, because the accent on this page is
   already spoken for by the full stop in the hero and the primary
   action, and a third use would make it decoration. */
.herolede .lede b{font-weight:600;color:var(--text)}

/* The actions sit on a rule, so the claim closes rather than trailing
   off into a row of buttons. */
.overview .actions{
  margin-top:24px;padding-top:22px;
  border-top:1px solid var(--line);
}

/* The threads list already numbers itself; the heading was bracket
   notation at the same size as the rows, so the section had no top. */
.threads > h3{
  font:600 max(9.5px,var(--floor)) var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-3);
}

@media(max-width:1180px) and (min-width:821px){
  .herogrid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
  .herogrid > .herolede{grid-column:1 / -1}
}


/* --- the landing's closing ground -----------------------------------

   Two things were wrong with the shelf at the foot of the landing page.

   Its text ran flush to the left edge of the viewport. It is a direct
   child of main.view rather than of the pane, which is what lets it span
   the full width, but the pane is where the horizontal padding lived, so
   the heading and all sixteen links sat hard against the glass with no
   margin at all. Nothing else on the site does that.

   And the page ended on pale cream with small text, while every other
   page now closes on a ground: the case files on their outcome band, the
   colophon and the CV on their headers. The landing is the one page that
   has to leave an impression and it was the one page that just stopped.

   It closes on the ground now, which also completes the rhythm the hero
   starts: dark, light, the files, dark. */
.view > .shelf{
  margin-top:40px;
  padding:clamp(34px,5.5vh,58px) clamp(20px,3vw,44px) clamp(30px,4.5vh,46px);
  background:var(--ground);
  color:var(--on-ground);
  border-top:1px solid var(--line);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 74%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 62%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  /* --line-strong and --bg-hover are the other half of inverting a
     ground, and this block only ever redefined --line. .btn draws its
     outline from --line-strong, so the contact buttons that close this
     section were drawing a dark ink at 24% alpha onto a near-black
     ground: darker than the thing behind them, which is to say gone.
     They read as indented text, which is the one thing a button must
     not do. Every ground that overrides --line needs these too. */
  --line-strong:color-mix(in srgb, var(--on-ground) 30%, transparent);
  --bg-hover:color-mix(in srgb, var(--on-ground) 8%, transparent);
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  color-scheme:dark;
}
.view > .shelf .shelf-head h3{
  font:700 clamp(24px,3vw,40px)/1.02 var(--display);
  letter-spacing:-.03em;
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
  color:var(--on-ground);
  max-width:18ch;
}
.view > .shelf .shelf-head p{
  margin-top:12px;font-size:13.5px;max-width:52ch;
  color:color-mix(in srgb, var(--on-ground) 74%, transparent);
}
.view > .shelf .shelf-list{margin-top:30px}
/* The five spines stand at one end of their shelf by design (a spine is
   narrow, plates.js), and at the band's full 1340px the shelf ran on
   empty for four fifths of a tinted panel: the placeholder look the
   scenes were rebuilt to be rid of (v7.54). On a desk the shelf is the
   length of a shelf, under the paragraph it illustrates. */
@media(min-width:701px){ .view > .shelf .secplate{max-width:640px;margin-top:22px} }
.view > .shelf .shelf-list b{color:var(--on-ground)}

/* A hover that animates padding moves the text it is highlighting, and
   every other list on the site answers the cursor with its accent rather
   than by shuffling. Same gesture, no reflow. */
.view > .shelf .shelf-list a{transition:color var(--snap)}
.view > .shelf .shelf-list a:hover{padding-left:0}
.view > .shelf .shelf-list a:hover b{color:var(--accent-text)}

@media print{
  .view > .shelf{background:none;color:#111;--on-ground:#111;--text:#111;--text-2:#333;--text-3:#555;--line:#bbb}
  .view > .shelf .shelf-head h3{color:#111;font-size:18px}
}


/* ============================================================
   THE BODY OF A CASE FILE
   ============================================================

   A case file is about twelve and a half thousand pixels tall. The dark
   header, the pull band and the closing band account for roughly fifteen
   hundred of them. The other eighty-eight per cent is pale cream, 11 to
   15px text, and hairline diagrams at one scale, eight sections deep,
   every one of them the same shape.

   So the art direction was punctuation around a document rather than the
   page itself, and the only screen on the site that actually looked
   art-directed was the landing hero. Adding more bands would not have
   fixed that; the bands were never the problem. Three things were
   missing from the body, and they are the same three the hero has.

     Scale.  Nothing in the body went above 25px. In the hero type runs to
             142. A page where every element is within a few points of
             every other has no hierarchy to read, only a length.
     Weight. A section opened with a 44px number and a 25px heading, which
             is a heading, not an opening.
     Crop.   Every figure sat in the same measure with the same margins as
             the prose beside it, so eight sections of evidence read as
             one continuous column of paragraph.

   None of this adds a word or moves a section. It is scale and edges. */

/* --- the opening of a section ------------------------------------- */

/* The folio number becomes the thing you see first. Big numerals are the
   oldest editorial device there is and the one the reference drawings
   lean on hardest; at 44px it was a label, and at 120 it is the mark that
   tells you where you are in a long document without reading anything. */
.doc .sec .plateno{
  align-items:end;
  margin-bottom:22px;
  padding-bottom:10px;
  border-bottom:1px solid var(--line);
}
.doc .sec .plateno > .pn{
  font:700 clamp(54px,8.2vw,124px)/.76 var(--display);
  letter-spacing:-.05em;
  font-stretch:calc(var(--wdth, 100) * 1%);
  border-bottom:0;
  padding-bottom:0;
  color:var(--text);
}
.doc .sec .plateno > .pc{
  align-self:end;
  padding-bottom:10px;
  color:var(--text-3);
}

/* ---- the entrance (v7.52, rebuilt in v7.53) ---------------------------
   v7.52 drew each section's number a second time, huge and faint, behind
   content that already showed it, and it read as an accident: behind a
   still on 002, through the heading on About. The number is drawn once,
   in the rail. As the section arrives its digits roll like an odometer to
   their value (stagecraft.js builds the reels and puts the text back when
   they stop), and the rail's rule draws across under it with the scroll.
   A browser without view timelines keeps the rule as it was. */
.doc .sec .plateno{position:relative}
.pn .odo{display:inline-block;vertical-align:top;height:.8em;margin:-.02em 0;overflow:hidden}
.pn .odo > span{display:block}
.pn .odo > span > i{display:block;height:.8em;line-height:.8em;font-style:normal;text-align:center}
.pn .odo > span > i::before{content:attr(data-d)}
.pn .odo-vh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(prefers-reduced-motion:no-preference){
  @supports (animation-timeline: view()){
    .doc .sec{view-timeline:--sec block}
    .doc .sec .plateno,.doc .sec.ground .plateno{border-bottom-color:transparent}
    .doc .sec .plateno::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;background:var(--line);transform-origin:0 50%;animation:ctx-arrow linear both;animation-timeline:--sec;animation-range:entry 10% cover 28%}
    /* the foot is a thin bar, so its own entry is a few pixels of scroll:
       the rule draws over the first quarter of its passage instead */
    .doc .sec .secfoot{view-timeline:--foot block}
    .doc .sec .secfoot .arrow{transform-origin:0 50%;animation:ctx-arrow linear both;animation-timeline:--foot;animation-range:entry 0% cover 25%}
  }
}
/* the rail's rule and the foot's draw themselves across as they enter;
   the foot's arrowhead rides its end */
@keyframes ctx-arrow{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* The heading follows the number at display scale rather than at barely
   more than body size. 58px against 14px body is a real interval; 25px
   against 14px is a bold paragraph. */
.doc .sec > h2{
  font:700 clamp(28px,4.4vw,58px)/1.02 var(--display);
  letter-spacing:-.032em;
  scale:var(--wscale, 1) 1;transform-origin:0 50%;
  max-width:20ch;
  text-wrap:balance;
}
.doc .sec > h2 .hl{
  color:color-mix(in srgb, var(--text) 54%, transparent);
  font-weight:500;
}
.doc .sec > h2 .no{
  display:block;
  font:500 max(10px,var(--floor)) var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-3);margin:0 0 10px;
}

/* The standfirst is the bridge between a 58px heading and 14px prose, so
   it has to sit between them rather than at the bottom. */
.doc .sec > .standfirst{
  margin-top:16px;
  font:400 clamp(15px,1.55vw,19px)/1.5 var(--ui);
  letter-spacing:-.008em;
  color:var(--text-2);
  max-width:54ch;
}


/* --- the evidence runs wider than the prose ------------------------

   Text keeps the measure, because a measure is what makes text readable.
   Figures do not: they go to the edges of the sheet. That one difference
   is most of what separates a magazine from a report, and it costs
   nothing but a negative margin. */
.doc .sec .fig,
.doc .sec .diagram,
.doc .sec .board,
.doc .sec .flow,
.doc .sec .stats{
  margin-inline:calc(var(--docx) * -1);
}
/* captions and anything that is really a line of text stay in the
   measure, indented back to it */
.doc .sec .fig > figcaption,
.doc .sec .diagram > figcaption{
  margin-inline:var(--docx);
}
@media(max-width:700px){
  .doc .sec .fig,
  .doc .sec .diagram,
  .doc .sec .board,
  .doc .sec .flow,
  .doc .sec .stats{margin-inline:0}
  .doc .sec .fig > figcaption,
  .doc .sec .diagram > figcaption{margin-inline:0}
}

/* Sections need air between them now that each one opens loudly. */
.doc .sec{margin-top:clamp(56px,9vh,104px)}

@media print{
  .doc .sec .plateno > .pn{font-size:26px}
  .doc .sec > h2{font-size:19px}
  .doc .sec > .standfirst{font-size:12px}
  .doc .sec .fig,.doc .sec .diagram,.doc .sec .board,
  .doc .sec .flow,.doc .sec .stats{margin-inline:0}
}


/* --- the plate behind the turn --------------------------------------

   The one band each case file turns on was a dark rectangle with a
   sentence on it. A dark rectangle is not an image, and the body of
   these pages had no image in it at all: eight sections of hairline
   diagrams at one scale, which is why the only screen on the site that
   read as art-directed was the landing hero.

   This is the same one-bit renderer the observation panel on About uses
   and the same noise function the hero field is built from, seeded
   differently per file so the four are visibly different objects rather
   than one texture used four times. It is the house material, and the
   point of a house material is that it turns up in more than one house.

   Behind the type, not beside it: the quote stays the subject and the
   plate is what it is printed on. */
.doc .sec .pull{position:relative;isolation:isolate;overflow:hidden}
/* Written at the same depth as the lift rule below it.

   As a bare .pullfield this was (0,1,0) and the rule that lifts the
   quote above the plate, .doc .sec .pull > *, is (0,3,0). So the canvas
   lost position:absolute, joined the layout at its own size, and turned
   a 300px band into a 900px slab with the sentence pushed off the
   bottom of it. Same trap as .bigtype and .cvhead: match the element's
   real depth rather than reaching for !important. */
.doc .sec .pull > .pullfield{
  /* -1, not 0 (v7.54). The sentence is bare text in the blockquote, and
     text paints under any positioned child at z-index 0, so the cloud was
     printed over the quote: each line bright where the mask is clear and
     greyed by the dots from a third of the way along. The band isolates,
     so -1 lands between its ground and its words and goes no further. */
  position:absolute;inset:0;z-index:-1;
  width:100%;height:100%;display:block;
  pointer-events:none;
  /* low enough that the sentence is never fighting it, high enough that
     the band is clearly an object and not a fill */
  opacity:.72;
  --dither-ink:color-mix(in srgb, var(--on-ground) 58%, var(--accent));
  --dither-paper:var(--ground);
  /* cropped: the form runs off the right of the band the way the hero's
     runs off the right of the screen */
  mask-image:linear-gradient(90deg, transparent 0%, #000 34%, #000 100%);
}
.doc .sec .pull > *:not(.pullfield){position:relative;z-index:1}
/* the quote's own text nodes sit directly in the blockquote and cannot be
   lifted; the plate goes under them instead (z-index -1, above) */
.doc .sec .pull{z-index:0}
.doc .sec .pull small{position:relative;z-index:1}

@media print{ .pullfield{display:none} }


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

   The real answer to a case file that reads as a document with three
   dark interruptions is not a fourth interruption. It is that the page
   should alternate.

   Eighty-five per cent of a twelve-thousand-pixel page was one tone.
   Every section opened the same way, on the same cream, and the three
   bands read as punctuation around a report rather than as the page
   having a design. Alternating section by section is a decision a person
   makes and a template does not: the scroll gets a pulse, each section
   is visibly a separate thing rather than more of the last one, and the
   evidence inside a dark section has somewhere to sit.

   It works at all because of how .invert was built: a ground re-points
   the surface tokens rather than setting colours, so every diagram,
   rule, label and table inside one comes out light on dark without a
   single component knowing it moved. Tagged in the markup rather than
   with :nth-of-type, so which sections are dark is visible where
   somebody editing the page will look, and the count runs backwards from
   the outcome so a file never closes on two grounds in a row. */
.doc .sec.ground{
  margin-inline:calc(var(--docx) * -1);
  padding:clamp(40px,6vh,76px) var(--docx) clamp(36px,5vh,62px);
  background:var(--ground);
  color:var(--on-ground);
  --bg:var(--ground);
  --bg-raised:var(--ground-2);
  --bg-sunken:color-mix(in srgb, #000 28%, var(--ground));
  --bg-hover:var(--ground-2);
  --text:var(--on-ground);
  --text-2:color-mix(in srgb, var(--on-ground) 74%, transparent);
  --text-3:color-mix(in srgb, var(--on-ground) 60%, transparent);
  --line:color-mix(in srgb, var(--on-ground) 16%, transparent);
  --line-strong:color-mix(in srgb, var(--on-ground) 30%, transparent);
  --grid:color-mix(in srgb, var(--on-ground) 6%, transparent);
  --hud-stroke:color-mix(in srgb, var(--on-ground) 24%, transparent);
  /* lifted from --accent-base rather than --accent, because a property
     cannot mix itself; 64% keeps the drawings' accent visible on the
     deep blue's dark ground and 55% carries text at AA on every file's */
  --accent:color-mix(in srgb, var(--accent-base) 64%, white);
  --accent-text:color-mix(in srgb, var(--accent-base) 55%, white);
  --accent-soft:color-mix(in srgb, var(--accent-base) 20%, transparent);   /* the base, not the lifted accent: a tint of the lifted one lightened the chip under its own text below AA */
  --shadow:none;
  color-scheme:dark;
}
/* the figures inside a ground already bleed to the sheet edge, and the
   ground now owns that padding, so they bleed to the ground's edge
   instead of punching a hole through it */
.doc .sec.ground .fig,
.doc .sec.ground .diagram,
.doc .sec.ground .board,
.doc .sec.ground .flow,
.doc .sec.ground .stats{margin-inline:0}

/* A ground carries its own folio rule rather than the page's. */
.doc .sec.ground .plateno{border-bottom-color:var(--line)}
.doc .sec.ground .secfoot{border-top-color:var(--line)}

/* Two grounds meeting would read as one tall section, so a paper
   section between them keeps its own top rule and a ground following
   the band it turns on does not double up. */
.doc .sec.ground + .sec.ground{margin-top:0;padding-top:clamp(28px,4vh,48px)}

@media(max-width:700px){
  .doc .sec.ground{margin-inline:calc(var(--docx) * -1);padding-inline:var(--docx)}
}


/* --- depicted software keeps its paper -----------------------------

   A ground re-points the surface tokens and everything inside comes out
   light on dark, which is exactly right for prose, rules, labels and
   diagrams drawn in those tokens. It is exactly wrong for a drawing of
   somebody's software.

   The before-and-after on case file 001 is a rebuild of a real
   interface: translucent status chips, a teal that belongs to that
   product, a table with its own header. Those were drawn against paper,
   and composited against a near-black ground the chips fell to 1.0:1.
   The text was still on the page and could not be read at all.

   The colophon has always said an embedded product keeps its own design
   system and the seam between the two is a one-pixel rule. So that is
   what happens here: a depicted interface inside a ground gets paper
   back, and reads as a screen sitting on a dark desk rather than as page
   content that has been swallowed. It is the same way the product
   screenshots two sections further down already read, and they were the
   best-looking thing on the page.

   The values are a neutral paper rather than the skin's own, because
   what sits on them is somebody else's palette, not this site's. The
   accent is left alone and only darkened enough to hold on a light
   surface. */
/* Only the outer frame, not what is drawn inside it.

   The first version listed .board and table here too, and that was
   wrong in an instructive way: the before-and-after on case file 001 is
   a dark-themed dashboard, so .board paints itself near-black. Handing
   it light text tokens put dark text on a dark board. The frame is this
   site's surface and takes the site's paper; everything inside the
   frame is somebody else's product and is left alone entirely. */
.doc .sec.ground :where(.demo){
  /* a cool neutral since v7.46, with the site's paper (it was a warm one) */
  --bg:#f8f9fb;
  --bg-raised:#ffffff;
  --bg-sunken:#edf0f4;
  --bg-hover:#e5e9ef;
  --text:#10151d;
  --text-2:#3c4554;
  --text-3:#566072; /* v7.36: a lighter third text read 4.1:1 on the sunken paper */
  --line:rgba(14,19,27,.15);
  --line-strong:rgba(14,19,27,.27);
  --grid:rgba(14,19,27,.05);
  /* 84% left the teal labels at 2.94:1 on their own 12% tint. These
     chips sit on an already-tinted paper, so the accent has to go
     further down than it would on plain white. */
  /* from the base, not the ground's lifted accent: 58% of a lifted teal
     and black is a mid teal that read 4.0:1 on this paper */
  --accent:var(--accent-base);
  --accent-text:color-mix(in srgb, var(--accent-base) 58%, #000);
  --accent-soft:color-mix(in srgb, var(--accent-base) 12%, transparent);   /* the base, not the lifted accent: a tint of the lifted one lightened the chip under its own text below AA */
  color-scheme:light;
}
.doc .sec.ground :where(.demo){
  background:var(--bg);
  color:var(--text);
  /* the one-pixel seam */
  border:1px solid color-mix(in srgb, var(--on-ground) 26%, transparent);
}
.doc .sec.ground .demo{padding-bottom:0}


/* ============================================================
   THE CASE FILE OPENS ON AN OBJECT
   ============================================================

   The landing page has a three-thousand-point form filling half the
   frame. No other page on the site had anything of the kind, and that is
   the whole of why only the landing hero ever looked art-directed. A
   case file opened on a dark rectangle containing a kicker, a title, a
   lede, a four-column table of role and client and duration, and three
   columns of problem, move and proof. That is a dark table of contents.
   It is information, and there is a great deal of it, and there is
   nothing in it to look at.

   So the band is a hero now. One object, cropped by the right edge, with
   the title over it and a single caption under it: the same five moves
   the landing hero makes, in the file's own colour, built from a figure
   the file actually states where it has one.

   The facts did not get smaller or go away. They moved one block down,
   onto paper, where a four-column table belongs. */

.doc > header.casehead{
  position:relative;
  /* About half a screen since v7.22 (Shad: smaller, the product right
     under it), down from clamp(420px,58vh,640px). */
  min-height:clamp(320px,50vh,500px);
  grid-template-columns:minmax(0,1fr);
  align-content:center;
  padding-block:clamp(24px,3.5vh,40px);
  overflow:hidden;
  isolation:isolate;
}

/* The object. Same geometry as the landing hero: off the right edge, off
   the bottom, with the left column left clear for the type so the two
   share the frame rather than fight over it. */
.casefield{
  position:absolute;
  inset:-6% -14% -16% 50%;
  z-index:0;
  pointer-events:none;
  /* as .secplate-box: without this the browser takes a finger's drag for
     scrolling and the plate never sees a move */
  touch-action:pan-y;
  --plate-ink:var(--on-ground);
  --plate-accent:var(--accent);
}
.casefield canvas.field,
.casefield canvas.fplate{position:absolute;inset:0;display:block;width:100%;height:100%}

/* The contour ground, under the drawing and inside the same box. Before
   this, the only hero on the site with anything behind its object was the
   landing screen, because field.js builds its ground inside the sphere it
   belongs to and nothing else could ask for one. Every case file, the CV
   and the colophon now stand on their own piece of ground, each from its
   own seed, which is what makes them read as the same place rather than
   the same drawing. */
.casefield canvas.terrain{
  position:absolute;inset:0;display:block;width:100%;height:100%;
  pointer-events:none;z-index:0;
  /* The box this sits in is inset from the band, so without a mask the
     contours stop at a hard vertical edge and the ground reads as a
     rectangle someone pasted on. Fading it out at the boundary is what
     makes it ground: it has no edge, it just stops being legible. */
  -webkit-mask-image:radial-gradient(130% 115% at 62% 50%, #000 34%, transparent 86%);
  mask-image:radial-gradient(130% 115% at 62% 50%, #000 34%, transparent 86%);
}
.casefield canvas.fplate{z-index:1}
.casefield{--terrain-ink:var(--on-ground)}
.doc > header.cvhead > .casefield,
.doc > header.pagehead > .casefield{--terrain-ink:var(--on-ground)}

/* Lit from where the object is, in the paper colour rather than the
   accent: an orange lamp on a green ground makes mud, which the landing
   hero already taught me once. */
.doc > header.casehead::before{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(104% 86% at 74% 36%,
    color-mix(in srgb, var(--on-ground) 10%, transparent) 0%, transparent 58%);
}

.doc > header.casehead > .kicker,
.doc > header.casehead > h1,
.doc > header.casehead > .plain,
.doc > header.casehead > .lede,
.doc > header.casehead > .casecap{position:relative;z-index:2}
/* The plain line under the headline (v7.30): what it is, for whom, and the
   problem, in the words a recruiter files it under. Three short lines, each
   led by its label, so it reads as a record and not as a second lede. */
.doc > header.casehead > .plain{
  max-width:48%;margin:14px 0 2px;
  display:grid;gap:3px;
  font:450 13px/1.45 var(--ui);
  color:color-mix(in srgb, var(--on-ground) 84%, transparent);
}
.doc > header.casehead > .plain > span{display:grid;grid-template-columns:64px minmax(0,1fr);column-gap:8px;align-items:baseline;text-wrap:pretty}
.doc > header.casehead > .plain i{
  font-style:normal;
  font:500 max(9.5px,var(--floor))/1 var(--mono);letter-spacing:.09em;text-transform:uppercase;
  color:color-mix(in srgb, var(--on-ground) 58%, transparent);
}
@media(max-width:860px){.doc > header.casehead > .plain{max-width:none}}
@media print{.doc > header.casehead > .plain{color:var(--text)}.doc > header.casehead > .plain i{color:var(--text-2)}}
/* the reconstruction note under the hero caption (v7.25): said once, up
   front, rather than only in the source notes at the foot */
.doc > header.casehead > .reconnote{position:relative;z-index:2;margin:10px 0 0;max-width:52ch;font:400 11.5px/1.5 var(--mono);color:color-mix(in srgb, var(--on-ground) 62%, transparent)}
/* The type and the drawing share the band and never touch. The drawing
   starts at the half-way line and the type stops two points short of it:
   for seven versions the box began at 34% and the title, the lede, the
   kicker and the caption ran to 54%, so every case file opened with its
   own name printed across its own drawing, at every desk width. The
   kicker wraps inside its half rather than running under the boxes. The
   caption keeps its full-width rule; only its words are held back. */
.doc > header.casehead > .kicker{max-width:48%}
.doc > header.casehead > h1{max-width:min(17ch,48%)}
.doc > header.casehead > .lede{max-width:min(46ch,48%)}
.doc > header.casehead > h1{
  font-size:clamp(34px,5.2vw,76px);
  line-height:.9;
  margin-top:12px;
}
.doc > header.casehead > .casecap{
  max-width:none;padding-right:52%;
  margin:clamp(16px,2.5vh,28px) 0 0;
  padding-top:12px;
  border-top:1px solid var(--line);
  font:400 max(10.5px,var(--floor))/1.5 var(--mono);letter-spacing:.07em;
  color:var(--text-2);
}
.doc > header.casehead > .casecap b{
  font-weight:600;color:var(--on-ground);
  letter-spacing:.1em;text-transform:uppercase;
}

/* --- the facts, on paper ------------------------------------------- */
.doc > .casefacts{
  margin:0 calc(var(--docx) * -1) 36px;
  padding:clamp(24px,4vh,40px) var(--docx) clamp(22px,3.4vh,34px);
  border-bottom:1px solid var(--line);
  display:grid;
  grid-template-columns:minmax(0,1fr);
  column-gap:44px;
}
@media(min-width:1000px){
  .doc > .casefacts{grid-template-columns:minmax(0,1fr) minmax(220px,.5fr)}
  .doc > .casefacts > .meta,
  .doc > .casefacts > .tldr,
  .doc > .casefacts > .arc{grid-column:1}
  .doc > .casefacts > .casemark{grid-column:2;grid-row:1 / 4;align-self:center;margin:0;max-width:none}
}
.doc > .casefacts > .meta{margin-top:0;border-top:0;padding-top:0}
.doc > .casefacts > .arc{margin-top:26px}
/* I owned, and who with (v7.30): the two questions a portfolio round asks
   first, as a two-column record under the facts, label left, answer right,
   in the same voice as the plain line in the header. */
.doc > .casefacts > .tldr{margin:16px 0 0;padding-top:14px;border-top:1px solid var(--line);display:grid;gap:8px}
.doc > .casefacts > .tldr > div{display:grid;grid-template-columns:92px minmax(0,1fr);column-gap:12px;align-items:baseline}
.doc > .casefacts > .tldr dt{font:500 max(9.5px,var(--floor))/1.3 var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--text-3)}
.doc > .casefacts > .tldr dd{margin:0;font:450 14px/1.5 var(--ui);color:var(--text);text-wrap:pretty}
.doc > .casefacts > .arc > div:first-child b{
  font:700 clamp(19px,2.1vw,27px)/1.1 var(--display);
  letter-spacing:-.026em;color:var(--accent-text);margin-top:7px;
}
@media(max-width:999px){
  .doc > .casefacts > .casemark{margin:24px 0 0;max-width:320px}
}

@media(max-width:860px){
  .doc > header.casehead{min-height:0;padding-block:28px}
  .casefield{position:relative;inset:auto;height:clamp(180px,30vh,240px);
             margin:22px -8% -6% 0;min-width:0;overflow:hidden;display:block}
  /* On a phone the file opens on its title, not on its drawing. The drawing
     is first in the markup because on a desk it sits behind the type, but in
     a single column that put three hundred pixels of boxes between the top
     of the page and what the file is called. The header is a one-column grid
     here, so order alone moves it; the caption follows its drawing. (The CV
     header does the same with flex, and needed align-self to keep its width;
     a grid item stretches on its own.) */
  .doc > header.casehead > .casefield{order:3}
  .doc > header.casehead > .casecap{order:4}
  .doc > header.casehead > .kicker,
  .doc > header.casehead > h1,
  .doc > header.casehead > .lede,
  .doc > header.casehead > .casecap{max-width:none}
  .doc > header.casehead > .casecap{padding-right:0}
}

@media print{
  .doc > header.casehead{min-height:0;padding-block:0}
  .casefield{display:none}
  .doc > .casefacts{margin-inline:0;padding-inline:0}
}


/* The CV and the colophon open on an object too.

   Every page in the drawer now opens the same way: a ground, a form
   cropped by the right edge, the title over it. The CV's is shorter,
   because a CV that opens on a 640px plate is a poster and this one has
   to survive being printed and read by a machine; the print rule above
   takes the object out entirely. */
.doc > header.cvhead,
.doc > header.pagehead{position:relative;overflow:hidden;isolation:isolate}
.doc > header.cvhead{min-height:clamp(280px,36vh,400px);display:flex;align-items:flex-end}
/* One column, said out loud. The kicker, title and lede are placed in
   column 1 by a shared rule, so the caption, which nothing placed, was
   auto-flowed into an implicit second column and printed under the
   drawing on the right. */
.doc > header.pagehead{min-height:clamp(340px,46vh,520px);display:grid;align-content:center;grid-template-columns:minmax(0,1fr)}
.doc > header.pagehead > .casecap{grid-column:1}
/* The CV's plate is a comparison, so it is cropped on the right like
   every other object here but held inside the band vertically: five bars
   drawn to length against each other only works if you can see all five
   of them. An abstract form can be cut anywhere; a chart cannot. */
.doc > header.cvhead > .casefield{inset:10% -16% 10% 46%}
.doc > header.pagehead > .casefield{inset:-8% -14% -18% 54%}
.doc > header.cvhead > .id,
.doc > header.cvhead > .co,
.doc > header.pagehead > .kicker,
.doc > header.pagehead > .bigtype,
.doc > header.pagehead > .lede{position:relative;z-index:2}
/* Held to the left half for the same reason as the case files: the
   drawing began at 40% and "Colophon", set at display size, ran to 49%. */
.doc > header.pagehead > .lede{max-width:min(46ch,50%)}
.doc > header.pagehead > .bigtype{max-width:min(12ch,52%)}
.doc > header.pagehead > .kicker{max-width:52%}
/* The plate starts at 46% of the header, and the name block had no width
   of its own, so the day the subtitle grew a second title in front of the
   role it ran straight over the first bar. Held short of the plate here;
   the phone layout below stacks the two and lifts the limit. */
.doc > header.cvhead > .id{max-width:44%}

@media(max-width:860px){
  .doc > header.cvhead,
  .doc > header.pagehead{min-height:0;display:block}
  /* On a phone the CV opens on the name, not on the drawing. The drawing
     comes first in the markup because on a desk it sits behind the text,
     but on a narrow screen that order put four hundred pixels of bars
     between the top of the page and the candidate's name, which is the one
     thing a recruiter on a phone has come to read. Flex only to reorder:
     everything else about the block layout stays as it was. */
  .doc > header.cvhead{display:flex;flex-direction:column}
  /* The colophon opens the same way: its kicker was printed over the bottom
     rows of the grid drawing on a phone, and the title came a screen later.
     Flex here because this header is display:block on a phone, so order has
     nothing to act on otherwise. */
  .doc > header.pagehead{display:flex;flex-direction:column;align-items:stretch}
  .doc > header.pagehead > .casefield{order:3;margin-top:22px}
  .doc > header.pagehead > .casecap{order:4}
  /* align-self, because the header is align-items:flex-start further down
     and a span whose only children are absolutely positioned canvases has
     no width of its own: it was measuring 0px wide, and the plate inside it
     drew five bars into a 4px bitmap. The reader saw 250px of empty ground
     under the contact block, with the caption "Five roles" beneath it. */
  .doc > header.cvhead > .casefield{order:3;margin-top:22px;align-self:stretch}
  .doc > header.cvhead > .casecap{order:4}
  .doc > header.cvhead > .casefield,
  .doc > header.pagehead > .casefield{
    position:relative;inset:auto;display:block;
    height:clamp(180px,30vh,250px);margin:20px -8% -6% 0;
    min-width:0;overflow:hidden;
  }
  .doc > header.pagehead > .lede,
  .doc > header.pagehead > .bigtype,
  .doc > header.pagehead > .kicker,
  .doc > header.cvhead > .id{max-width:none}
}
@media print{ .doc > header.cvhead > .casefield, .doc > header.pagehead > .casefield{display:none} .doc > header.cvhead > .id{max-width:none} .doc > header.casehead > .casecap{padding-right:0} }


/* --- the plates answer the hand --------------------------------------

   A drawing you cannot touch is a screenshot, and this site's whole
   argument is that a running product beats a screenshot of one. So the
   plates are models rather than pictures, and the cursor says so: grab
   on a plate you can move, and the pointer stays over the band rather
   than being swallowed by the type sitting on top of it.

   Only where there is a pointer to do it with. plates.js adds
   .plate-live itself after checking hover:hover, so a touch device is
   not told to grab something. */
.casefield.plate-live{cursor:grab;pointer-events:auto}
.casefield.plate-held{cursor:grabbing}
/* The band's own text must stay selectable and clickable; only the empty
   right-hand side where the drawing lives takes the pointer. */
.doc > header.casehead > :not(.casefield){position:relative;z-index:2}

/* A plate that moves is worth saying out loud once, quietly, in the
   caption that is already there. */
.casecap .live{
  color:var(--accent-text);
}
/* These invitations were hidden on touch because a finger could not work
   the plates. It can now, so the invitation is told to everyone; the
   wording already suits a finger as well as a hand. */
/* (was: hidden under hover:none) */

/* The CV and colophon captions sit under their plates like the case
   files' do, on the same rule. */
.doc > header.cvhead > .casecap,
.doc > header.pagehead > .casecap{
  position:relative;z-index:2;
  margin:clamp(18px,3vh,30px) 0 0;padding-top:12px;
  border-top:1px solid var(--line);
  font:400 max(10.5px,var(--floor))/1.5 var(--mono);letter-spacing:.07em;color:var(--text-2);
}
.doc > header.cvhead > .casecap b,
.doc > header.pagehead > .casecap b{
  font-weight:600;color:var(--on-ground);letter-spacing:.1em;text-transform:uppercase;
}
.doc > header.cvhead{flex-direction:column;align-items:flex-start;justify-content:flex-end}
/* On a desk the caption keeps its full-width rule but its words stop short
   of the drawing: the colophon's plate starts at 54%, the CV's at 46%.
   Here, after the shared caption rule, because a width set earlier at the
   same weight loses to it; that is how the first attempt at this did
   nothing at all. Phones and print stack the drawing, so this is desk only. */
@media(min-width:861px){
  .doc > header.pagehead > .casecap{padding-right:48%}
  .doc > header.cvhead > .casecap{padding-right:56%}
}


/* --- the section plates ----------------------------------------------

   A survey of the whole site found the holes precisely: #fixes carries
   five hundred words and no picture on all four case files, and the
   colophon's changelog runs to six thousand pixels and four and a half
   thousand words with nothing to look at at all. Those are the longest
   unbroken stretches of text anywhere here, and they were the stretches
   with nothing in them.

   So the plate goes where the gap is, rather than everywhere. Sections
   that already run a live product or carry a diagram got nothing added:
   piling art onto a section that is already showing you the software
   would be decoration, which is the thing this whole direction is
   against. */
.doc .sec .secplate{
  margin:clamp(26px,4vh,40px) calc(var(--docx) * -1) 0;
  padding:0;
}
/* The box's surface is a tint of whatever is already writing on it,
   never a named panel colour. var(--bg-sunken) is a cream in the site's
   own palette, so on the landing shelf, which is a dark section that
   flips --text to cream, the box came out cream and the plate drew cream
   ink on it: a spine chart that was there, answered the mouse, and could
   not be seen. Keying the surface and the ink to the same --text means a
   plate cannot be invisible on any section, including ones not written
   yet. */
.secplate-box{
  position:relative;display:block;
  /* Every one of these plates says "run a hand along them" or "pull one
     out", and on a phone none of them did anything. The listeners are
     pointer events, which touch does fire, but without a touch-action the
     browser claims a drag on the canvas for scrolling and cancels the
     pointer stream before a single move is delivered. The one interactive
     thing on this site that worked on a phone was the before/after split,
     which is also the one place touch-action was ever set.

     pan-y, not none: the plates want a horizontal scrub, and keeping
     vertical scrolling with the page matters more on a phone than the one
     plate that would rather have both axes. */
  touch-action:pan-y;
  height:clamp(190px,26vh,300px);
  background:color-mix(in srgb, var(--text) 5%, transparent);
  border-block:1px solid var(--line);
  overflow:hidden;
  --plate-ink:var(--text);
  --plate-accent:var(--accent);
}
.secplate-box canvas{position:absolute;inset:0;display:block;width:100%;height:100%}
.secplate.plate-live .secplate-box{cursor:crosshair}
.secplate > figcaption{
  margin:10px var(--docx) 0;
  font:400 max(10.5px,var(--floor))/1.5 var(--mono);letter-spacing:.06em;color:var(--text-3);
}
.secplate > figcaption .live{color:var(--accent-text)}
/* (was: hidden under hover:none) */

/* Inside a ground the plate sits on the ground's own raised surface
   rather than cutting a pale hole in it. */
.doc .sec.ground .secplate{margin-inline:0}
.doc .sec.ground .secplate-box{
  background:color-mix(in srgb, var(--on-ground) 5%, transparent);
  --plate-ink:var(--on-ground);
}
.doc .sec.ground .secplate > figcaption{margin-inline:0}

@media(max-width:700px){
  .doc .sec .secplate{margin-inline:0}
  .secplate > figcaption{margin-inline:0}
}
/* the governance matrix is eighteen rows of real labels; a phone's 26vh
   gave each row 12px (v7.54) */
@media(max-width:700px){ .secplate[data-kind="matrix"] .secplate-box{height:360px} }
@media print{ .secplate{display:none} }

/* ---- the scenes (v7.52, rebuilt in v7.53) ----------------------------
   A scene is drawn by the scroll on every pointer (scenes.js, scenes.css).
   It sits in the secplate's box so a ground recolours it and print drops
   it, but the box is only its stage now: no tint and no rules. v7.52's
   grey panel with a small drawing in the middle read as a placeholder
   nobody had filled. The heights are reserved here, in the sheet every
   page carries, so building a scene never moves the page; the lineage is
   real markup and takes its own height. */
.secplate.scene .secplate-box{height:clamp(300px,40vh,400px);background:none;border:0;touch-action:auto}
.doc .sec.ground .secplate.scene .secplate-box{background:none}
.secplate.scene .secplate-box > [data-scene]{position:absolute;inset:0;display:block}
.secplate.scene.tall .secplate-box{height:clamp(480px,60vh,540px)}
.secplate.scene[data-kind="stack"] .secplate-box{height:clamp(320px,45vh,430px)}
.secplate.scene.web .secplate-box{height:auto;overflow:visible}
/* the ledger reads the table under it: a column a row on a desk, a line a
   row on a phone, where the figure says how many rows (--rows) */
.secplate.scene[data-kind="ledger"] .secplate-box{height:236px}
.secplate.scene.web .secplate-box > [data-scene]{position:relative;inset:auto}
.overview > .secplate.scene{margin:clamp(26px,4vh,40px) 0 0}
.dir .secplate.scene{margin:0}
.overview > .secplate.scene > figcaption,
.dir .secplate.scene > figcaption{margin:10px 0 0}
@media(max-width:700px){
  .secplate.scene.tall .secplate-box{height:770px}
  .secplate.scene[data-kind="stack"] .secplate-box{height:228px}
  .secplate.scene[data-kind="evidence"] .secplate-box{height:290px}
  .secplate.scene[data-kind="ledger"] .secplate-box{height:calc(var(--rows, 6) * 62px + 40px)}
}
/* with no script there is nothing to stand in the box, so there is no box;
   the lineage is markup and stands finished */
@media (scripting:none){.secplate.scene:not(.web){display:none}}

/* The lineage on Work (v7.54). It is markup, so its layout is here, where
   it is styled from the first paint and without a script; scenes.css adds
   the wires and the arrival when the engine comes near. */
.lg{position:relative;--gut:clamp(36px,7vw,96px)}
.lg-head,.lg-row{display:grid;grid-template-columns:minmax(0,1fr) var(--gut) minmax(0,.86fr) var(--gut) minmax(0,1fr);align-items:center}
.lg-head{padding-bottom:10px;margin-bottom:6px;border-bottom:1px solid var(--line);font:500 9px/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-3)}
.lg-head > :nth-child(1){grid-column:1}
.lg-head > :nth-child(2){grid-column:3;text-align:center}
.lg-head > :nth-child(3){grid-column:5;text-align:right}
.lg-row{padding:14px 0;border-bottom:1px solid color-mix(in srgb,var(--text) 6%,transparent)}
.lg-row:last-child{border-bottom:0}
.lg-then,.lg-now{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px;min-width:0}
.lg-then{grid-column:1;align-items:flex-end}
.lg-now{grid-column:5;align-items:flex-start}
.lg-none{font:500 9px/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--text-3);padding:6px 0}
.lg-hub{grid-column:3;position:relative;padding:14px 16px 13px;border-radius:12px;background:color-mix(in srgb,var(--c) 10%,var(--bg-raised,var(--bg)));border:1px solid color-mix(in srgb,var(--c) 45%,transparent);box-shadow:0 18px 34px -24px color-mix(in srgb,var(--c) 70%,transparent)}
.lg-hub b{display:block;font:700 30px/.9 var(--display);letter-spacing:-.04em;color:color-mix(in srgb,var(--c) 75%,var(--text))}
.lg-hub span{display:block;margin-top:7px;font:600 13.5px/1.2 var(--ui);color:var(--text)}
.lg-hub small{display:block;margin-top:5px;font:500 8.5px/1.3 var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-3)}
.lg-node{display:grid;align-items:center;gap:10px}
.lg-then .lg-node{grid-template-columns:auto 76px;text-align:right;--dir:10px}
.lg-now .lg-node{grid-template-columns:118px auto;--dir:-10px}
.lg-then .lg-node .lg-t{order:-1}
.lg-thumb{position:relative;display:block;border-radius:6px;overflow:hidden;border:1px solid var(--line-strong);background:var(--bg-sunken,var(--bg));aspect-ratio:16/10}
.lg-thumb img{display:block;width:100%;height:100%;object-fit:cover;object-position:0 0}
.lg-now .lg-thumb::before{content:"";position:absolute;left:0;right:0;top:0;height:9px;background:color-mix(in srgb,var(--text) 8%,var(--bg-raised,#fff));border-bottom:1px solid var(--line);z-index:1}
.lg-t b{display:block;font:600 12.5px/1.2 var(--ui);color:var(--text)}
.lg-t i{display:block;margin-top:4px;font:500 8.5px/1.2 var(--mono);font-style:normal;letter-spacing:.12em;text-transform:uppercase;color:var(--text-3)}
.lg-now .lg-t i::before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:6px;vertical-align:0;background:color-mix(in srgb,var(--c) 80%,var(--text))}
@media(max-width:700px){
  .lg{--gut:0px}
  .lg-head{display:none}
  .lg-row{grid-template-columns:22px minmax(0,1fr);grid-template-areas:"hub hub" ". now" ". then";row-gap:10px;padding:14px 0}
  .lg-hub{grid-area:hub;display:grid;grid-template-columns:auto 1fr;column-gap:12px;align-items:center;padding:11px 13px}
  .lg-hub b{grid-row:1/3;font-size:26px}
  .lg-hub span{margin-top:0;font-size:13px}
  .lg-hub small{margin-top:3px}
  .lg-now{grid-area:now;align-items:stretch}
  .lg-then{grid-area:then;align-items:stretch}
  .lg-then .lg-node,.lg-now .lg-node{grid-template-columns:64px minmax(0,1fr);text-align:left;--dir:8px}
  .lg-then .lg-node .lg-t{order:0}
  .lg-none{display:none}
  .lg-t b{font-size:12px}
  /* the floor on a phone is 11px (v7.54) */
  .lg-t i,.lg-hub small{font-size:11px;letter-spacing:.08em}
}

/* ---- the figures table ----------------------------------------------

   Rendered from assets/figures.js by figuretable.js, so this is styling
   for a table whose contents nobody types. It sits inside a dark ground,
   so everything keys off --on-ground rather than --text; the token table
   above it is on paper and keeps its own rules.

   Four columns on a desk and a stacked card on a phone. A table of
   definitions and shell commands does not survive being squeezed into
   four columns at 390px, and the thing a reader wants there is still
   "which figure, what value, how was it counted", in that order. */
.figuretable{
  margin-top:18px;
  border:1px solid color-mix(in srgb, var(--on-ground) 24%, transparent);
  overflow:hidden;
}
.figuretable table{width:100%;border-collapse:collapse}
.figuretable th{
  font:600 max(10px,var(--floor)) var(--mono);letter-spacing:.08em;text-transform:uppercase;
  color:color-mix(in srgb, var(--on-ground) 62%, transparent);
  text-align:left;padding:9px 13px;
  border-bottom:1px solid color-mix(in srgb, var(--on-ground) 24%, transparent);
  background:color-mix(in srgb, var(--on-ground) 7%, transparent);
}
.figuretable td{
  font:400 12px/1.5 var(--mono);
  color:color-mix(in srgb, var(--on-ground) 80%, transparent);
  padding:11px 13px;vertical-align:top;
  border-bottom:1px solid color-mix(in srgb, var(--on-ground) 14%, transparent);
}
.figuretable tr:last-child td{border-bottom:none}

.fig-name b{display:block;font-weight:600;color:var(--on-ground)}
.fig-parts{display:block;margin-top:3px;font-size:max(10.5px,var(--floor));
  color:color-mix(in srgb, var(--on-ground) 55%, transparent)}

.fig-value{white-space:nowrap}
.fig-n{display:block;font:600 17px/1 var(--mono);color:var(--on-ground);letter-spacing:-.01em}
.fig-kind{display:block;margin-top:5px;font-size:max(9.5px,var(--floor));letter-spacing:.09em;text-transform:uppercase}
tr.derived .fig-kind{color:var(--accent-text)}
tr.attested .fig-kind{color:color-mix(in srgb, var(--on-ground) 50%, transparent)}

.fig-how{max-width:44ch}
.fig-def{display:block}
/* The command is the part that makes the figure checkable, so it is set
   to be read and copied rather than skimmed past. It wraps: a shell line
   cut off at the column edge is a command you cannot run. */
/* A ruled block, not a filled one. The command needs to be set apart
   from the definition above it because it is a different kind of thing,
   something you run rather than read, and a rule down its edge does that
   without putting a panel inside a document sheet. */
.fig-cmd{
  display:block;margin-top:7px;padding:1px 0 1px 10px;
  font:400 11px/1.55 var(--mono);
  color:color-mix(in srgb, var(--on-ground) 72%, transparent);
  border-inline-start:2px solid var(--accent);
  white-space:pre-wrap;word-break:break-word;
}
.fig-src,.fig-was{
  display:block;margin-top:7px;font-size:11px;
  color:color-mix(in srgb, var(--on-ground) 55%, transparent);
}
.fig-was{border-inline-start:2px solid color-mix(in srgb, var(--on-ground) 30%, transparent);padding-inline-start:9px}

.fig-asof{white-space:nowrap;font-size:11px;
  color:color-mix(in srgb, var(--on-ground) 55%, transparent)}

.fig-foot{
  margin:12px 0 0;font:400 11px/1.6 var(--mono);
  color:color-mix(in srgb, var(--on-ground) 58%, transparent);
}

@media(max-width:760px){
  .figuretable{border-inline:0}
  .figuretable tr{display:block;padding:14px 0;
    border-bottom:1px solid color-mix(in srgb, var(--on-ground) 16%, transparent)}
  .figuretable td{display:block;border:0;padding:0 2px}
  /* td.fig-value, not .fig-value: the rule above it is .figuretable td
     at (0,1,1) and a bare class at (0,1,0) loses to it, so the value
     stayed display:block and its gap did nothing. The number and its
     DERIVED tag were touching, reading as one word. */
  .figuretable td.fig-value{display:flex;align-items:baseline;gap:9px;margin:4px 0 7px}
  .fig-n{display:inline;font-size:22px}
  .fig-kind{display:inline;margin:0}
  .fig-how{max-width:none}
  .fig-asof{margin-top:7px}
}
@media print{ .figuretable .fig-cmd{border-inline-start-color:#999} }

/* ---- decisions: the part a portfolio of artefacts cannot show --------

   Everything else on a case file is a thing that was made. This is the
   record of calls taken, including the two that went backwards: a
   mandate locked and reverted inside one session, and a pair of earlier
   decisions retired by a later one. They are set as records rather than
   as prose, because that is what they are, and because a reverted
   decision formatted as a paragraph reads like an apology where the log
   entry reads like a system working.

   The cost line is the part that matters and it is not decoration: a
   decision with no stated cost is a decision nobody had to take. */
.doc .sec .subhead{
  margin:34px 0 10px;
  font:600 clamp(17px,1.9vw,22px)/1.25 var(--display);
  letter-spacing:-.02em;color:var(--text);
}
.doc .sec.ground .subhead{color:var(--on-ground)}

.decisions{
  margin-top:18px;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(275px,1fr));
  gap:26px 30px;
}
/* No card. The site spent a whole version removing boxes and its own
   check refuses them inside a document sheet, which caught this: four
   filled panels with borders is the house style of every other
   portfolio, which is the reason it was removed. A record is separated
   by a rule and a gap, the way the checks above it are. */
.decision{padding-top:13px;border-top:1px solid var(--line)}
.doc .sec.ground .decision{
  border-top-color:color-mix(in srgb, var(--on-ground) 24%, transparent);
}

/* The wave number is the citation. It is first and it is mono, because
   the whole claim of the section is that these are entries in a log that
   exists, not anecdotes written for a portfolio. */
.decision > .dw{
  display:block;font:600 max(9.5px,var(--floor)) var(--mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent-text);margin-bottom:7px;
}
.decision > b{
  display:block;font:600 15px/1.3 var(--ui);letter-spacing:-.01em;
  color:var(--text);margin-bottom:8px;
}
.doc .sec.ground .decision > b{color:var(--on-ground)}
.decision > p{margin:0 0 9px;font:400 13px/1.62 var(--ui);color:var(--text-2)}
.doc .sec.ground .decision > p{color:color-mix(in srgb, var(--on-ground) 82%, transparent)}
.decision > p:last-child{margin-bottom:0}

/* The cost line is the part that matters and it is not decoration: a
   decision with no stated cost is a decision nobody had to take. */
.decision > .cost{margin-top:11px;font-size:12.5px}
.decision > .cost > span{
  display:block;font:600 max(9.5px,var(--floor)) var(--mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-3);margin-bottom:4px;
}
.doc .sec.ground .decision > .cost > span{
  color:color-mix(in srgb, var(--on-ground) 58%, transparent);
}

@media(max-width:700px){ .decisions{grid-template-columns:1fr} }

/* A correction appended to a changelog entry rather than written over
   it. The entry records what shipped; the bracket records that a figure
   in it is now known to be wrong. Rewriting the entry would be tidier
   and would destroy the one thing a log is for. */
.amend{
  display:block;margin-top:9px;padding-inline-start:11px;
  border-inline-start:2px solid var(--accent);
  font:400 12px/1.6 var(--mono);
  color:color-mix(in srgb, var(--on-ground, var(--text)) 62%, transparent);
}

/* ---- plate2: the diagram idiom, rebuilt ------------------------------

   Every diagram in the case files was a flowchart: rounded cards with a
   raised fill, an arrowhead on every connector, a decision diamond, and
   the figures set at 10.5px in the middle of a box. It was the default
   output of thinking in boxes, and it sat in the middle of four case
   files on a site where every other object had been redrawn.

   Four things change, and all four come from the principles the colophon
   already publishes about these drawings.

   Scale is huge or tiny. The number is the subject, so it is set at 40px
   and the label that explains it at 10. Nothing sits in between, which is
   what makes a plate read as an instrument rather than as a slide.

   Lines connect rather than divide. One spine runs the whole width and
   the stages are stations on it, instead of eight arrows pointing at
   eight boxes. The spine is the argument: the loop does not stop.

   Nothing decorative may look like data. The boxes are gone. A station is
   a tick on the spine and a column of type under it, because a rectangle
   around a label is a container that means nothing and reads as though it
   does.

   And the hand. Hovering a station brings it up and drops the others
   back, so the drawing answers the pointer like everything else here. */

.diagram.plate2{
  margin-top:26px;
  padding-top:0;
}
.diagram.plate2 > .figrail{
  display:flex;justify-content:space-between;gap:12px;
  padding-bottom:9px;margin-bottom:14px;
  border-bottom:1px solid var(--line);
  font:500 max(9.5px,var(--floor)) var(--mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--text-3);
}
.doc .sec.ground .diagram.plate2 > .figrail{
  border-bottom-color:color-mix(in srgb, var(--on-ground) 22%, transparent);
  color:color-mix(in srgb, var(--on-ground) 58%, transparent);
}

.diagram.plate2 svg{display:block;width:100%;height:auto;overflow:visible}

/* the spine, and the leg that closes the loop */
.diagram.plate2 .spine{
  fill:none;stroke:var(--line-strong);stroke-width:1;
}
.doc .sec.ground .diagram.plate2 .spine{
  stroke:color-mix(in srgb, var(--on-ground) 34%, transparent);
}
.diagram.plate2 .spine-return{stroke-dasharray:3 4}

.diagram.plate2 .tick{
  fill:none;stroke:var(--line-strong);stroke-width:1;
}
.doc .sec.ground .diagram.plate2 .tick{
  stroke:color-mix(in srgb, var(--on-ground) 42%, transparent);
}

.diagram.plate2 .n{
  font:500 9.5px var(--mono);letter-spacing:.1em;
  fill:var(--text-2);
}
.diagram.plate2 .lab{
  font:600 10.5px var(--mono);letter-spacing:.08em;
  fill:var(--text);
}
/* the figure, set at the size the thing deserves */
.diagram.plate2 .fig{
  font:700 40px/1 var(--display);letter-spacing:-.03em;
  fill:var(--text);
}
.diagram.plate2 .sub{
  font:400 10.5px var(--ui);
  fill:var(--text-2);
}
.diagram.plate2 .ret{
  font:500 9.5px var(--mono);letter-spacing:.1em;
  fill:var(--text-2);
}
.doc .sec.ground .diagram.plate2 .n,
.doc .sec.ground .diagram.plate2 .ret{
  fill:color-mix(in srgb, var(--on-ground) 72%, transparent);
}
.doc .sec.ground .diagram.plate2 .lab,
.doc .sec.ground .diagram.plate2 .fig{fill:var(--on-ground)}
.doc .sec.ground .diagram.plate2 .sub{
  fill:color-mix(in srgb, var(--on-ground) 76%, transparent);
}

/* one accent, on the stations that carry the claim */
.diagram.plate2 .stn.hot .fig,
.diagram.plate2 .stn.hot .lab{fill:var(--accent-text)}
.diagram.plate2 .stn.hot .tick{stroke:var(--accent);stroke-width:2}

/* the hand. A station comes up, the rest go back, and the transition is
   short enough to feel like a response rather than an animation. */
.diagram.plate2 .stn{transition:opacity .14s ease}
.diagram.plate2 svg:hover .stn{opacity:.34}
.diagram.plate2 svg:hover .stn:hover{opacity:1}
.diagram.plate2 svg:hover .stn:hover .fig,
.diagram.plate2 svg:hover .stn:hover .lab{fill:var(--accent-text)}
.diagram.plate2 svg:hover .stn:hover .tick{stroke:var(--accent);stroke-width:2}
@media(prefers-reduced-motion:reduce){ .diagram.plate2 .stn{transition:none} }

/* A six-station spine does not survive a phone. Below the width where
   the labels would start colliding the whole plate scrolls sideways,
   which keeps it legible instead of squeezing it into something nobody
   can read. It scrolls at 1274px (v7.32): the smallest label is drawn at
   9.5 units on a 1100-unit plate, and 1274 is the width at which that
   label lands at 11px, the site's floor on a phone. At 860 it was 7.4px,
   a plate you had to pinch as well as pan. */
@media(max-width:820px){
  .diagram.plate2{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .diagram.plate2 svg{width:1274px;max-width:none}
}
@media print{
  .diagram.plate2 svg:hover .stn{opacity:1}
  .diagram.plate2{overflow:visible}
  .diagram.plate2 svg{width:100%}
}

/* the boundary plate: a line that is solid by default and open only
   where someone chose to open it */
.diagram.plate2 .bound{
  fill:none;stroke:var(--text);stroke-width:2;
}
.doc .sec.ground .diagram.plate2 .bound{stroke:var(--on-ground)}
.diagram.plate2 .boundlab{
  font:600 9.5px var(--mono);letter-spacing:.1em;fill:var(--text-2);
}
.diagram.plate2 .cross{
  fill:none;stroke:var(--accent);stroke-width:1.4;
}
.diagram.plate2 .gate{
  fill:var(--bg);stroke:var(--accent);stroke-width:2;
}
.doc .sec.ground .diagram.plate2 .gate{fill:var(--ground)}
.diagram.plate2 .gatelab{
  font:600 10.5px var(--mono);letter-spacing:.07em;fill:var(--accent-text);
}
.diagram.plate2 .gatesub{font:400 10.5px var(--ui);fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .gatesub{
  fill:color-mix(in srgb, var(--on-ground) 76%, transparent);
}

/* The refusals are struck through rather than listed, because they are
   not features that were cut on the way. They are things the service
   will not do, and a list of them reads as a backlog where a struck
   list reads as a decision. */
.diagram.plate2 .strike{
  fill:none;stroke:var(--text-3);stroke-width:1;
}
.doc .sec.ground .diagram.plate2 .strike{
  stroke:color-mix(in srgb, var(--on-ground) 50%, transparent);
}
.diagram.plate2 .refusals .sub{fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .refusals .sub{
  fill:color-mix(in srgb, var(--on-ground) 72%, transparent);
}
.diagram.plate2 svg:hover .refusals{opacity:.66;transition:opacity .14s ease}
.diagram.plate2 svg:hover .refusals:hover{opacity:1}

/* layers, beams and the connector that is deliberately absent --------- */
.diagram.plate2 .lay{fill:none;stroke:var(--line-strong);stroke-width:1}
.doc .sec.ground .diagram.plate2 .lay{
  stroke:color-mix(in srgb, var(--on-ground) 34%, transparent);
}
.diagram.plate2 .stn.hot .lay{stroke:var(--accent);stroke-width:2}

/* the beam carries everything above and below it, so it is the heaviest
   line in the drawing. That is the whole claim about the model layer,
   said in stroke width rather than in a caption. */
.diagram.plate2 .beam{fill:none;stroke:var(--text);stroke-width:3}
.doc .sec.ground .diagram.plate2 .beam{stroke:var(--on-ground)}
/* these two set a font and a letter-spacing and stopped there, so they fell
   back to the SVG default fill, which is black. Black is survivable on paper
   and invisible on an inverted ground or in the dark theme. Nothing was
   broken only because no figure had reached for them yet; they label the
   beam, and the beam is drawn on both grounds, so they carry a fill now and
   match .lab and .sub rather than depending on where they happen to land. */
.diagram.plate2 .beamlab{font:600 10.5px var(--mono);letter-spacing:.09em;fill:var(--text)}
.diagram.plate2 .beamsub{font:400 10.5px var(--ui);fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .beamlab{fill:var(--on-ground)}
.doc .sec.ground .diagram.plate2 .beamsub{
  fill:color-mix(in srgb, var(--on-ground) 76%, transparent);
}

.diagram.plate2 .link{fill:none;stroke:var(--line-strong);stroke-width:1}
.doc .sec.ground .diagram.plate2 .link{
  stroke:color-mix(in srgb, var(--on-ground) 34%, transparent);
}
.diagram.plate2 .linklab{font:400 10px var(--ui);fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .linklab{
  fill:color-mix(in srgb, var(--on-ground) 72%, transparent);
}

/* A connector that is drawn and then cut, rather than left off the page.
   The component layer cannot reach a raw value, and that refusal is the
   reason a theme swap is free; an absent line says nothing, a severed
   one says somebody decided. */
.diagram.plate2 .link-cut{stroke-dasharray:3 4;opacity:.5}
.diagram.plate2 .cut{fill:none;stroke:var(--accent);stroke-width:1.6}
/* the accent twin of .lab, the dimmed twins of .lab and .sub. All three
   set a fill and no font, so they inherited 14px from the document while
   every label beside them is 10.5, which is why the accent station in the
   Astrato plate sat a size larger than the four it belongs with. */
.diagram.plate2 .cutlab{font:600 10.5px var(--mono);letter-spacing:.08em;fill:var(--accent-text)}

/* the one figure big enough to be the answer rather than a datum */
.diagram.plate2 .figbig{
  font:700 56px/1 var(--display);letter-spacing:-.035em;fill:var(--accent-text);
}

/* A route that was taken and abandoned. It is drawn and then cut rather
   than left off the drawing, because a path that is simply missing tells
   you nobody tried it, and trying it was the expensive part. */
.diagram.plate2 .deadlab{font:600 10.5px var(--mono);letter-spacing:.08em;fill:var(--text-2)}
.diagram.plate2 .deadsub{font:400 10.5px var(--ui);fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .deadlab,
.doc .sec.ground .diagram.plate2 .deadsub{
  fill:color-mix(in srgb, var(--on-ground) 72%, transparent);
}
.diagram.plate2 .ret-cap{stroke:var(--line-strong)}
.doc .sec.ground .diagram.plate2 .ret-cap{
  stroke:color-mix(in srgb, var(--on-ground) 34%, transparent);
}

/* --- plate2: quantities -------------------------------------------------------

   Most of the slides in these decks were never pictures. They were text:
   a heading, three bullets, a coloured corner, exported at 4x from Figma.
   Reproduced on a web page they are a screenshot of a paragraph, which is
   the least informative thing a figure can be, and no amount of cropping
   fixes it.

   So the ones that carried an argument rather than an interface have been
   redrawn as figures, in the plate2 idiom the case files already use, with
   the same rules: the picture is the number, scale is huge or tiny with
   nothing between, and nothing decorative may look like data. The ones
   that were genuinely pictures (a product, a heatmap, a before and after)
   are still the original export, because a drawing of a screenshot would
   be a worse claim, not a better one.

   The rest of plate2, above, covers spines, stations, boundaries and
   gates: the shape of a process. What follows adds the vocabulary for
   quantities, bars and lanes and a weighted edge, because a case file
   that states a finding has to be able to draw one too.
   ------------------------------------------------------------------------ */

/* a measured bar and the lane it is measured in */
.diagram.plate2 .lane{fill:none;stroke:var(--line);stroke-width:1}
.doc .sec.ground .diagram.plate2 .lane{
  stroke:color-mix(in srgb, var(--on-ground) 20%, transparent);
}
.diagram.plate2 .bar{fill:var(--text);opacity:.82}
.doc .sec.ground .diagram.plate2 .bar{fill:var(--on-ground);opacity:.76}
.diagram.plate2 .bar.hot{fill:var(--accent);opacity:1}
.doc .sec.ground .diagram.plate2 .bar.hot{fill:var(--accent);opacity:1}
/* a bar that is there to be compared against, not read */
.diagram.plate2 .bar.ghost{fill:none;stroke:var(--line-strong);stroke-width:1;stroke-dasharray:3 3;opacity:.7}
.doc .sec.ground .diagram.plate2 .bar.ghost{
  stroke:color-mix(in srgb, var(--on-ground) 36%, transparent);fill:none;
}

.diagram.plate2 .val{font:600 13px var(--mono);letter-spacing:-.01em;fill:var(--text)}
.doc .sec.ground .diagram.plate2 .val{fill:var(--on-ground)}
.diagram.plate2 .val.hot{fill:var(--accent-text)}
.diagram.plate2 .key{font:500 9.5px var(--mono);letter-spacing:.08em;fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .key{
  fill:color-mix(in srgb, var(--on-ground) 72%, transparent);
}
.diagram.plate2 .rowlab{font:400 max(10.5px,var(--floor)) var(--ui);fill:var(--text-2)}
.doc .sec.ground .diagram.plate2 .rowlab{
  fill:color-mix(in srgb, var(--on-ground) 78%, transparent);
}
.diagram.plate2 .rowlab.hot{fill:var(--accent-text)}
/* A row label or a row's figure can be the point of its own row without
   being a station, which .stn.hot already covers. These were being marked
   hot in the markup and styled by nothing, so the marking said one thing
   and the drawing said another. */
.diagram.plate2 .lab.hot,
.diagram.plate2 .fig.hot{fill:var(--accent-text)}

/* the edge of a relationship, weighted by how much traffic it will carry */
.diagram.plate2 .edge{fill:none;stroke:var(--line-strong);stroke-width:1.4}
.doc .sec.ground .diagram.plate2 .edge{
  stroke:color-mix(in srgb, var(--on-ground) 46%, transparent);
}
.diagram.plate2 .edge.weak{stroke-dasharray:4 5}
.diagram.plate2 .edge.hot{stroke:var(--accent);stroke-width:2}
.diagram.plate2 .node{fill:var(--text)}
.doc .sec.ground .diagram.plate2 .node{fill:var(--on-ground)}

/* a phase band behind a schedule, so the bars have something to sit in */
.diagram.plate2 .phase{fill:var(--text);opacity:.045}
.doc .sec.ground .diagram.plate2 .phase{fill:var(--on-ground);opacity:.07}
.diagram.plate2 .phaseline{stroke:var(--line-strong);stroke-width:1;stroke-dasharray:2 4;fill:none}
.doc .sec.ground .diagram.plate2 .phaseline{
  stroke:color-mix(in srgb, var(--on-ground) 34%, transparent);
}

/* Every plate carries the same content as prose, one disclosure below
   it. WCAG 1.4.10 exempts content that genuinely needs two dimensions,
   and a six-station technical plate is in the same family as a map or a
   data table, but the exemption only deserves to be claimed when the
   content is actually available another way. At 200% zoom this is the
   version that reflows. */
.plate-alt{margin-top:10px}
.plate-alt > summary{
  cursor:pointer;list-style:none;
  font:500 max(10px,var(--floor)) var(--mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--text-3);padding:4px 0;
}
.plate-alt > summary::-webkit-details-marker{display:none}
.plate-alt > summary::before{content:'+ ';opacity:.7}
.plate-alt[open] > summary::before{content:'\2212 '}
.plate-alt > summary:hover{color:var(--accent-text)}
.plate-alt > summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.plate-alt > p{
  margin:8px 0 0;max-width:68ch;
  font:400 12.5px/1.65 var(--ui);color:var(--text-2);
}
.doc .sec.ground .plate-alt > summary{
  color:color-mix(in srgb, var(--on-ground) 55%, transparent);
}
.doc .sec.ground .plate-alt > p{
  color:color-mix(in srgb, var(--on-ground) 78%, transparent);
}
@media print{ .plate-alt{display:none} }

/* A facade card (v7.32) has not started booting until a hand reaches it,
   so it does not say it is booting: the poster is the card until then. */
.stage[data-boot="intent"]:not([data-queued]) .boot{display:none}


/* ------------------------------------------------------------
   v7.48: the cool layer, page by page (site.css carries the shared half)
   ------------------------------------------------------------ */

/* the landing. The grid focuses on the pointed card: the other stills
   lose their colour a little (pictures only, the type keeps its ink), the
   scope's rows and the proofs answer the hand, and the inline links'
   underline rises to the word. */
@media(hover:hover){
  .workgrid .bthumb img{transition:filter .35s ease}
  .workgrid:has(.stripcard:hover) .stripcard:not(:hover) .bthumb img{filter:saturate(.45)}
  .scope dd{transition:color var(--snap)}
  .scope:has(> div:hover) > div:not(:hover) dd{color:var(--text-2)}
  .scope dt{transition:color var(--snap)}
  .scope > div:hover dt{color:var(--accent-text)}
  .doc p > a:not([class]),
  .doc li:not(.shelf-list > li) > a:not([class]),
  .doc td > a:not([class]),
  .doc dd > a:not([class]),
  .doc figcaption > a:not([class]),
  .doc .ref > a:not([class]),
  .herocap a:not([class]),
  .herolede p > a:not([class]){transition:text-decoration-color .15s ease,color .15s ease,text-underline-offset .15s ease}
  .doc p > a:not([class]):hover,
  .doc li:not(.shelf-list > li) > a:not([class]):hover,
  .doc td > a:not([class]):hover,
  .doc dd > a:not([class]):hover,
  .doc figcaption > a:not([class]):hover,
  .doc .ref > a:not([class]):hover,
  .herocap a:not([class]):hover,
  .herolede p > a:not([class]):hover{text-underline-offset:.1em}
  /* the landing's end: the stills on the 404's shelf breathe */
  .notfound .shelf-list .rimg img{transition:transform .5s cubic-bezier(.2,.7,.2,1)}
  .notfound .shelf-list a:hover .rimg img{transform:scale(1.025)}
}

/* the 404: the path that was asked for is set as a chip, and the
   nearest file's link draws its rule in */
.notfound .asked code{display:inline-block;padding:1px 6px;border-radius:4px;background:var(--bg-sunken);font:500 .92em var(--mono);color:var(--text)}
.notfound .meant a{text-decoration-color:color-mix(in srgb, var(--accent-text) 42%, transparent)}
@media(hover:hover){.notfound .meant a:hover{text-decoration-color:currentColor}}

/* writing: a drop cap opens each essay, and a rail along the top of the
   chrome fills as the essay is read (a view timeline on the section: it
   is 0 until the essay fills the screen and 1 as its last line leaves,
   and it holds where the reader holds) */
.sec[data-essay] .prose > p:first-of-type::first-letter{
  float:left;margin:.1em .14em 0 0;
  font:800 3.1em/.8 var(--display);letter-spacing:-.02em;color:var(--accent-text);
}
@media(prefers-reduced-motion:no-preference){
  @supports (animation-timeline: view()){
    .sec[data-essay]::before{
      content:'';position:sticky;top:var(--viewhead-real,0px);z-index:3;display:block;height:2px;margin:0 0 -2px;
      background:var(--accent);transform-origin:0 50%;transform:scaleX(0);
      animation:essay-read linear both;animation-timeline:view();animation-range:contain 0% contain 100%;
      pointer-events:none;
    }
  }
}
@keyframes essay-read{to{transform:scaleX(1)}}

/* practice: the pointed row reads and the rest recede, a step's number
   lights, the memos sit a degree off the paper under the hand (the tilt
   is stagecraft 16) */
@media(hover:hover){
  .doc.practice ol.steps li,.doc.practice ol.calls li,.doc.practice ul.after li,.doc.practice ol.steps li b,.doc.practice ol.calls li b,.practable td{transition:color var(--snap)}
  .doc.practice ol:is(.steps,.calls):has(li:hover) li:not(:hover){color:var(--text-3)}
  .doc.practice ol:is(.steps,.calls):has(li:hover) li:not(:hover) b{color:var(--text-2)}
  .doc.practice ul.after:has(li:hover) li:not(:hover){color:var(--text-3)}
  .doc.practice ol.calls li::before,.doc.practice ol.steps li::before{transition:color var(--snap)}
  .doc.practice ol.calls li:hover::before,.doc.practice ol.steps li:hover::before{color:var(--accent-text)}
  .practable tbody:has(tr:hover) tr:not(:hover) td{color:var(--text-3)}
  .doc.practice .memo{transition:border-color var(--snap)}
  .doc.practice .memo:hover{border-top-color:var(--accent)}
}

/* the colophon: the tables recede round the pointed row, a swatch grows
   and can be copied (app.js), the token table's swatches breathe, and
   the changelog's version lights with its entry */
@media(hover:hover){
  .tokentable td{transition:color var(--snap)}
  .tokentable tbody:has(tr:hover) tr:not(:hover) td{color:var(--text-3)}
  .tokentable .swatch{transition:transform var(--snap)}
  .tokentable tr:hover .swatch{transform:scale(1.25)}
  .swatches .sw{transition:transform var(--snap)}
  .swatches li:hover .sw{transform:scale(1.08)}
  .change .v small{transition:color var(--snap)}
  .change:hover .v small{color:var(--text-2)}
  .spec-row{transition:color var(--snap)}
  .specimens:has(.spec-row:hover) .spec-row:not(:hover) .spec-k{color:var(--text-3)}
}
.swatches button.sw{border:0;padding:0;margin:0;cursor:pointer;font:inherit;appearance:none;-webkit-appearance:none}
.swatches button.sw:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.swatches button.sw:active{transform:scale(.96)}
.howbuilt .prose > p:first-of-type::first-letter{
  float:left;margin:.08em .14em 0 0;
  font:800 3em/.8 var(--display);letter-spacing:-.02em;color:var(--accent-text);
}

/* the CV: nothing on it animates (sections 10 and 21), so these are hover
   states on a screen and nothing else: a tenure bar takes its full ink,
   a proof lifts, a client's note reads, the sheet's links underline */
@media screen and (hover:hover){
  .cvdoc .cvjob .when i{transition:opacity var(--snap)}
  .cvdoc .cvjob:hover .when i{opacity:1}
  .cvdoc .cvproof b{transition:translate var(--snap)}
  .cvdoc .cvproof li:hover b{translate:0 -2px}
  .cvdoc .cvproof li{transition:border-color var(--snap)}
  .cvdoc .cvproof li:hover{border-top-color:var(--cv-accent-2)}
  .cvdoc .cvclients li{transition:color var(--snap)}
  .cvdoc .cvclients:has(li:hover) li:not(:hover){color:var(--cv-3)}
  .cvdoc .printbar .btn{transition:border-color var(--snap),background var(--snap),color var(--snap)}
}
/* the CV, continued: a role's title takes the accent, the stills in the
   aside breathe, the skills' chips answer the hand */
@media screen and (hover:hover){
  .cvdoc .cvjob h3{transition:color var(--snap)}
  .cvdoc .cvjob:hover h3{color:var(--cv-accent-2)}
  .cvdoc .cvstills img{transition:transform .5s cubic-bezier(.2,.7,.2,1)}
  .cvdoc .cvstills a:hover img{transform:scale(1.025)}
  .cvdoc .chip{transition:border-color var(--snap),color var(--snap)}
  .cvdoc .cvskills:has(p:hover) p:not(:hover){color:var(--cv-3)}
  .cvdoc .cvskills p{transition:color var(--snap)}
}

/* v7.49: the value line stays on a short screen. At 1280 by 720, a
   laptop, the band ran 21px past the fold with the slogan at 9.6vw on
   three lines, and the fold suite had been saying so on main; the
   slogan is a size smaller where the screen is short, the block's
   paddings a step tighter, and the band no longer insists on a height
   the screen does not have. Width-gated as well, so a phone, which has
   its own rules above, never takes this one. */
@media screen and (min-width:921px) and (max-height:800px){
  .herostage{min-height:0}
  .herostage .bigtype{font-size:clamp(50px,7.6vw,112px)}
  .herostage .headline{padding-block:14px 16px}
  .herostage .herokicker{margin-bottom:12px}
  .herostage .herodeck{margin-top:12px}
}

/* ------------------------------------------------------------
   v7.50: the landing's own pieces (site.css carries the shared ones and
   the law; app.js writes --mx and --my where the hand is)
   ------------------------------------------------------------ */
@media(hover:hover) and (pointer:fine){
  /* a sheen follows the hand across the pointed card: the accent at a
     tenth, radial, over the face, opacity only */
  .stripcard .face::before{content:'';position:absolute;inset:0;z-index:2;pointer-events:none;opacity:0;transition:opacity var(--snap);
    background:radial-gradient(280px circle at var(--mx,50%) var(--my,50%),color-mix(in srgb,var(--accent) 11%,transparent),transparent 70%)}
  .stripcard:hover .face::before{opacity:1}
  /* the card's number takes the accent and steps forward */
  .stripcard .bn{transition:background-color var(--snap),color var(--snap),translate var(--snap)}
  .stripcard:hover .bn{background:var(--accent);color:#fff;translate:2px 0}
  [data-theme="dark"] .stripcard:hover .bn{color:#0b1020}
  /* the proofs recede round the pointed proof, and its unit lights */
  .proofs b,.proofs i{transition:color var(--snap)}
  .proofs:has(li:hover) li:not(:hover) b{color:var(--text-2)}
  .proofs li:hover i{color:var(--accent-text)}
  /* the threads: the pointed thread keeps its ink */
  .threads li b{transition:color var(--snap)}
  .threads ol:has(li:hover) li:not(:hover) b{color:var(--text-2)}
  /* the bio's facts: the pointed fact's figure takes the accent, and the
     window's title while the hand is in the window */
  .facts b,.winpanel .wintitle{transition:color var(--snap)}
  .facts li:hover b,.winpanel:hover .wintitle{color:var(--accent-text)}
  /* a Start here row lights its label with its button */
  .starthere .sh-for{transition:color var(--snap)}
  .starthere li:hover .sh-for{color:var(--accent-text)}
}
/* the keyboard twins of the above */
.proofs:has(li:focus-within) li:not(:focus-within) b{color:var(--text-2)}
.threads ol:has(li:focus-within) li:not(:focus-within) b{color:var(--text-2)}
.starthere li:focus-within .sh-for{color:var(--accent-text)}
/* the card's back arrives row by row when the card is turned (by the
   hand, a tap or the keyboard); until then its rows wait a step below.
   With no script the grid is never marked and the rows simply show. */
@media(prefers-reduced-motion:no-preference){
  .workgrid[data-flip] .stripcard .wc-inside li{opacity:0;translate:0 6px;transition:opacity .3s ease,translate .3s cubic-bezier(.2,.7,.2,1)}
  .workgrid[data-flip] .stripcard:is(:hover,[data-flipped],:has(.back :focus-visible)) .wc-inside li{opacity:1;translate:0 0}
  .workgrid[data-flip] .stripcard .wc-inside li:nth-child(2){transition-delay:50ms}
  .workgrid[data-flip] .stripcard .wc-inside li:nth-child(3){transition-delay:100ms}
  .workgrid[data-flip] .stripcard .wc-inside li:nth-child(4){transition-delay:150ms}
  /* the answer line arrives anew with each press (app.js sets data-said) */
  .wp-say[data-said]{animation:say-in .35s cubic-bezier(.2,.7,.2,1) both}
}
/* on paper every row is there, whatever the grid was marked */
@media print{.workgrid[data-flip] .stripcard .wc-inside li{opacity:1!important;translate:none!important}}
@keyframes say-in{from{opacity:0;translate:0 4px}}
/* Start here counts in padded mono, 01 and 02, the way the files and the
   threads count */
.starthere ol{counter-reset:sh}
.starthere li{counter-increment:sh}
.starthere li::before{content:counter(sh,decimal-leading-zero);flex:none;font:500 max(10px,var(--floor))/1 var(--mono);letter-spacing:.08em;color:var(--text-3)}

/* ------------------------------------------------------------
   v7.50: the CV's own pieces. The CV never animates, by its own rule:
   these are hover and keyboard states, and craft.
   ------------------------------------------------------------ */
@media screen{
  /* the print bar stays in reach as the CV scrolls, and condenses when
     it is stuck (the scroll-state query, Chrome; sticky everywhere) */
  .cvdoc .printbar{position:sticky;top:0;z-index:3;background:var(--bg);container-type:scroll-state}
  /* It used to condense when stuck by hiding its note, which took 25px
     (55 on a phone) out of the page at the moment it stuck: the rule of
     44.5, ink and never size. The note is under the bar now and scrolls
     away with the page, so the bar sticks at the size it always has. */
  /* the name is trimmed to its cap height, so the header's spacing is
     the type's, not the line box's */
  @supports (text-box: trim-both cap alphabetic){
    .cvbig{text-box:trim-both cap alphabetic}
  }
  /* bullets, refs and the skills lines wrap pretty; the sheet keeps its
     printed line breaks, so screen only */
  .cvjob li,.cvjob .ref,.cvref,.cvskills p{text-wrap:pretty}
  /* the CV's selection is in its own accent */
  .cvdoc ::selection{background:color-mix(in srgb,var(--cv-accent-2) 22%,transparent);color:var(--cv-text,inherit)}
}
@media screen and (hover:hover){
  /* the bullet under the hand keeps its ink, the others recede */
  .cvdoc .cvjob ul li{transition:color var(--snap)}
  .cvdoc .cvjob ul:has(li:hover) li:not(:hover){color:var(--cv-3)}
  /* the tenure's years light with the bar */
  .cvdoc .cvjob .when{transition:color var(--snap)}
  .cvdoc .cvjob:hover .when{color:var(--cv-accent-2)}
  /* an education row's degree takes the accent */
  .cvdoc .cvedu li b{transition:color var(--snap)}
  .cvdoc .cvedu li:hover b{color:var(--cv-accent-2)}
  /* the proofs recede round the pointed proof, and its bar grows */
  .cvdoc .cvproof:has(li:hover) li:not(:hover) b{color:var(--cv-3)}
  .cvdoc .cvproof li::before{transition:width var(--snap)}
  .cvdoc .cvproof li:hover::before{width:44px}
  /* a skills line's lead word takes the accent */
  .cvdoc .cvskills p b{transition:color var(--snap)}
  .cvdoc .cvskills p:hover b{color:var(--cv-accent-2)}
  /* a still's caption lights with the still */
  .cvdoc .cvstills a span,.cvdoc .cvstills figcaption{transition:color var(--snap)}
  .cvdoc .cvstills a:hover span,.cvdoc .cvstills a:hover figcaption{color:var(--cv-accent-2)}
}
/* the keyboard twins of the CV's hover states, this round's and v7.48's */
@media screen{
  .cvdoc .cvjob:focus-within .when i{opacity:1}
  .cvdoc .cvjob:focus-within h3,.cvdoc .cvjob:focus-within .when{color:var(--cv-accent-2)}
  .cvdoc .cvproof li:focus-within b{translate:0 -2px}
  .cvdoc .cvproof li:focus-within::before{width:44px}
  .cvdoc .cvstills a:focus-visible img{transform:scale(1.025)}
  .cvdoc .cvstills a:focus-visible span,.cvdoc .cvstills a:focus-visible figcaption{color:var(--cv-accent-2)}
  .cvdoc .cvclients:has(li:focus-within) li:not(:focus-within){color:var(--cv-3)}
  .cvdoc .cvedu li:focus-within b{color:var(--cv-accent-2)}
}
/* a reader asking for more contrast gets the tenure bars in full ink */
@media screen and (prefers-contrast:more){
  .cvdoc .cvjob .when i{opacity:1}
}
