/* ---------------------------------------------------------------------------
   RECEIPT — warm cream, no landscape.

   TYPE SCALE IS THE SPACING MECHANISM.
   The references this is built against (Christopher Ireland, Alison Roman,
   Waka Waka) get their air from a wide gap between display and body sizes,
   with section gaps of only 40-54px. The previous build did the inverse:
   modest type pushed apart by large padding, which reads as dead space rather
   than composition. So the scale here runs ~104px display against 20px body,
   and section padding is deliberately small. If a section feels cramped, open
   the type, do not add padding.

   TWO FAMILIES, NOT THREE.
   Newsreader carries display and body. Martian Mono carries data and labels.
   The sans is gone. The rule is: the serif speaks, the mono counts. Mono is
   load-bearing for the honesty system (measured deltas green, scope figures
   black) and must not be swapped out for the serif in those roles.

   LEAN ON THE ARTIFACT, NOT THE WARMTH.
   Warm cream + serif + rust is a heavily worn combination. What distinguishes
   this site is the receipt as a genuine second surface: the tape, the torn
   edge, the itemization, the barcode. When a section feels generic, make it
   more physically like printed tape.
--------------------------------------------------------------------------- */
* { margin:0; padding:0; box-sizing:border-box; }

:root {
  --sheet:#f6f1e6;      /* bone: the page */
  --tape:#fffdf8;       /* till paper, one step lighter than the sheet */
  --ink:#2b2118;        /* espresso */
  --muted:#6e6257;
  --faint:#a99c8c;      /* rules and dotted leaders ONLY, never text on paper */
  --line:#e3dacb;
  --lift:#185c43;       /* shopkeeper green: measured movement only */
  --leak:#8a2d3b;       /* oxblood: the leak and stated costs only */
  --btx:#6b6157;        /* before-state text, AA on all three paper grounds */
  --before-bg:#efe8db;
  --rule-on-ink:#857868; /* dividers and underlines on the ink ground only */

  --body:"Newsreader Variable",Georgia,serif;
  --mono:"Martian Mono Variable",ui-monospace,monospace;

  /* type scale: the wide end of the range is what buys the whitespace */
  --fs-display:clamp(3rem,8.5vw,6.5rem);
  --fs-h1:clamp(2.1rem,4.6vw,3.5rem);
  --fs-h2:clamp(1.55rem,3vw,2.15rem);
  --fs-h3:clamp(1.3rem,2.3vw,1.7rem);
  --fs-body:20px;
  --fs-sm:17px;
  --fs-lab:13px;        /* was 10.5px tracked caps: the devtool tell */
  --fs-dlab:12.5px;
  --fs-till:11.5px;     /* printed small type on the tape stays small */

  --measure:60ch;
  --pad:clamp(22px,4vw,64px);
  --sec:clamp(40px,5vw,72px);     /* section rhythm; between the tight original and the loose pass */
  --rise:cubic-bezier(.16,1,.3,1);
  --spring:cubic-bezier(.34,1.56,.64,1);
}

body {
  background:var(--sheet);
  color:var(--ink);
  font-family:var(--body);
  font-size:var(--fs-body);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
}

::selection { background:var(--lift); color:#fff; }
/* TWO-TONE FOCUS RING, one treatment for the whole site. The green alone
   measured 7.03:1 on cream and 7.79:1 on receipt paper but collapsed to 1.99:1
   on the ink footer, so a keyboard user reaching the three contact links got a
   ring they could not see. Green sits immediately against the element and the
   light stroke sits immediately outside it, so whichever ground it lands on,
   one of the two clears 3:1 and the pair always contrast with each other
   (7.79:1). Deliberately not a footer override: one focus style to maintain. */
:focus-visible { outline:3px solid var(--tape); outline-offset:4px;
                 box-shadow:0 0 0 4px var(--lift); }

h1,h2,h3 { font-family:var(--body); font-weight:500; text-wrap:balance; }

/* `lab` is an editorial label: sentence case, gentle tracking. `dlab` is a
   DATA label and keeps its caps, because caps is what makes a metric row read
   as a ledger line rather than a sentence. */
.lab  { font-family:var(--mono); font-size:var(--fs-lab); letter-spacing:.05em; color:var(--muted); }
.dlab { font-family:var(--mono); font-size:var(--fs-dlab); letter-spacing:.08em; text-transform:uppercase; color:var(--muted); }
.fig  { font-family:var(--mono); font-weight:700; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
.till { font-family:var(--mono); font-size:var(--fs-till); letter-spacing:.03em; line-height:1.95; }
.rule { border:none; border-top:1px dashed var(--faint); margin:12px 0; }

/* 1170: halfway between the original 1120 and the 1220 that felt too wide,
   so the margin comes in 25px a side rather than 50. Below 1170 the sheet is
   full width and --pad governs, so small screens are unaffected. */
.sheet { max-width:1170px; margin-inline:auto; }
.sheet-in { padding-inline:var(--pad); }

/* ---- navigation: one line, no bars ------------------------------------
   Both hairline rules and the second tier are gone. Links are set at reading
   size in the body serif so the bar reads as a menu, not as chrome
   announcing itself. Mono survives only on the true utility text. */
/* Three zones, not two. The nav used to live inside a flex wrapper with the
   status, so it could only ever sit beside it: neither centred nor right
   aligned. A 1fr auto 1fr grid centres the nav on the header regardless of how
   wide the name and status are, and since the sheet is centred in the viewport
   that is true viewport centring. align-items:baseline puts all three on one
   baseline, which the old nesting could not do because a flex container
   exposes only its first item's baseline to its parent. */
.masthead {
  display:grid; grid-template-columns:1fr auto 1fr; align-items:baseline;
  gap:10px clamp(20px,4vw,48px); padding:clamp(24px,3vw,38px) var(--pad) clamp(18px,2.4vw,26px);
}
.masthead .name { font-size:21px; font-weight:600; letter-spacing:-.008em; color:inherit; text-decoration:none; }
/* The status is not navigation. Its gap is roughly double the space between
   nav items so the eye groups them separately, it sits a size down, and it is
   a span with no hover and no pointer, so it never invites a click. */
.masthead nav { display:flex; gap:clamp(16px,2.4vw,28px); justify-self:center; }
.masthead nav a { font-size:17px; color:var(--muted); text-decoration:none; transition:color .15s ease-out; }
.masthead nav a:hover { color:var(--ink); }
.masthead nav a.here { color:var(--ink); }
.masthead .util { font-family:var(--mono); font-size:11.5px; letter-spacing:.05em; color:var(--muted);
                  cursor:default; justify-self:end; white-space:nowrap; }
/* Below ~820px the three zones cannot sit on one line without the status
   running into the links, so the header stacks instead of colliding. */
@media (max-width:820px){
  .masthead { display:flex; flex-wrap:wrap; align-items:baseline; gap:8px clamp(16px,3vw,28px); }
  .masthead .name { flex:1 0 100%; }
  .masthead nav, .masthead .util { justify-self:auto; }
}
.dot { display:inline-block; width:6px; height:6px; background:var(--lift); border-radius:50%; margin-right:8px; }

/* ---- the till receipt object ---- */
.tillcol { width:min(356px,88vw); }
.slot { height:13px; background:var(--ink); border-radius:3px; position:relative; z-index:3; }
.paper { position:relative; z-index:1; margin:0 9px; background:var(--tape); padding:24px 20px 16px; overflow:hidden;
         box-shadow:0 1px 2px rgba(20,16,10,.16), 0 14px 30px rgba(20,16,10,.14);
         clip-path:polygon(0 0,100% 0,100% calc(100% - 9px),
           95% 100%,90% calc(100% - 9px),85% 100%,80% calc(100% - 9px),75% 100%,
           70% calc(100% - 9px),65% 100%,60% calc(100% - 9px),55% 100%,50% calc(100% - 9px),
           45% 100%,40% calc(100% - 9px),35% 100%,30% calc(100% - 9px),25% 100%,
           20% calc(100% - 9px),15% 100%,10% calc(100% - 9px),5% 100%,0 calc(100% - 9px)); }
/* The window the receipt prints out of. It carries the reveal so .paper can
   keep its torn-edge clip-path, and it changes no layout while animating. */
.paper-window { position:relative; }

/* The mouth of the printer. Anything above the window's top edge is inside the
   machine and does not render, so a sheet sliding down out of the slot has its
   top genuinely cut off rather than merely uncovered. Sides and bottom run
   wide so the paper's drop shadow is never clipped. */
.printing .paper-window { clip-path:inset(0 -70px -70px -70px); }

/* `.printing` marks the one receipt that performs: the hero. Its resting state
   is the END of that performance, torn free with the printer gone, so reduced
   motion and any no-animation path land there instantly instead of waiting.
   Only opacity and translate, so the slot still holds its 13px of layout and
   nothing below the hero moves. Other receipts keep their printer bar. */
.printing .slot { opacity:0; }
.printing .paper-window { translate:0 -16px; }

/* ---- tape ---------------------------------------------------------------
   One treatment, two placements, so the About portrait and the hero receipt
   read as the same system. Translucent so the paper shows through, cut edges
   slightly darker, soft shadow so the strip sits above what it holds. */
.taped { position:relative; }
.taped::before, .taped::after {
  content:""; position:absolute; width:106px; height:27px; z-index:4;
  background:
    linear-gradient(105deg, rgba(255,255,255,.36) 0%, rgba(255,255,255,0) 45%),
    linear-gradient(to bottom, rgba(233,224,204,.84), rgba(214,203,180,.88));
  border-left:1px solid rgba(163,150,124,.3);
  border-right:1px solid rgba(163,150,124,.3);
  box-shadow:0 1px 3px rgba(20,16,10,.17);
}
/* Two placements, and the placement carries meaning: top corners for a sheet
   still leaving the slot, opposite corners for one that was torn off and kept.
   So the homepage hero is taped-top and everything filed away — the About
   portrait, every case receipt — is taped. */
/* default: opposite corners, as on the About portrait */
.taped::before { top:-13px; left:-26px; transform:rotate(-6deg); }
.taped::after  { bottom:-13px; right:-26px; transform:rotate(-6deg); }
/* both top corners, for a sheet held down where it leaves the slot. Applied to
   the outer .tillcol, never to .paper: .paper carries the torn-edge clip-path,
   and a clipped element clips its own pseudo-elements too. */
.taped-top::before { top:-7px; left:-24px; transform:rotate(-7deg); }
.taped-top::after  { top:-7px; right:-24px; bottom:auto; transform:rotate(6deg); }
@media (max-width:560px){
  .taped::before, .taped::after { width:84px; }
  .taped-top::before { left:-12px; } .taped-top::after { right:-12px; }
}

.paper .c { text-align:center; }
.paper .shopname { font-weight:700; font-size:13px; letter-spacing:.14em; }
.paper .row { display:flex; justify-content:space-between; gap:10px; }
.paper .row .v { font-weight:700; }
.paper .row.lift .v { color:var(--lift); }
.paper .row.leakr .v { color:var(--leak); }
.paper .grp { font-weight:700; margin-top:5px; }
.paper .small { font-size:9.5px; color:var(--muted); line-height:1.85; }
.barcode { height:32px; margin:13px 5px 5px;
  background:repeating-linear-gradient(to right,
    var(--ink) 0 2px, transparent 2px 4px, var(--ink) 4px 5px, transparent 5px 9px,
    var(--ink) 9px 12px, transparent 12px 14px, var(--ink) 14px 15px, transparent 15px 18px,
    var(--ink) 18px 20px, transparent 20px 25px); }

/* ---- shared pieces ---- */
.band { padding-block:var(--sec); }
.sechead { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:baseline;
           gap:8px 22px; border-top:1px solid var(--ink); padding-top:15px; }
.sechead h2 { font-size:var(--fs-h2); letter-spacing:-.014em; }
.go { font-family:var(--mono); font-size:var(--fs-lab); letter-spacing:.05em; color:var(--ink);
      text-decoration:none; border-bottom:1px solid var(--lift); padding-bottom:2px; }
.go:hover { color:var(--lift); }

/* plates: work mounted on till paper, so imagery joins the receipt stack */
figure.plate { margin-block:clamp(28px,3.5vw,46px); }
figure.plate img { display:block; width:100%; height:auto; background:var(--tape); border:1px solid var(--line); }
figure.plate figcaption { margin-top:11px; font-size:15px; font-style:italic; color:var(--muted); }
.slip figure.plate { margin-inline:calc(-1 * var(--slip-pad)); }
.slip figure.plate img { border-inline:0; }
.slip figure.plate figcaption { margin-inline:var(--slip-pad); }

/* Wide plates on narrow screens -------------------------------------------
   A five-phone flow sequence squeezed into a 350px column renders each phone
   about 58px wide, which is unreadable. Below 700px a `.wide` plate becomes a
   horizontal scroller at a legible minimum width instead of scaling down or
   being silently cropped, so no part of the sequence is lost. The scroll is
   contained to the figure: the page body never scrolls sideways. */
figure.plate .hint { display:none; }
@media (max-width:700px){
  figure.plate.wide .scroller {
    overflow-x:auto; overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch;
  }
  figure.plate.wide .scroller img { min-width:var(--wide-min,720px); max-height:none; }
  figure.plate.wide .hint {
    display:block; margin-bottom:8px; font-family:var(--mono); font-size:11.5px;
    letter-spacing:.05em; font-style:normal; color:var(--btx);
  }
}

/* ---- case study template ------------------------------------------------
   Shared by all three case studies so they stay one system. One steady
   reading column carries the argument; only plates break out. The job receipt
   floats into the head the same way the tape floats into the homepage hero. */
.col { max-width:var(--measure); }

.head { display:flow-root; padding-block:clamp(28px,3.6vw,48px) clamp(26px,3.2vw,42px); }
.head-till { float:right; margin:8px 0 28px clamp(30px,5vw,60px); }
.head-till .tillcol { width:min(304px,88vw); }
.head h1 { margin-top:14px; font-size:var(--fs-h1); font-weight:400; line-height:1.02;
           letter-spacing:-.024em; max-width:16ch; font-variation-settings:"opsz" 64; }
.head .standfirst { margin-top:20px; max-width:44ch; color:var(--muted); }
@media (max-width:900px){
  .head { display:flex; flex-direction:column; }
  /* ordered by `.kicker`, never by `.lab`: other elements carry `lab` too */
  .head .kicker { order:1 } .head h1 { order:2 } .head .standfirst { order:3 }
  .head-till { order:4; float:none; margin:30px 0 0; }
}

.docket { display:grid; grid-template-columns:repeat(4,1fr); gap:20px;
          border-block:1px solid var(--ink); padding-block:18px; }
@media (max-width:780px){ .docket { grid-template-columns:repeat(2,1fr); } }
.docket .v { margin-top:8px; font-size:var(--fs-sm); }

.opening { padding-block:clamp(32px,3.9vw,52px); }
.caveat { margin-top:18px; font-size:var(--fs-sm); font-style:italic; color:var(--muted); max-width:58ch; }

/* decisions are line items on the job, each priced with what it cost */
.item { padding-block:clamp(34px,4.2vw,58px); border-top:1px dashed var(--faint); }
.item .no { color:var(--leak); font-weight:700; }
.item h2 { margin-top:12px; max-width:24ch; font-size:var(--fs-h2); line-height:1.14; letter-spacing:-.016em; }
.item .body { margin-top:18px; color:var(--muted); }
.item .body p + p { margin-top:14px; }

/* the swap, set as a returned/exchanged pair on till paper */
.swap { margin-top:26px; max-width:var(--measure); background:var(--tape);
        box-shadow:0 1px 2px rgba(20,16,10,.14), 0 12px 26px rgba(20,16,10,.12); }
.swap .two { display:grid; grid-template-columns:1fr 1fr; }
@media (max-width:640px){ .swap .two { grid-template-columns:1fr; } }
.swap .h { padding:22px 24px; }
.swap .h.b { background:var(--before-bg); border-right:1px dashed var(--faint); }
@media (max-width:640px){ .swap .h.b { border-right:0; border-bottom:1px dashed var(--faint); } }
.swap .h.b .dlab { color:var(--btx); }
.swap .h.a .dlab { color:var(--lift); }
.swap .h .t { margin-top:12px; font-size:18px; line-height:1.3; }
.swap .h.b .t { color:#615549; }
.swap .h .d { margin-top:8px; font-size:var(--fs-sm); color:var(--muted); }
.swap .h.b .d { color:var(--btx); }
.swap .foot { border-top:1px dashed var(--faint); padding:13px 24px; display:flex; flex-wrap:wrap;
              gap:8px 14px; align-items:baseline; }
.swap .foot .to { color:var(--lift); }
/* Green is measured movement, and a page carrying only baselines and
   projections has none. On .unmeasured pages the after-state labels go neutral
   rather than colouring a result that has not been observed yet. This is the
   label treatment only: the availability dot and link underlines are interface
   chrome that happen to share the hue, and they stay. */
.unmeasured .swap .h.a .dlab,
.unmeasured .swap .foot .to { color:var(--ink); }

/* what a decision cost, stated rather than hidden */
.cost { margin-top:22px; max-width:54ch; border-left:2px solid var(--leak);
        background:var(--before-bg); padding:15px 19px; }
.cost .k { color:var(--leak); }
.cost p { margin-top:8px; font-size:var(--fs-sm); color:var(--btx); }

/* Plates sit inside the margin now, so their left edge starts where the text
   starts instead of running out past it. The old negative margin pushed them
   to the sheet edge, which broke the one vertical line the page otherwise
   holds. Kept as `.bleed` so the markup does not have to change if this gets
   reverted. */
figure.plate.bleed { margin-inline:0; }
/* Bounded, not raw vh: unbounded 66vh let plates grow with the window. */
figure.plate.bleed img { border-inline:1px solid var(--line); max-height:clamp(300px,66vh,620px); object-fit:contain; }
figure.plate.bleed figcaption { margin-inline:0; }

/* Cream, not ink: the counterfoil footer is the one dark block on the page,
   and stacking two of them buried its perforated tear edge. */
.next { margin-inline:var(--pad); border-top:1px solid var(--ink);
        padding-block:clamp(26px,3.2vw,42px) 0;
        display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-end; gap:18px 22px; }
.next .t { font-size:clamp(1.4rem,3vw,2.05rem); letter-spacing:-.018em; margin-top:8px; max-width:24ch; line-height:1.12; }

/* ---- the counterfoil: the footer is the tear-off stub -------------------
   A perforated top edge rather than a plain dark band. This is the artifact
   doing the work at the point where the page would otherwise go generic. */
/* Inset to the content column, so the stub's left edge lines up with the
   section headings above it rather than landing between the content column and
   the viewport, which aligned to neither. */
.colophon { position:relative; background:var(--ink); color:var(--sheet);
            margin:var(--sec) var(--pad) 0;
            padding:clamp(34px,4vw,50px) clamp(26px,3vw,44px) clamp(28px,3.4vw,42px); }
.colophon::before {
  content:""; position:absolute; inset-inline:0; top:-7px; height:14px;
  background:radial-gradient(circle at 7px 7px, var(--sheet) 6.5px, transparent 7px) repeat-x 0 0/14px 14px;
}
/* Top-aligned, not centred: against a two-line headline the action cluster
   used to float in the gutter between "Senior IC roles" and "contract". The
   margin drops it past the "Available now" label so it starts level with the
   headline's first line. */
.colophon .in { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-start; gap:24px 32px; }
/* Both columns now start at the container top. The 30px on .acts and the 10px
   on .t existed to clear the "Available now" label that sat above the headline;
   with the label gone they were holding the buttons 20px below the headline
   they are supposed to line up with. */
.colophon .acts { margin-top:0; }
@media (max-width:700px){ .colophon .acts { margin-top:18px; } }
.colophon .t { font-size:clamp(1.5rem,3.2vw,2.3rem); letter-spacing:-.016em; max-width:24ch; line-height:1.1; }
.colophon .lab { color:#a2968a; }
.colophon a { color:var(--sheet); text-decoration:none; font-family:var(--mono); font-size:var(--fs-lab); letter-spacing:.05em; }
/* An action label, not the raw address. A white box containing an email
   address reads as a filled input, which is exactly the ambiguity to avoid;
   labelling it with the verb makes it unmistakably a button, and it now
   matches the "Work with me" button on About rather than sitting between a
   button and a link. */
.colophon a.primary { background:var(--sheet); color:var(--ink); padding:14px 26px;
                      transition:background .18s,color .18s; }
.colophon a.primary:hover { background:var(--lift); color:#fff; }
.colophon a.quiet { border-bottom:1px solid var(--rule-on-ink); padding-bottom:2px; }
.colophon .acts { display:flex; flex-wrap:wrap; gap:12px 24px; align-items:baseline; }

/* ---- motion -------------------------------------------------------------
   NOTHING IS HIDDEN BEHIND AN ANIMATION. The previous build set opacity:0 in
   the base rule and relied on a keyframe to restore it, which made the whole
   page invisible whenever the animation was deferred or dropped. Initial
   states now live only inside @keyframes, so if animations never run the
   content simply renders. The sheet itself is never faded at all. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes rise { from { opacity:0; transform:translateY(14px); } }
  /* Cards floor at 10% rather than 0. A card cresting the bottom edge of the
     viewport at literally zero opacity makes the page look like it ends at the
     fold, so there is nothing to scroll toward. A tenth of an inch of ink is
     enough to read as "something is down there" without pre-empting the
     arrival. */
  @keyframes rise-card { from { opacity:.1; transform:translateY(14px); } }
  @keyframes fadein { from { opacity:0; } }

  /* MOTION BELONGS TO THE OBJECTS, NOT THE TYPE.
     On load the receipt performs: it feeds 190px down out of the printer,
     tears, rides up, gets taped. If the type also travels, two things compete
     for the same moment and they even disagree about direction, since the copy
     was rising while the paper fed downward. So page-load text only fades.

     It is also faster and finishes sooner: every line has settled by roughly
     630ms, before the tear at 700ms, so the sequence reads as two beats, the
     claim arriving and then the object performing, rather than one scrum. The
     h1 is the LCP element and no longer waits on a transform to be legible. */
  .r  { animation:fadein 450ms ease-out backwards; }
  .r2 { animation-delay:.06s; } .r3 { animation-delay:.12s; } .r4 { animation-delay:.18s; }

  /* THE PRINTER. 930ms, once on load, on the document timeline so it never
     replays on scroll.
       0 to 480ms    prints out of the slot, straight cut edge advancing
       480 to 580    the tear: a sideways snap, and the torn edge appears
       600 to 770    the receipt rides up over the printer, printer fades out
       800 to 930    two strips of tape press it down at the top corners

     A clip reveal, not a height animation. The paper's bottom edge advances
     while its text stays put, which is what paper leaving a printer does, and
     it changes nothing in layout: the old max-height version pushed every
     section below the hero 111px out at 1440 and 511px at 485.

     Why the tear reads: the window clips a straight line as it descends, so
     the leading edge is square while the paper is still "attached". Only when
     the clip clears the bottom does .paper's own scalloped clip-path show,
     which is the moment the sheet comes off the roll. The snap sells it.

     Every property here is clip-path, translate, or opacity. Nothing is
     injected, rasterised, or gated, and the receipt's text is ordinary DOM
     content, readable long before the tape lands. */
  /* A gentle ease-out, not an expo one: a printer feeds paper at a fairly
     steady rate and eases only at the end. The sharper curve had the print 96%
     finished by 240ms, which read as a snap rather than a feed. */
  /* THE FEED. The sheet is not uncovered in place, it travels: it starts 190px
     up inside the machine, so its top is genuinely cut off at the slot and only
     the lower part is showing, then rides down until the whole receipt is out.
     190 of roughly 515px, because it should read as a sheet already part way
     through printing rather than starting from nothing.

     Because the paper itself moves, its own torn bottom edge travels with it,
     which is the advancing tear the height-based version had and a static clip
     reveal could never produce.

     On `transform`, not `translate`: the tear snap owns `translate` and the
     settle owns `rotate`, so all three animate the same element without any of
     them wiping another. */
  @keyframes feed { from { transform:translateY(-190px); } to { transform:translateY(0); } }

  .printing .paper-window {
    animation:lift 170ms cubic-bezier(.22,1,.36,1) 820ms backwards;
  }
  /* Rests at -16px, so this animates up from 0, which is the paper's natural
     flow position: top edge flush against the bottom of the slot. `translate`
     is absolute, not relative to the base, so a `from` of 16px pushed the
     sheet a further 16px clear of the printer and it printed into thin air
     instead of out of the machine. */
  @keyframes lift { from { translate:0 0; } }

  /* The tear comes off in two parts, on two separate properties so neither
     wipes the other: a sideways snap as the sheet parts from the roll, and a
     slow rock as it settles. The rock is the old sway, which the first clip
     version dropped along with the torn edge. */
  .printing .paper {
    animation:feed 700ms cubic-bezier(.25,.46,.45,.94) both,
              tear 110ms ease-out 700ms backwards,
              settle 340ms cubic-bezier(.34,1.3,.64,1) 740ms backwards;
  }
  @keyframes tear {
    0% { translate:0 0; } 35% { translate:3px 0; } 70% { translate:-2px 0; } 100% { translate:0 0; }
  }
  @keyframes settle {
    0% { rotate:0deg; } 38% { rotate:.65deg; } 100% { rotate:0deg; }
  }

  /* the printer has done its job and leaves */
  .printing .slot { animation:unplug 170ms ease-out 820ms backwards; }
  @keyframes unplug { from { opacity:1; } }

  /* The angle lives in `transform`, so the entrance animates `translate`
     instead. Sharing one property would wipe the rotation on the last frame. */
  .taped-top::before, .taped-top::after { animation:press 130ms ease-out 1020ms backwards; }
  @keyframes press { from { opacity:0; translate:0 -8px; } }

  .pulse { animation:p 2.4s ease-in-out infinite; }
  @keyframes p { 0%,100% { opacity:1; } 50% { opacity:.35; } }

  /* Case study headlines arrive a word at a time, so the claim reads as a
     sentence being spoken rather than a block appearing. Only on the case
     pages: the homepage headline shares its moment with the receipt, and two
     things performing at once is what we spent the last pass removing. The
     words are ordinary text nodes, so the headline is still one string to a
     crawler and to a screen reader. */
  /* Slower on the case pages than on the homepage: nothing else is moving
     here, so the headline can take its time arriving. */
  .wordfade .w { display:inline-block; animation:fadein 620ms ease-out backwards;
                 animation-delay:calc(var(--i) * 80ms + 120ms); }
  .head .r { animation-duration:640ms; }
  .head .r2 { animation-delay:.14s; } .head .r3 { animation-delay:.28s; }

  @supports (animation-timeline: view()) {
    .sv { animation:rise linear backwards; animation-timeline:view(); animation-range:entry 4% entry 30%; }
    /* Cards get a longer, later range than section headings so each one is
       clearly its own arrival as you come down the page. */
    .slip.sv { animation-name:rise-card; animation-range:entry 2% entry 38%; }
  }
}

/* ---- data figures ---------------------------------------------------------
   Drawn in HTML rather than exported as SVG. An <img> cannot reach the page's
   webfonts, so an SVG chart would set its labels in the browser's fallback
   mono while every other figure on the site sets in Martian Mono, and the mono
   is load-bearing for the honesty system. Drawing them here also means they
   reflow at 390 instead of needing the horizontal scroller a fixed viewBox
   would force. Every bar is a percentage of its own scale, so the geometry is
   the data rather than an illustration of it. */
.chart { background:var(--tape); border:1px solid var(--line);
         padding:clamp(20px,2.6vw,30px) clamp(18px,2.4vw,28px); }
.chart-h { font-family:var(--mono); font-size:11.5px; letter-spacing:.085em;
           text-transform:uppercase; color:var(--muted); line-height:1.6; }
/* padding, not max-width: the rule is this element's border-top, so capping
   the width would stop the rule short of the chart it belongs to. Padding
   shortens the mono measure while the rule still spans the full box. */
.chart-foot { margin-top:20px; padding-top:14px; padding-right:36%;
              border-top:1px solid var(--line);
              font-family:var(--mono); font-size:10.5px; letter-spacing:.035em;
              color:var(--btx); line-height:1.8; }
@media (max-width:700px){ .chart-foot { padding-right:0; } }

/* ONE grid for the whole chart, with each row as display:contents, so every
   track resolves to the identical width. Per-row grids looked correct until a
   wider value like "0.007-0.02%" shrank that row's track and quietly broke the
   shared scale the chart is entirely built on. */
.bars, .ranges { margin-top:22px; display:grid;
                 grid-template-columns:minmax(92px,150px) 1fr max-content;
                 gap:15px 14px; align-items:center; }
.bar-row, .range-row, .range-axis { display:contents; }
.bar-lab, .range-lab { font-size:var(--fs-sm); line-height:1.22; }
.bar-val, .range-val { font-family:var(--mono); font-size:12.5px; font-weight:700;
                       font-variant-numeric:tabular-nums; white-space:nowrap; }

.bar-track { height:22px; background:var(--before-bg); }
/* min-width so the near-zero channels stay visible as a sliver: the point of
   the chart is that they are almost nothing, not that they are nothing */
.bar-fill { display:block; height:100%; width:var(--w); min-width:3px; background:var(--ink); }
.bar-row.leakr .bar-fill { background:var(--leak); }
.bar-row.leakr .bar-val { color:var(--leak); }

/* one shared 0-60% scale across all three rows, so the two that sit inside the
   benchmark and the one that sits far outside it are directly comparable */
.range-track { position:relative; height:24px; background:var(--before-bg); }
.range-band { position:absolute; inset-block:0; left:var(--a); width:var(--w); background:var(--ink); }
.range-row.leakr .range-band { background:var(--leak); }
.range-row.leakr .range-val { color:var(--leak); }
.range-pt { position:absolute; inset-block:0; left:var(--a); width:3px; translate:-1px 0; background:var(--ink); }
.axis-ticks { grid-column:2; position:relative; height:19px; margin-top:-6px;
              border-top:1px solid var(--line); }
.axis-ticks span { position:absolute; top:5px; left:var(--a); translate:-50% 0;
                   font-family:var(--mono); font-size:10px; color:var(--btx); }
.axis-ticks span:first-child { translate:0 0; }
.axis-ticks span:last-child { translate:-100% 0; }

/* narrow: the label column cannot hold "Kejsar, top ten by volume" and leave a
   readable track, so the label takes its own line above the bar it names */
@media (max-width:560px){
  .bars, .ranges { grid-template-columns:1fr max-content; gap:6px 12px; }
  .bar-lab, .range-lab { grid-column:1 / -1; margin-top:9px; }
  .bar-track, .range-track { grid-column:1; }
  .axis-ticks { grid-column:1; }
}
