/* Shell — tokens, reset and the utility panel — is in site.css.
   Below is only what this page adds or overrides on top of it. */
html{scroll-behavior:smooth}
/* clip, not hidden: hidden would make body a scroll container and kill position:sticky.
   Needed because the work-list glow paints past the column on both sides. */
body{overflow-x:clip}
h1,h2,h3{font-weight:600;letter-spacing:-.03em;line-height:.92}

/* every corner, one radius — the shell already covers .me img/.row/.status/.cta */
.toggle,.tabs,.cell,.stat,.plate,.log,.ov .close,.ov .tags span,
.w .flag,.log input,.swatches i{border-radius:var(--r)}
.toggle button:first-child,.tabs button:first-child{border-radius:var(--r) 0 0 var(--r)}
.toggle button:last-child,.tabs button:last-child{border-radius:0 var(--r) var(--r) 0}
.log{overflow:hidden}                     /* clip header + form to the rounded frame */

/* ── main ───────────────────────────────────────────────────────── */
main{margin-left:var(--bar);padding:0 clamp(20px,5vw,72px)}
.pad{padding:clamp(64px,10vh,132px) 0;border-bottom:1px solid var(--line)}

/* ── display type ────────────────────────────────────────────────────
   Headlines are set word-by-word, not as one block of text: each word is
   its own inline-flex box so it can carry a colour, a highlight pill, an
   inline picture, or its own entrance delay. That word-level control is
   the whole effect — a sentence that reads like a layout. */
.mega{font-size:clamp(44px,8.6vw,132px);font-weight:700;letter-spacing:-.055em;line-height:.9;
  display:flex;flex-wrap:wrap;align-items:baseline;column-gap:.24em;row-gap:.04em}
.sect{display:flex;align-items:baseline;gap:18px;flex-wrap:wrap;margin-bottom:clamp(28px,5vh,56px)}
.sect h2{font-size:clamp(34px,5.8vw,84px);font-weight:700;letter-spacing:-.05em;
  display:flex;flex-wrap:wrap;align-items:baseline;column-gap:.22em}
.lede{max-width:56ch;font-size:17px;color:var(--ink-2);margin-top:26px}

/* one word = one box */
.wd{display:inline-flex;align-items:baseline;position:relative}
.wd.mut{color:var(--ink-3)}                        /* dropped back, so the rest reads as emphasis */
.wd.acc{color:var(--accent)}
.wd.ital{font-style:italic;font-weight:400;letter-spacing:-.02em}
.wd.out{color:transparent;-webkit-text-stroke:1.4px var(--ink)}
/* highlight pill sits behind the glyphs, sized off the word's own box */
.wd.pill{color:var(--paper);padding:0 .18em;margin:0 -.04em;isolation:isolate;--fill:1}  /* own stacking context, or z-index:-1 falls behind the page background; --fill is the pill's sweep, 1 = fully drawn */
/* insets are uneven on purpose: line-height is .9, so the word box already
   crops the descender — the pill has to reach further down than up to sit
   optically even between cap-height and the tail of a y or g */
.wd.pill::before{content:"";position:absolute;inset:-.09em -.02em -.14em;z-index:-1;
  background:var(--accent);border-radius:999px;transform:scaleX(var(--fill,1));transform-origin:left}
/* a picture used as a word: same baseline, same rhythm */
.wd.pic{width:1.5em;height:1.05em;border-radius:999px;overflow:hidden;align-self:center;
  background:repeating-linear-gradient(135deg,var(--paper-2) 0 7px,var(--paper) 7px 14px);
  box-shadow:0 0 0 1px var(--line);transform:translateY(.06em)}
.wd.pic img{width:100%;height:100%;object-fit:cover;display:block}
.br{flex-basis:100%;height:0}                      /* line break inside a flex headline */
/* per-word entrance: words hold at 0 until the headline is marked .in */
.wd{opacity:0}
.in .wd{animation:aWord 520ms cubic-bezier(.2,.85,.25,1) var(--d,0ms) both}
.in .wd.pill::before{animation:aPill 420ms cubic-bezier(.2,.8,.2,1) calc(var(--d,0ms) + 120ms) both}
@keyframes aPill{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* ── work: list ⇄ grid ──────────────────────────────────────────── */
.toggle{display:flex;border:1px solid var(--line);margin-left:auto}
.toggle button{position:relative;padding:7px 12px;font:500 11px/1 var(--mono);letter-spacing:.12em;color:var(--ink-3);
  transition:color var(--t),background var(--t)}
.toggle button[aria-pressed=true]{background:var(--ink);color:var(--paper)}
/* same wipe as the case tabs: hovering an unpressed view plays the switch it does */
.toggle button > span{position:relative;z-index:1}
.toggle button::before{content:"";position:absolute;inset:0;z-index:0;background:var(--accent);
  border-radius:999px;transform:scaleX(0);transform-origin:0 50%;
  transition:transform 420ms cubic-bezier(.34,1.56,.64,1),border-radius 420ms cubic-bezier(.2,.8,.2,1)}
.toggle button:hover:not([aria-pressed=true])::before{transform:scaleX(1);border-radius:3px}
.toggle button:hover:not([aria-pressed=true]){color:#0d0d0f}
.toggle button:active:not([aria-pressed=true])::before{transform:scaleX(1) scaleY(.94)}
.works{display:grid;gap:0}
.w{display:grid;grid-template-columns:2.4fr 1fr 1fr 66px;gap:16px;align-items:baseline;
  padding:22px 4px;border-top:1px solid var(--line);text-align:left;width:100%;transition:padding-left var(--t),background var(--t)}
.w:last-child{border-bottom:1px solid var(--line)}
.w:hover{padding-left:14px;background:var(--paper-2)}
.w .n{font-size:clamp(24px,3vw,40px);letter-spacing:-.04em;font-weight:600;line-height:1}
.w .m{font:400 12px/1.3 var(--mono);color:var(--ink-2)}
.w .arw{font:400 14px/1 var(--mono);white-space:nowrap;color:var(--ink-3);justify-self:end;transition:transform var(--t),color var(--t)}
.w:hover .arw{transform:translateX(4px);color:var(--accent)}
.w .flag{font:500 10px/1 var(--mono);letter-spacing:.12em;color:var(--accent);border:1px solid var(--accent);padding:4px 6px}
.w .n .flag{display:block;width:max-content;margin-top:10px}
/* list mode borrows the panel's cursor field: one accent glow under the pointer,
   rows leaning toward it by --pull. Same --mx/--my contract, same cursorPull(). */
@media (hover:hover) and (min-width:881px){
  .works:not(.grid){position:relative}
  /* the field reaches 260px, so the box it paints in has to reach further — at
     inset:0 the circle gets cut square at the first and last row. The offset in
     the gradient position cancels the enlarged box, so --mx/--my stay row-local. */
  .works:not(.grid)::after{content:"";position:absolute;inset:-280px;pointer-events:none;opacity:0;transition:opacity 240ms ease-out;
    background:radial-gradient(260px circle at calc(var(--mx,50%) + 280px) calc(var(--my,50%) + 280px),
      color-mix(in srgb,var(--accent) 9%,transparent),transparent 72%)}
  .works:not(.grid):hover::after{opacity:1}
  .works:not(.grid) .w{transition:padding-left var(--t),background var(--t),transform var(--t);
    transform:translateX(calc(var(--pull,0) * 5px))}
  .works:not(.grid) .w .arw{color:color-mix(in srgb,var(--accent) calc(var(--pull,0) * 100%),var(--ink-3))}
}
/* work is grouped into runs — the group title is set at display scale, the
   items under it at 12px. That jump is the whole hierarchy; no rules needed. */
.grp-t{font-size:clamp(30px,4.4vw,52px);font-weight:700;letter-spacing:-.045em;line-height:1;margin:0 0 20px}
.works .run + .run{margin-top:clamp(40px,6vh,72px)}

/* grid mode: uniform square covers, caption underneath. Same buttons as the
   list, restyled — one markup tree, two layouts. */
.works.grid{display:block}
.works.grid .run-items{display:grid;grid-template-columns:repeat(3,1fr);gap:30px 52px;padding-inline:40px}
/* the inline padding and the wider column gap are the runway the skill chips fan into */
.works.grid .w{display:block;position:relative;z-index:1;width:100%;padding:0;margin:0;border:0;background:none;
  text-align:left;transition:none}
.works.grid .w:hover{z-index:3}          /* the skill chips fan out past the card; they must sit over its neighbours */
/* .cw is only a positioning box: the chips live in it too, behind the plate, so they can leave it */
.works.grid .w .cw{display:block;position:relative;aspect-ratio:1}
.works.grid .w .cov{display:block;position:absolute;inset:0;z-index:1;overflow:hidden;border-radius:14px;
  background:var(--paper-2);box-shadow:0 0 0 1px var(--line);
  transition:border-radius 420ms cubic-bezier(.2,.8,.2,1)}
.works.grid .w:hover .cov{border-radius:26px}
.works.grid .w .cov::before{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(135deg,color-mix(in srgb,var(--ink) 6%,transparent) 0 12px,transparent 12px 24px);
  transition:transform 420ms cubic-bezier(.2,.8,.2,1)}
.works.grid .w:hover .cov::before{transform:scale(1.06)}
/* a real cover replaces the hatch; same 1.06 push on hover, on the image itself.
   contain, not cover: the covers are device mockups — cropping one cuts the device in half */
.works.grid .w .cov img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;object-position:center;
  padding:14%;transition:transform 420ms cubic-bezier(.2,.8,.2,1)}
.works.grid .w .cov:has(img)::before{content:none}
/* the covers are mockups on white; contain letterboxes them, so the plate matches the render, not the theme */
.works.grid .w .cov:has(img){background:#fff}
.works.grid .w:hover .cov img{transform:scale(1.06)}
.works.grid .w .n{display:block;margin-top:11px;font:600 13px/1.25 var(--sans);letter-spacing:-.01em;color:var(--ink)}
.works.grid .w .m{display:block;font:400 12px/1.3 var(--mono);color:var(--ink-3)}
.works.grid .w .m + .m,.works.grid .w .arw{display:none}   /* one meta line per card, like the reference */
.works.grid .w .n .flag{display:none}                      /* the cover carries the chip in grid mode */
/* the chip sits 10px inside the plate, so its radius tracks the plate's minus that
   inset — 14−10 and 26−10 — or the corners stop being concentric mid-transition */
.works.grid .w .cov .flag{position:absolute;top:10px;left:10px;z-index:1;
  background:var(--accent);color:#0d0d0f;border-color:var(--accent);border-radius:4px;
  transition:border-radius 420ms cubic-bezier(.2,.8,.2,1)}
.works.grid .w:hover .cov .flag{border-radius:16px}
/* skill chips: parked dead centre behind the plate, fanned to --l/--t on hover.
   left/top animate rather than translate because the offsets are % of the square, not of the chip */
/* --l/--t are the fanned-out spot, written inline per chip when the card renders;
   parked on the centre here so the hover rule has something to resolve against */
.works.grid .w .sk{position:absolute;left:50%;top:50%;z-index:0;pointer-events:none;--l:50%;--t:50%;
  padding:6px 11px;border-radius:999px;white-space:nowrap;
  font:500 11.5px/1 var(--mono);letter-spacing:.03em;color:var(--ink);
  /* the chips land half on the plate, half on the page, so they carry their own
     opaque field: full-contrast ink, a darker hairline, and a lift shadow */
  background:var(--paper);box-shadow:0 0 0 1px color-mix(in srgb,var(--ink) 24%,transparent),0 4px 14px -6px color-mix(in srgb,var(--ink) 40%,transparent);
  opacity:0;transform:translate(-50%,-50%) rotate(var(--r)) scale(.85);
  /* back-out easing: the chips overshoot their spot and settle, same bounce as the photo stack on the about page */
  transition:left 460ms cubic-bezier(.34,1.56,.64,1),top 460ms cubic-bezier(.34,1.56,.64,1),
             transform 460ms cubic-bezier(.34,1.56,.64,1),opacity 200ms ease;
  transition-delay:var(--d,0ms)}
.works.grid .w:hover .sk{left:var(--l);top:var(--t);opacity:1;
  transform:translate(-50%,-50%) rotate(var(--r)) scale(1)}
.works .cw{display:none}                                   /* list mode has no cover */
.works.grid .cw{display:block}
@media (max-width:1180px){.works.grid .run-items{grid-template-columns:repeat(2,1fr)}}
@media (max-width:520px){.works.grid .run-items{gap:20px 14px;padding-inline:0}
  .works.grid .w .sk{display:none}}   /* no runway on a phone, and no hover either */

/* ── flagship case ─────────────────────────────────────────────── */
.tabs{display:flex;gap:0;border:1px solid var(--line);width:max-content;margin-bottom:26px}
.tabs button{position:relative;padding:10px 18px;font:500 11px/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);border-right:1px solid var(--line);
  transition:color var(--t),background var(--t),transform 420ms cubic-bezier(.34,1.56,.64,1)}
.tabs button:last-child{border-right:0}
.tabs button[aria-selected=true]{background:var(--ink);color:var(--paper)}
/* hovering an unselected tab plays its own selection: the ink field wipes in
   from the left as an accent pill and settles into the square selected shape.
   Same back-out bounce as the skill chips, same radius-morph idea as the cover flag. */
.tabs button > span{position:relative;z-index:1}
.tabs button::before{content:"";position:absolute;inset:0;z-index:0;background:var(--accent);
  border-radius:999px;transform:scaleX(0);transform-origin:0 50%;
  transition:transform 420ms cubic-bezier(.34,1.56,.64,1),border-radius 420ms cubic-bezier(.2,.8,.2,1)}
.tabs button:hover:not([aria-selected=true])::before{transform:scaleX(1);border-radius:3px}
.tabs button:hover:not([aria-selected=true]){color:#0d0d0f}
.tabs button:active:not([aria-selected=true])::before{transform:scaleX(1) scaleY(.94)}

.panes>div[hidden]{display:none}
.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:8px}
.cell{background:var(--paper);padding:20px;box-shadow:0 0 0 1px var(--line)}  /* ring, not gap-fill: uneven auto-fit rows keep clean hairlines */
.cell h4{font:600 15px/1.3 var(--sans);margin:8px 0 6px}
.cell p{font-size:13.5px;color:var(--ink-2)}
.cell code{font:400 12px/1.7 var(--mono);color:var(--ink);display:block;white-space:pre-wrap;margin-top:12px}
.cell p code{margin-top:0}                                 /* inline code inside prose keeps the line */

/* .cta carries a sidebar margin it does not want out here, so each use resets it. */
.cta.alt{background:transparent;color:var(--ink)}
.cta.alt:hover{background:var(--ink);color:var(--paper)}
.fine-note{font:400 12px/1.6 var(--mono);color:var(--ink-3);margin-top:14px}
.swatches{display:flex;gap:0;margin-top:10px}
.swatches i{flex:1;height:44px;border-right:1px solid var(--paper)}
.scale{display:flex;flex-direction:column;gap:2px;margin-top:8px}
.scale b{font-weight:600;letter-spacing:-.04em;line-height:1}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin-top:26px}
.stat{background:var(--paper);padding:18px 20px;box-shadow:0 0 0 1px var(--line)}
.stat b{display:block;font:600 clamp(30px,4vw,46px)/1 var(--sans);letter-spacing:-.045em}
.stat span{font:400 11px/1.4 var(--mono);color:var(--ink-3);letter-spacing:.1em;text-transform:uppercase;display:block;margin-top:8px}

/* ── hero fact strip ────────────────────────────────────────────────
   Four answers, set in mono so they read as spec and not as sales copy.
   auto-fit, so it is one row on a monitor and two on a phone without a
   breakpoint being involved. */
/* Rings, not a background showing through 1px gaps — the gap trick paints a
   grey slab wherever the last row is short of cells, which is exactly the
   failure the v3 case study complains about. */
.facts{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:1px;margin-top:34px;max-width:760px}
.facts li{background:var(--paper);padding:14px 16px;box-shadow:0 0 0 1px var(--line)}
.facts b{display:block;font:600 19px/1.1 var(--sans);letter-spacing:-.03em}
.facts span{display:block;font:400 11px/1.4 var(--mono);color:var(--ink-3);margin-top:5px}

/* ── price tiers ────────────────────────────────────────────────────
   Same ring-not-gap hairline as .cell. The middle one is marked because a
   buyer with three options and no guidance picks none of them. */
.tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px}
.tier{position:relative;background:var(--paper);padding:24px;box-shadow:0 0 0 1px var(--line)}
.tier.feature{box-shadow:0 0 0 1px var(--accent)}
.tier .flag{position:absolute;top:-1px;right:-1px;background:var(--accent);color:#0d0d0f;
  font:500 10px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;padding:6px 9px;border-radius:0 0 0 var(--r)}
.tier h3{font:600 19px/1.25 var(--sans);letter-spacing:-.025em;margin:8px 0 14px;max-width:22ch}
.tier .from{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid var(--line)}
.tier .from b{font:600 clamp(28px,3.4vw,38px)/1 var(--sans);letter-spacing:-.045em}
.tier .from span{font:400 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.tier > p{font-size:13.5px;color:var(--ink-2)}
.tier .inc{list-style:none;margin:16px 0 0;display:grid;gap:7px}
.tier .inc li{position:relative;padding-left:20px;font-size:13.5px;color:var(--ink-2)}
/* the tick is drawn, not a glyph: one accent mark per line, no font dependency */
.tier .inc li::before{content:"";position:absolute;left:2px;top:.42em;width:9px;height:5px;
  border-left:1.5px solid var(--accent);border-bottom:1.5px solid var(--accent);transform:rotate(-45deg)}
.tier code{font:400 12px/1.6 var(--mono);color:var(--ink-3);display:block;margin-top:18px;
  padding-top:14px;border-top:1px solid var(--line)}
/* Picking a tier is the smallest commitment on the page: it fills the enquiry
   form in and moves you there. The card grows a button and the layout stops
   caring how long each tier's text is, because the button is pinned to the
   bottom — three cards, three buttons on one line. */
.tier{display:flex;flex-direction:column}
.tier code{margin-bottom:18px}
.tier .pick{margin-top:auto;width:100%;padding:13px;cursor:pointer;
  border:1px solid var(--ink);border-radius:var(--r);background:transparent;color:var(--ink);
  font:500 12px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  transition:background var(--t),color var(--t),border-color var(--t)}
.tier .pick:hover{background:var(--ink);color:var(--paper)}
.tier.feature .pick{background:var(--accent);border-color:var(--accent);color:#0d0d0f}
.tier.feature .pick:hover{background:transparent;color:var(--accent)}
/* the tier you came from stays marked while you fill the form in */
.tier.picked{box-shadow:0 0 0 2px var(--accent)}
.tier.picked .pick{background:var(--accent);border-color:var(--accent);color:#0d0d0f}

/* ── process steps ──────────────────────────────────────────────── */
.steps{list-style:none;display:grid;gap:0;counter-reset:step;max-width:900px}
.steps li{display:grid;grid-template-columns:64px 1fr;gap:18px;padding:22px 0;border-top:1px solid var(--line)}
.steps li:last-child{border-bottom:1px solid var(--line)}
.steps .num{font:500 12px/1 var(--mono);letter-spacing:.14em;color:var(--accent);padding-top:5px}
.steps h4{font:600 17px/1.3 var(--sans);letter-spacing:-.02em;display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.steps .when{font:400 11px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.steps p{font-size:14px;color:var(--ink-2);margin-top:6px;max-width:64ch}

/* ── enquiry form ───────────────────────────────────────────────────
   Form left, the three ways round it right. A mailto: link alone loses
   everyone reading in webmail, which on a phone is most people. */
.reach{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(240px,1fr);gap:8px;margin-top:32px;align-items:start}
.enq{background:var(--paper);padding:24px;box-shadow:0 0 0 1px var(--line);display:grid;gap:14px}
.enq .two{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px}
.enq label{display:grid;gap:7px;font:500 11px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.enq input,.enq select,.enq textarea{width:100%;font:400 14px/1.5 var(--sans);color:var(--ink);
  background:var(--paper-2);border:1px solid var(--line);border-radius:var(--r);padding:11px 12px;
  transition:border-color var(--t),background var(--t)}
.enq textarea{resize:vertical;min-height:96px}
.enq input:focus,.enq select:focus,.enq textarea:focus{border-color:var(--accent);outline:none;background:var(--paper)}
.enq input::placeholder,.enq textarea::placeholder{color:var(--ink-3)}
.enq [name=_gotcha]{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.enq .cta{margin:6px 0 0;cursor:pointer}
.enq .sent{font:400 12px/1.6 var(--mono);color:var(--ink-3)}
.enq .sent.ok{color:var(--accent)}
.other{background:var(--paper);padding:24px;box-shadow:0 0 0 1px var(--line);display:grid;gap:8px;align-content:start}
.other .lbl{margin-bottom:6px}
.other .cta{margin:0;padding:14px;cursor:pointer}
@media (max-width:880px){.reach{grid-template-columns:1fr}}
footer a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
.bar .fine a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}

/* evidence (deep case only): v3 screenshots with numbered pins, each pin answered below */
.ev{display:grid;gap:1px;margin-top:20px}
.ov .ev figure{background:var(--paper);box-shadow:0 0 0 1px var(--line);padding:16px;margin:0}
.ev .shot{position:relative;display:block;background:var(--paper-2);box-shadow:0 0 0 1px var(--line)}
.ev .shot img{display:block;width:100%;height:auto}
.ev .pin{position:absolute;transform:translate(-50%,-50%);width:22px;height:22px;
  background:var(--accent);color:#fff;border-radius:50%;font:500 11px/22px var(--mono);text-align:center;
  box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 22%,transparent)}
.ov .ev figcaption{font:inherit;color:inherit;letter-spacing:normal;margin-top:14px}
.ev figcaption > .lbl{margin-bottom:10px}
.ev ol{list-style:none;display:grid;gap:1px}
.ev li{display:grid;grid-template-columns:22px 1fr;gap:12px;padding:11px 0;border-top:1px solid var(--line)}
.ev li b{font:500 11px/20px var(--mono);color:var(--accent);text-align:center;box-shadow:inset 0 0 0 1px var(--line);height:20px}
.ev li p{font-size:13.5px;color:var(--ink-2);margin:0}
.ev li p + p{margin-top:4px}
.ev li code{font:400 12.5px/1.5 var(--mono);color:var(--ink)}
.ev li em{font-style:normal;font:500 11px/1 var(--mono);letter-spacing:.12em;color:var(--ink-3);margin-right:8px}
.ev li .now em{color:var(--accent)}
@media (max-width:520px){.ev li{grid-template-columns:20px 1fr;gap:9px}}

/* ── project overlay + per-element entrances ───────────────────── */
.ov{position:fixed;inset:0;z-index:60;background:var(--paper);overflow-y:auto;padding:clamp(24px,5vw,64px);
  display:none;clip-path:inset(0 0 100% 0)}
.ov.open{display:block;animation:sheet 320ms cubic-bezier(.2,.8,.2,1) forwards}
@keyframes sheet{to{clip-path:inset(0 0 0 0)}}
/* z-index, or later positioned content (rail pins, flags) paints over the sticky button */
.ov .close{position:sticky;top:0;z-index:5;float:right;font:500 11px/1 var(--mono);letter-spacing:.14em;
  border:1px solid var(--line);padding:10px 14px;background:var(--paper);transition:background var(--t),color var(--t)}
.ov .close:hover{background:var(--ink);color:var(--paper)}
.ov .wrap{max-width:1080px;margin:0 auto}
.ov h2{font-size:clamp(40px,7vw,104px);letter-spacing:-.05em;margin:36px 0 18px}
.ov .tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:26px}
.ov .tags span{font:500 11px/1 var(--mono);letter-spacing:.1em;border:1px solid var(--line);padding:7px 9px;text-transform:uppercase}
.plate{aspect-ratio:16/7;background:
  repeating-linear-gradient(135deg,var(--paper-2) 0 12px,var(--paper) 12px 24px);
  border:1px solid var(--line);display:grid;place-items:center;margin:8px 0 30px}
.plate span{font:400 12px/1 var(--mono);color:var(--ink-3);letter-spacing:.1em}
.plate.shot{background:var(--paper-2);overflow:hidden;padding:0}
.plate.shot img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.ov figure{margin:8px 0 30px}
.ov figure .plate{margin:0}
.ov figcaption{font:400 11px/1.6 var(--mono);color:var(--ink-3);letter-spacing:.08em;margin-top:10px}
.hl{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:8px;margin-top:30px}
/* the buyer's translation of the case: accent rule, prose size, no box */
.ov .forclient{max-width:62ch;margin-top:26px;padding-left:16px;border-left:2px solid var(--accent);
  font-size:16px;color:var(--ink)}

/* deep case: the long version of section 03, only rendered for the flagship */
.ov .deep{margin-top:56px;border-top:1px solid var(--line);padding-top:34px}
.ov .deep h3{font-size:clamp(26px,3.6vw,42px);margin:10px 0 12px}
.ov .deep .cols{margin-top:20px}
.ov .deep + .deep{margin-top:48px}
.ov .note{font:400 13px/1.7 var(--mono);color:var(--ink-3);margin-top:16px}
.ov .oldlink{display:inline-flex;align-items:center;gap:10px;margin-top:18px;border:1px solid var(--ink);
  padding:13px 16px;border-radius:var(--r);font:500 12px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  transition:background var(--t),color var(--t)}
.ov .oldlink:hover{background:var(--ink);color:var(--paper)}

/* section rail: sits in the gutter left of the 1080px column, so it only exists
   where there is real room for it — under 1440px the sections still scroll fine */
.ovnav{display:none}
@media (min-width:1440px){
  /* width is whatever the gutter actually has left over, so a long label
     truncates instead of running under the column */
  .ov.open .ovnav{position:fixed;top:50%;transform:translateY(-50%);right:calc(50vw + 556px);
    width:max-content;max-width:calc(50vw - 580px);
    display:flex;flex-direction:column;align-items:stretch;border-right:1px solid var(--line)}
  .ovnav button{position:relative;padding:8px 14px 8px 0;text-align:right;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    font:400 11px/1.4 var(--mono);letter-spacing:.06em;color:var(--ink-3);
    transition:color var(--t),transform var(--t)}
  .ovnav button[aria-current=true]{color:var(--ink)}
  .ovnav button[aria-current=true]::after{content:'';position:absolute;right:-1px;top:0;bottom:0;width:1px;background:var(--accent)}
}
/* same cursor pull as the sidebar: accent field under the pointer, nearest entries lean */
@media (hover:hover) and (min-width:1440px){
  /* the field box is 200px larger than the rail on every side — the 180px gradient
     has finished fading long before it reaches an edge, so no box corner shows */
  .ovnav::after{content:"";position:absolute;inset:-200px;pointer-events:none;opacity:0;transition:opacity 240ms ease-out;
    background:radial-gradient(180px circle at calc(var(--mx,50%) + 200px) calc(var(--my,50%) + 200px),
      color-mix(in srgb,var(--accent) 8%,transparent),transparent 72%)}
  .ovnav:hover::after{opacity:1}
  .ovnav button{transform:translateX(calc(var(--pull,0) * -4px))}
  .ovnav button:not([aria-current=true]){color:color-mix(in srgb,var(--ink) calc(var(--pull,0) * 100%),var(--ink-3))}
}

/* entrance keyframes — one per element kind, not one for the page */
[data-anim]{--d:0ms}
.ov.open [data-anim=img]{animation:aImg 460ms cubic-bezier(.2,.8,.2,1) var(--d) both}
.ov.open [data-anim=head] i,.ov [data-anim=head] i{font-style:normal}
.ov.open [data-anim=head] i{display:inline-block;animation:aWord 380ms cubic-bezier(.2,.85,.25,1) var(--d) both}
.ov.open [data-anim=tag]{animation:aTag 260ms cubic-bezier(.3,1.5,.5,1) var(--d) both}
.ov.open [data-anim=body],.ov.open [data-anim=cell]{animation:aFade 240ms ease-out var(--d) both}
.ov.open [data-anim=metric]{animation:aMetric 300ms ease-out var(--d) both}
@keyframes aImg{from{clip-path:inset(0 0 100% 0);transform:scale(1.06)}to{clip-path:inset(0 0 0 0);transform:none}}
@keyframes aWord{from{opacity:0;transform:translateY(.5em) rotate(1.5deg)}to{opacity:1;transform:none}}
@keyframes aTag{from{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}
@keyframes aFade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes aMetric{from{opacity:0;filter:blur(6px)}to{opacity:1;filter:none}}

/* ── idle log ──────────────────────────────────────────────────── */
.log{position:fixed;right:18px;bottom:18px;width:min(360px,calc(100vw - 36px));z-index:50;
  border:1px solid var(--line);background:var(--paper);transform:translateY(calc(100% - 34px));transition:transform 220ms cubic-bezier(.2,.7,.3,1),opacity var(--t)}
.log.up{transform:translateY(0)}
/* out of the way while the enquiry form is on screen */
.log.away{opacity:0;pointer-events:none;transform:translateY(100%)}
.log-toggle{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line);
  font:500 11px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;cursor:pointer;width:100%;text-align:left}
.log-toggle .c{margin-left:auto;color:var(--ink-3)}
/* collapsed panel is parked off-screen — keep it out of the tab order too */
.log:not(.up) ol,.log:not(.up) form{visibility:hidden}
.log ol{list-style:none;height:168px;overflow-y:auto;padding:10px 12px;font:400 12px/1.65 var(--mono);color:var(--ink-2)}
.log li{opacity:0;animation:aFade 200ms ease-out both}
.log li b{color:var(--ink);font-weight:400}
.log li.me b{color:var(--accent)}
.log form{display:flex;border-top:1px solid var(--line)}
.log input{flex:1;border:0;background:none;padding:11px 12px;font:400 12px/1 var(--mono);color:var(--ink)}
.log input::placeholder{color:var(--ink-3)}
.log button[type=submit]{padding:0 13px;font:500 11px/1 var(--mono);letter-spacing:.1em;border-left:1px solid var(--line)}
.log button[type=submit]:hover{background:var(--ink);color:var(--paper)}
.log .local{font:400 10.5px/1.5 var(--mono);color:var(--ink-3);padding:8px 12px;border-top:1px solid var(--line)}
.log:not(.up) .local{visibility:hidden}

footer{padding:52px 0;display:flex;flex-wrap:wrap;gap:14px 32px;font:400 12px/1.6 var(--mono);color:var(--ink-3)}

/* ── mobile ─────────────────────────────────────────────────────────
   The tab-bar shell is in site.css; below is only what this page adds. */
@media (max-width:880px){
  main{margin-left:0;padding-bottom:calc(72px + env(safe-area-inset-bottom))}
  .bar.open{animation:sheetUp 200ms cubic-bezier(.2,.7,.3,1)}
  .bar.open ~ .log{opacity:0;pointer-events:none}   /* log gets out of the drawer's way */
  @keyframes sheetUp{from{transform:translateY(24px)}to{transform:none}}
  /* collapsed log is a pill above the tab bar — no translate, or its opaque box
     parks on top of the nav and eats the taps meant for it */
  .log{right:14px;bottom:calc(80px + env(safe-area-inset-bottom));transform:none;
    width:auto;overflow:hidden;border-radius:999px}
  /* display:none, not just visibility — hidden children still set the shrink-to-fit width */
  .log:not(.up) ol,.log:not(.up) form,.log:not(.up) .local{display:none}
  .log:not(.up) .log-toggle{border-bottom:0;padding:9px 14px}
  .log.up{width:min(360px,calc(100vw - 28px));border-radius:var(--r)}
  .w{grid-template-columns:1fr auto;gap:6px 14px}
  .w .m:nth-of-type(2){display:none}
}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

/* the footer line that checks itself */
#weigh{cursor:pointer;text-decoration:underline;text-decoration-style:dotted;
  text-decoration-color:var(--ink-3);text-underline-offset:4px;transition:color var(--t)}
#weigh:hover{color:var(--accent)}
/* the picture-word in the headline reacts to a pointer, because it looks like it should */
.wd.pic{transition:transform 300ms cubic-bezier(.34,1.56,.64,1)}
.wd.pic:hover{transform:translateY(.06em) rotate(-4deg) scale(1.12)}
/* sys replies in the log read as machine, not as another visitor */
.log li b{color:var(--ink);font-weight:400}


/* ════════════════════════════════════════════════════════════════════
   Ambient interaction, page-level
   Everything below reads custom properties written by page.js. Nothing
   below is required to understand the page — it is the layer that makes
   it feel handled rather than laid out.
   ════════════════════════════════════════════════════════════════════ */

/* ── the price grid gets the cursor field ────────────────────────────
   Same --mx/--my contract as the sidebar and the work list, so the whole
   site has one idea of what a pointer does. The hovered tier lifts, the
   others step back — a choice should feel like a choice. */
@media (hover:hover) and (pointer:fine){
  .tiers{position:relative}
  .tiers::after{content:"";position:absolute;inset:-200px;pointer-events:none;z-index:0;
    opacity:0;transition:opacity 260ms ease-out;
    background:radial-gradient(300px circle at calc(var(--mx,50%) + 200px) calc(var(--my,50%) + 200px),
      color-mix(in srgb,var(--accent) 10%,transparent),transparent 70%)}
  .tiers:hover::after{opacity:1}
  .tier{position:relative;z-index:1;
    transition:transform 320ms cubic-bezier(.2,.8,.2,1),box-shadow var(--t),opacity var(--t)}
  /* :has, not :hover on the group — otherwise the pointer sitting in the gap
     between two cards dims all three and the section just looks switched off */
  .tiers:has(.tier:hover) .tier{opacity:.7}
  .tiers .tier:hover{opacity:1;transform:translateY(-4px);box-shadow:0 0 0 1px var(--ink)}
  .tiers .tier.feature:hover{box-shadow:0 0 0 2px var(--accent)}
  /* the tick marks draw themselves in, one after the next, when the card is under the pointer */
  .tier .inc li::before{transition:transform 300ms cubic-bezier(.34,1.56,.64,1),opacity 200ms ease;
    transition-delay:calc(var(--i,0) * 40ms);opacity:.45;transform:rotate(-45deg) scale(.8)}
  .tier:hover .inc li::before{opacity:1;transform:rotate(-45deg) scale(1)}
}

/* ── hero facts arrive ───────────────────────────────────────────────
   Same blur-out-of-nothing entrance the case metrics use, so a number
   behaves like a number everywhere on the site. */
.facts li{opacity:0}
.facts.in li{animation:aMetric 420ms ease-out var(--d,0ms) both}

/* ── process: a rail that fills as you read ──────────────────────────
   The line is the spine of the section; the accent half of it is how far
   you have got. Steps ahead of you sit back, the one you are in is ink. */
.steps{position:relative}
.steps::before,.steps::after{content:"";position:absolute;left:26px;top:0;bottom:0;width:1px}
.steps::before{background:var(--line)}
.steps::after{background:var(--accent);transform:scaleY(var(--fill,0));transform-origin:top;
  transition:transform 420ms cubic-bezier(.2,.8,.2,1)}
.steps li{transition:opacity 320ms ease-out}
.steps li:not(.on){opacity:.45}
.steps .num{position:relative;z-index:1;color:var(--ink-3);transition:color var(--t)}
.steps li.on .num{color:var(--accent)}
/* the dot on the rail, drawn on the number's own line */
.steps .num::before{content:"";position:absolute;left:-14px;top:.42em;width:7px;height:7px;border-radius:50%;
  background:var(--paper);box-shadow:0 0 0 1px var(--line);transition:background var(--t),box-shadow var(--t)}
.steps li.on .num::before{background:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 20%,transparent)}
@media (max-width:560px){.steps::before,.steps::after{display:none}.steps .num::before{display:none}}

/* ── work covers: the render sits a little behind the frame ──────────
   --px/--py are the pointer's offset from the card centre, -1…1. The
   cover drifts against it, so the frame reads as a window rather than a
   picture glued to the page. Grid mode only — the list has no cover. */
@media (hover:hover) and (pointer:fine){
  .works.grid .w .cov img{
    transform:translate(calc(var(--px,0) * -7px),calc(var(--py,0) * -7px))}
  .works.grid .w:hover .cov img{
    transform:translate(calc(var(--px,0) * -7px),calc(var(--py,0) * -7px)) scale(1.06)}
}

/* ── the form answers back ───────────────────────────────────────────
   A field that has something in it says so, and the count above the
   button turns the form from a wall into three questions. */
.enq label.filled{color:var(--accent)}
.enq label.filled input,.enq label.filled textarea{border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
.enq .prog{display:flex;align-items:center;gap:10px;font:400 11px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-3);margin-top:4px}
.enq .prog i{flex:1;height:2px;background:var(--line);border-radius:2px;overflow:hidden}
.enq .prog i::after{content:"";display:block;height:100%;width:calc(var(--p,0) * 100%);background:var(--accent);
  transition:width 320ms cubic-bezier(.2,.8,.2,1)}

/* ── prose links: the underline draws itself ─────────────────────────── */
.lede a,.tier a{text-decoration:none;background-image:linear-gradient(var(--accent),var(--accent));
  background-size:0% 1px;background-repeat:no-repeat;background-position:0 100%;
  box-shadow:inset 0 -1px 0 var(--line);
  transition:background-size 280ms cubic-bezier(.2,.8,.2,1),color var(--t)}
.lede a:hover,.tier a:hover{background-size:100% 1px;color:var(--accent)}
