/* ══ WBJ Solutions — single committed light theme, no OS dark mode ══ */
:root{
  --white:#FFFFFF;
  --off:#FAF7F3;
  --ink:#111111;
  /* headings are near-black carrying a trace of the brand hue, so
     they read as black but feel warm rather than printer-default */
  --heading:#241A12;
  --charcoal:#252524;
  --grey:#707070;
  --grey-2:#9A9590;
  --line:#E7E3DD;
  /* ONE brand colour, pulled from the W. Three candidates, switchable
     on the swatch control bottom-right. Each defines four roles:
       --brand      fills, bars, panel rules
       --hl         a tint of the brand that passes AA with black over it
       --swipe      the highlighter. The brand itself, drawn at 45%
       --brand-deep links and white-text buttons (must pass AA on white)
       --brand-lift the same hue lifted, for the charcoal footer          */
  --brand:#D97D39; --hl:#D97D39; --brand-deep:#9E5419; --brand-lift:#E9A470;
  --accent:var(--brand-deep);
  --accent-soft:#FAEEE3;
  /* Errors do not follow the brand, and deliberately so. The old error
     colour was a burnt orange six degrees off the brand hue, which meant a
     form that had gone wrong and a form that had gone right were the same
     colour. This is a red, far enough round the wheel to be unmistakable
     and dark enough to pass AA on white at label size. */
  --bad:#B4342A;
  --bad-soft:#FCEDEA;

  --f:"Outfit",system-ui,-apple-system,"Segoe UI",sans-serif;

  --measure:1125px;
  --gutter:clamp(22px,10vw,150px);

  /* The scale had h1 and h2 almost the same size, and the split hero then
     capped h1 below h2 outright: the headline on the home page rendered at
     56px while every section heading on it rendered at 60px. A page whose
     most important line is its fourth largest thing has no focal point, and
     no amount of spacing or typeface fixes that. h1 is now roughly twice h2,
     and the steps below it are even. */
  --h1:clamp(2.7rem,6.2vw,5.4rem);
  --h2:clamp(1.8rem,3vw,2.7rem);
  --h3:clamp(1.3rem,1.9vw,1.7rem);
  --h4:clamp(1.1rem,1.5vw,1.3rem);
}
*,*::before,*::after{box-sizing:border-box}
html{color-scheme:light}
body{
  margin:0; background:var(--white); color:var(--ink);
  font-family:var(--f); font-size:20px; font-weight:400; line-height:1.5;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
/* height:auto matters: the width/height attributes on each img act as
   presentational hints, so without this any image given a CSS width
   gets squashed to its attribute height. */
img{max-width:100%; height:auto; display:block}
a{color:inherit}
:focus-visible{outline:3px solid var(--accent); outline-offset:3px}
@media (prefers-reduced-motion: reduce){*{animation:none !important; transition:none !important}}

h1,h2,h3,h4{margin:0; color:var(--heading); font-weight:800; line-height:1.1; letter-spacing:-.02em; text-wrap:balance}
.sec--dark h1,.sec--dark h2,.sec--dark h3,.sec--dark h4{color:var(--white)}
h1{font-size:var(--h1); letter-spacing:-.034em; line-height:1.04}
h2{font-size:var(--h2); letter-spacing:-.028em}
h3{font-size:var(--h3)}
h4{font-size:var(--h4); font-weight:700}
p{margin:0}

.wrap{width:100%; max-width:calc(var(--measure) + var(--gutter)*2); margin-inline:auto; padding-inline:var(--gutter)}
.sec{padding-block:clamp(64px,9vw,130px); position:relative}
.sec--off{background:var(--off)}
.sec--dark{background:var(--charcoal); color:var(--white)}
.sec--dark .body{color:#C9C5C0}
.body{color:var(--grey); font-weight:400; max-width:32em}
.body p + p{margin-top:1em}
/* Links written as [label](url) inside a prose block. Underlined rather than
   colour alone, because colour on its own is not a link to somebody who
   cannot see the colour. */
.body a, .lede a{
  color:var(--brand-deep); text-decoration:underline;
  text-underline-offset:.16em; text-decoration-thickness:1.5px;
}
.body a:hover, .lede a:hover{text-decoration-thickness:2.5px}
.sec--dark .body a, .sec--dark .lede a{color:var(--brand-lift)}
.lede{font-size:clamp(1.05rem,1.5vw,1.375rem); color:var(--grey); line-height:1.5; max-width:28em}
.sec--dark .lede{color:#C9C5C0}

/* ── section edge ─────────────────────────────────────────
   Switchable on the Edge control, bottom-left. Default is
   none: sections simply meet on a colour change.            */
.zag{display:block; width:100%; height:clamp(26px,3.4vw,50px); line-height:0; margin-bottom:-1px}
.zag svg{display:block; width:100%; height:calc(100% + 1px)}
:root[data-edge="none"] .zag{height:0; overflow:hidden}

/* ── highlighter ──────────────────────────────────────────
   A translucent marker band drawn as a background on the text
   itself, so it wraps correctly line by line and the words sit
   over it. Tall enough to read as a highlight, low enough never
   to look like a strike-through.

   45% rather than 32%. At the lower figure it was pale enough to
   read as a wash behind the words rather than a mark drawn over
   them, which is a matter of strength and was for a while mistaken
   for a matter of hue. The colour is the brand's own: a highlighter
   in any other colour reads as a second system rather than as the
   brand marking its own words.                                  */
/* The highlighter has been taken off. The class stays on the headings
   because the editor, the swipe script and the page templates all expect
   it, and leaving it inert here is one rule to put back rather than a
   dozen templates to edit if it is ever wanted again. */
.hl{display:inline}

/* ── masthead ─────────────────────────────────────────── */
/* Floating header: fixed, no bar, no border. It hides on scroll
   down and returns compact on scroll up, and flips to white
   whenever it is sitting over a dark section.                  */
.mast{
  position:fixed; top:0; left:0; right:0; z-index:50; pointer-events:none;
  color:var(--heading); transition:transform .3s ease;
}
.mast--light{color:var(--white)}

/* The contact line, inside the fixed header so it cannot scroll away
   underneath it. It is at rest only: once the page moves, the header
   compacts and the bar goes with it, because a permanent second bar on a
   phone is two thirds of the screen gone before anything is read. */
.topbar{
  background:var(--charcoal); color:#CFC9C2; pointer-events:auto;
  font-size:14.5px; line-height:1.4; letter-spacing:.005em;
  overflow:hidden; max-height:60px; transition:max-height .25s ease, opacity .2s ease;
}
.topbar__in{display:flex; flex-wrap:wrap; gap:4px 26px; align-items:center;
  justify-content:space-between; padding-block:10px}
.topbar p{margin:0}
.topbar__sep{opacity:.45; padding-inline:4px}
.topbar a{color:#fff; text-decoration:none; border-bottom:1px solid transparent; transition:border-color .15s}
.topbar a:hover{border-bottom-color:var(--brand-lift)}
.mast--compact .topbar{max-height:0; opacity:0}
@media (max-width:620px){
  /* The number is what somebody on a phone is there for. The hours and the
     address are a tap away on the contact page. */
  .topbar__in{justify-content:center; padding-block:8px}
  .topbar__where{display:none}
}
.mast__in{display:flex; align-items:flex-start; justify-content:space-between; gap:20px; padding-block:34px; transition:padding .25s ease}
.mast--compact .mast__in{padding-block:14px}
/* Compact: the header leaves the measure and spreads to the page edges, so
   the logo and the nav sit in the margins rather than on top of the column
   of text. It cannot clear it entirely at every width, since the nav is
   wider than the margin is, but it takes the worst of the overlap away and
   on a wide screen it clears completely. */
.mast--compact .mast__in{
  max-width:none;
  padding-inline:clamp(18px,2.4vw,44px);
}
.brand{display:block; flex:0 0 auto; text-decoration:none; pointer-events:auto; transition:padding .25s ease, background .25s ease, box-shadow .25s ease}
.brand img{height:54px; width:auto; transition:height .25s ease}
.b-light,.b-mark{display:none}
.mast--light .b-dark{display:none}
.mast--light .b-light{display:block}
.navbar{display:flex; align-items:center; gap:30px; padding-top:10px; pointer-events:auto; transition:gap .25s ease}

/* Scrolled: each item gets its own white chip, so everything stays
   readable over any background without a full-width bar. Dark text
   returns, because the chips are always white.                     */
.mast--compact{color:var(--heading)}
.mast--compact .b-dark{display:block}
.mast--compact .b-light{display:none}
.mast--compact .brand,
.mast--compact .navbar a,
.mast--compact .hamb{
  /* background-color, not the shorthand: the nav links carry a background-image
     highlight and the shorthand would wipe it out */
  background-color:var(--white); border-radius:999px;
  box-shadow:0 2px 5px rgba(17,17,17,.06), 0 10px 26px -14px rgba(17,17,17,.5);
}
.mast--compact .brand{padding:9px 20px}
.mast--compact .brand img{height:34px}
.mast--compact .navbar{gap:9px; padding-top:0}
.mast--compact .navbar a{height:52px; display:flex; align-items:center; padding:0 22px; font-size:18px; background-position:22px 66%; background-size:0 .6em}
.mast--compact .navbar a:hover,
.mast--compact .navbar a[aria-current="page"]{background-size:calc(100% - 44px) .6em}
.mast--compact .hamb{width:52px; height:52px; padding:0 15px; justify-content:center; gap:5px}
.mast--compact .hamb i{height:2.5px}
@media (max-width:560px){
  .brand img{height:38px}
  .mast--compact .brand{padding:7px 14px}
  .mast--compact .brand img{height:28px}
  .mast--compact .hamb{width:44px; height:44px}
  .mast__in{padding-block:22px}
}
.navbar a{
  font-size:20px; font-weight:500; text-decoration:none; color:inherit; padding-inline:.12em;
  /* A two pixel underline in the brand colour, drawn in from the left on
     hover and left in place on the page you are on. It used to be the
     highlighter band; with that gone from the headings, a marker here would
     be the only one on the site. */
  background-image:linear-gradient(var(--brand), var(--brand));
  background-repeat:no-repeat; background-position:0 100%; background-size:0% 2px;
  padding-bottom:4px;
  transition:background-size .25s ease;
}
.navbar a:hover,.navbar a[aria-current="page"]{background-size:100% 2px}
.mast--light .navbar a{background-image:linear-gradient(currentColor, currentColor)}
/* Four items plus the logo stop fitting a little under 900px, measured
   rather than guessed. Below that the links go and the menu button becomes
   the only way through; above it the button goes instead, because with all
   four links on the bar the overlay holds nothing the page does not already
   offer: home is the logo, and the town, phone and email are in the footer.
   A button that opens a list of things already on screen is furniture. */
@media (max-width:900px){.navbar a:not(.always){display:none}}
@media (min-width:901px){.navbar .hamb{display:none}}   /* .navbar .hamb, not .hamb: the base rule sets display:flex further
      down the file and would otherwise win on source order alone. */

/* Scrolled, the four links become the button, at every width.
   The reason the header spreads to the page edges once you scroll is to get
   it out of the way of the text. Four chips are about 700px of furniture
   floating over whatever is underneath, which undoes most of that; one chip
   is 52px and sits in the margin. The links are still all there unscrolled,
   so somebody arriving still sees the whole navigation without touching
   anything, and by the time it collapses they are reading rather than
   looking for the way around. The overlay it opens says more than the bare
   words did anyway: how many services, the phone number.
   Specificity, not source order, is what beats the two rules above. */
.mast--compact .navbar a:not(.always){display:none}
.mast--compact .navbar .hamb{display:flex}
.hamb{
  width:34px; height:26px; border:0; background:none; padding:0; cursor:pointer;
  display:flex; flex-direction:column; justify-content:space-between; align-items:flex-end;
  transition:width .25s ease, height .25s ease, background .25s ease, box-shadow .25s ease;
}
.hamb{color:inherit}
.hamb i{display:block; height:3px; background:currentColor; border-radius:2px; width:100%}

/* Four looks for the same button, chosen in Settings. Each keeps the 34px
   hit area, so the target never shrinks below what a thumb can find. */

/* three bars, stepped: the familiar hamburger */
.hamb--bars i:nth-child(2){width:70%}
.hamb--bars i:nth-child(3){width:88%}

/* two bars: quieter, and the default */
.hamb--duo{height:16px; justify-content:space-between}
.hamb--duo i{height:3px}
.hamb--duo i:nth-child(2){width:64%}

/* four dots, echoing the rounded shapes in the mark */
.hamb--dots{height:26px; display:grid; grid-template-columns:repeat(2,9px); gap:8px;
  justify-content:end; align-content:center; width:auto}
.hamb--dots i{width:9px; height:9px; border-radius:999px}

/* the word, with a rule under it */
.hamb--word{width:auto; height:auto; flex-direction:column; align-items:stretch; gap:5px}
.hamb__word{font:700 15px/1 var(--f); letter-spacing:.12em; text-transform:uppercase; color:currentColor}
.hamb--word i{height:2px}
.hamb--word i:nth-child(3){width:60%; align-self:flex-end}

/* full-screen menu overlay */
.overlay{position:fixed; inset:0; z-index:90; background:var(--white); display:flex; flex-direction:column; overflow:hidden}
.overlay[hidden]{display:none}
/* the W mark, oversized and muted: the real gradient, just
   turned right down so it reads as a tint rather than a logo */
.overlay::before{
  content:""; position:absolute; z-index:0; pointer-events:none;
  right:-10%; top:50%; translate:0 -50%;
  width:min(80vw,920px); aspect-ratio:2531/1459;
  /* Root-relative, not bare: a url() resolves against the stylesheet, so
     url(mark.webp) would look for it in /assets/css/ and silently find
     nothing. A missing background image throws no error anywhere. */
  background:url(/assets/img/mark.webp) no-repeat center/contain;
  opacity:.16; filter:saturate(.75);
}
.overlay__top,.overlay nav,.overlay__foot{position:relative; z-index:1}
.overlay__top{display:flex; justify-content:space-between; align-items:flex-start; padding:34px var(--gutter)}
.overlay__top img{height:54px}
.close{border:0; background:none; font-size:42px; line-height:1; cursor:pointer; color:var(--ink); font-family:var(--f); font-weight:300}
.overlay nav{flex:1; display:flex; flex-direction:column; justify-content:center; padding:0 var(--gutter) 6vh; gap:0}
.overlay nav a{
  font-size:clamp(2rem,5.6vw,3.6rem); font-weight:800; letter-spacing:-.03em; line-height:1.05;
  text-decoration:none; color:var(--heading);
  display:flex; align-items:baseline; justify-content:space-between; gap:24px;
  padding-block:clamp(10px,1.9vh,20px); border-bottom:1px solid var(--line);
}
.overlay nav a:first-child{border-top:1px solid var(--line)}
.overlay nav em{
  font-style:normal; font-size:17px; font-weight:500; letter-spacing:0; color:var(--grey-2);
  flex:0 0 auto; transition:color .2s;
}
.overlay nav a:hover em{color:var(--brand-deep)}
@media (max-width:620px){.overlay nav em{display:none}}
.overlay nav a .u{position:relative; display:inline-block}
.overlay nav a .u::before{content:""; position:absolute; left:-.05em; right:calc(100% + .05em); bottom:-.06em; height:3px; background:var(--brand); transition:right .35s ease}
.overlay nav a:hover .u::before{right:-.05em}
.overlay nav a span{position:relative}
.overlay__foot{padding:0 var(--gutter) 34px; color:var(--grey); font-size:17px}

/* ── hero ─────────────────────────────────────────────── */
.hero{padding-top:clamp(176px,18vw,268px); padding-bottom:clamp(70px,10vw,150px)}
.hero--split{display:grid; grid-template-columns:minmax(0,1.22fr) minmax(0,.78fr); gap:clamp(24px,5vw,64px); align-items:center}
@media (max-width:900px){
  .hero--split{grid-template-columns:1fr; gap:34px}
  .hero--split .hero__art{max-width:240px}
}
/* Still a little smaller than a full-width h1, because it shares the row
   with the illustration, but nowhere near small enough to fall under a
   section heading again. */
.hero--split h1{font-size:clamp(2.5rem,5.4vw,4.6rem)}
.portrait img{max-width:none; width:100%}
.portrait.split__art img{max-width:390px}
@media (max-width:900px){.hero--split .portrait{max-width:340px}}

/* handwritten aside, pointing up at the portrait */
.hero__art .scribble{
  display:flex; align-items:flex-end; justify-content:flex-end; gap:10px;
  margin-top:12px; color:var(--brand-deep);
  text-transform:none; letter-spacing:0; font-weight:400;
}
.scribble span{
  font-family:"Caveat",ui-rounded,var(--f); font-size:clamp(1.3rem,1.8vw,1.7rem); font-weight:600;
  line-height:1.1; text-align:right; max-width:16ch; padding-bottom:2px;
  text-transform:none; letter-spacing:0;          /* the figcaption rules above set both */
}
.scribble svg{
  width:66px; height:54px; flex:0 0 auto; align-self:flex-end; margin-bottom:2px;
  fill:none; stroke:currentColor; stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round;
  animation:nudge 3.4s ease-in-out infinite;
}
@keyframes nudge{
  0%, 55%, 100%{translate:0 0}
  78%{translate:-4px -5px}
}
@media (max-width:900px){.scribble{justify-content:flex-start}.scribble span{text-align:left}}
.hero__art img{width:100%; max-width:340px; margin-inline:auto}
.hero__art figcaption{text-align:center; margin-top:14px; font-size:15px; font-weight:700; letter-spacing:.11em; text-transform:uppercase; color:var(--grey-2)}

.hello{font-size:clamp(1.05rem,1.7vw,1.35rem); font-weight:600; color:var(--brand-deep); margin-bottom:16px}
.trust{margin-top:34px; font-size:16px; color:var(--grey-2); line-height:1.5; max-width:46ch}
/* Three equal choices laid out as three equal columns. As pills on a flex
   row they wrapped two and then one at every normal desktop width, which
   reads as an accident rather than as a set, and a bare label never said
   who each route was for. */
.routes{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; margin-top:34px}
@media (max-width:900px){.routes{grid-template-columns:1fr}}
.route b{display:block; font-size:17px; font-weight:700; color:var(--heading); letter-spacing:-.012em}
.route span{display:block; font-size:15.5px; line-height:1.45; color:var(--grey); margin-top:4px}
.route i{display:flex; align-items:center; gap:7px; font-style:normal; font-size:15px;
  font-weight:600; color:var(--brand-deep); margin-top:auto; padding-top:10px}
.route i svg{width:15px; height:15px}
.route{
  display:flex; flex-direction:column; text-decoration:none;
  border:1px solid var(--line); border-radius:9px; background:var(--white);
  padding:17px 19px 15px;
  transition:border-color .18s, transform .18s, box-shadow .18s;
}
.route:hover{border-color:var(--brand); transform:translateY(-2px);
  box-shadow:0 2px 6px rgba(17,17,17,.04), 0 18px 30px -24px rgba(17,17,17,.5)}
.route svg{fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round; opacity:.65}
.route:hover svg{opacity:1}
/* Long enough to say something, short enough to stay a headline. */
.hero h1{max-width:15ch}
.hero .l2{display:block; margin-top:.14em}
.hero__sub{margin-top:38px; max-width:30em; font-size:clamp(1.05rem,1.5vw,1.375rem); color:var(--grey)}
.hero__cta{margin-top:38px; display:flex; flex-wrap:wrap; gap:14px}

/* ── footnote marker and its note ─────────────────────────
   The marker is a link to the note, so with the script blocked it jumps to
   a note that is simply there. With the script running the note starts
   collapsed and the marker toggles it.                                    */
.fnmark{
  display:inline-block; vertical-align:super; line-height:1;
  font-size:.62em; font-weight:700; margin-left:.05em; padding:0 .18em;
  color:var(--brand-deep); text-decoration:none; border-radius:3px;
}
.fnmark:hover{background:var(--accent-soft)}
.fnote{
  display:flex; gap:.45em; margin-top:14px; max-width:48ch;
  font-size:15px; line-height:1.55; color:var(--grey-2);
}
.fnote[hidden]{display:none}
.fnote__star{flex:0 0 auto; color:var(--brand-deep); font-weight:700}
.fnmark[aria-expanded="true"]{background:var(--brand-deep); color:var(--white)}

/* The same note, hanging off the asterisk. The script moves it here; the
   block above is what it looks like with the script blocked. Charcoal
   rather than a pale card, because at this size a bubble on a white page
   needs to be clearly a layer over the page and not another paragraph
   of it. */
.fnote--pop{
  position:absolute; z-index:60; display:block; margin:0;
  max-width:min(330px, calc(100vw - 32px));
  background:var(--charcoal); color:rgba(255,255,255,.88);
  border-radius:10px; padding:14px 17px;
  font-size:15.5px; line-height:1.55;
  box-shadow:0 20px 44px -18px rgba(17,17,17,.6), 0 2px 6px rgba(17,17,17,.12);
  opacity:0; transform:translateY(-5px) scale(.97); transform-origin:var(--arrow,20px) 0;
  transition:opacity .16s ease, transform .16s cubic-bezier(.2,.8,.3,1);
}
.fnote--pop.is-open{opacity:1; transform:none}
.fnote--pop .fnote__star{display:none}
.fnote--pop a{color:var(--brand-lift)}
/* The pointer. A rotated square rather than a border triangle, so it takes
   the bubble's own radius and reads as part of it. */
.fnote--pop::before{
  content:""; position:absolute; top:-6px; left:var(--arrow,20px);
  width:14px; height:14px; margin-left:-7px;
  background:var(--charcoal); border-radius:3px; transform:rotate(45deg);
}
@media (prefers-reduced-motion:reduce){
  .fnote--pop{transition:none; opacity:1; transform:none}
}
/* The line under the closing pair of buttons, saying which is which. */
.ctahint{margin-top:20px; font-size:16px; color:var(--grey-2); line-height:1.55; max-width:56ch}

/* ── closing call to action ───────────────────────────────
   The one block on the site that is centred, and deliberately: everything
   else is a column of text with something beside it, while this is a short
   closing statement with two buttons under it and nothing to its right.
   Left-aligned it read as top-heavy, a full-width headline over a narrow
   huddle in the corner. Centred it reads as the end of the page.          */
/* Left aligned on the site's own measure. It used to be the only centred
   block anywhere and the only one with a narrower column, which read as a
   mistake rather than a change of pace. */
.cta h2{max-width:16ch}
.cta .lede{max-width:48ch}
.cta .ctahint{color:rgba(255,255,255,.45)}
.cta .eyebrow{color:var(--brand-lift); margin-bottom:16px}

/* ── buttons ──────────────────────────────────────────── */
.btn{
  display:inline-flex; align-items:center; gap:11px; padding:16px 34px;
  font-family:var(--f); font-size:19px; font-weight:600; text-decoration:none;
  border:2px solid var(--ink); border-radius:999px; background:var(--ink); color:var(--white);
  cursor:pointer; transition:background .2s, color .2s, border-color .2s;
}
.btn:hover{background:var(--accent); border-color:var(--accent)}
.btn--line{background:none; color:var(--ink)}
.btn--line:hover{background:var(--ink); color:var(--white); border-color:var(--ink)}
.sec--dark .btn{background:var(--white); color:var(--ink); border-color:var(--white)}
.sec--dark .btn:hover{background:var(--accent); border-color:var(--accent); color:var(--white)}
.sec--dark .btn--line{background:none; color:var(--white)}
.btn svg{width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round}

.tlink{display:inline-flex; align-items:center; gap:10px; font-weight:600; text-decoration:none; font-size:19px}
.tlink svg{width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round; transition:transform .2s}
.tlink:hover{color:var(--accent)}
.tlink:hover svg{transform:translateX(5px)}

/* ── two-column section ───────────────────────────────── */
.split{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.82fr); gap:clamp(30px,6vw,90px); align-items:center}
/* Two pages want a wider first column and a top-aligned second one. These
   were inline styles on the element, which is how the contact page ended up
   two columns wide on a phone: an inline grid-template-columns beats the
   media query below, so the collapse never happened and the form ran
   underneath the contact details. Modifiers, so the media query can win. */
/* The case study. Top aligned, not centred: the facts sit beside a column
   of copy that can run to three times their height, and centring floats
   them in the middle of nothing. Sticky so they stay with the reader all
   the way down the brief rather than scrolling away at the first heading.
   100px clears the compact header chips with air to spare. */
.split--text{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr); align-items:start}
.split--text > :last-child{position:sticky; top:100px}
.split--form{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); align-items:start}
@media (max-width:900px){
  .split,.split--text,.split--form{grid-template-columns:1fr; gap:40px}
  /* One column: nothing to stay beside, and a sticky block in a
     single column just sits on top of what follows it. */
  .split--text > :last-child{position:static}
}
.split__art img{width:100%; max-width:400px; margin-inline:auto}

/* Illustration with handwritten labels drawn over it. The wrapper carries
   the image's own max width so the overlay and the artwork share an edge. */
.annot{position:relative; width:100%; max-width:400px; margin-inline:auto}
.annot img{display:block}
.annot__ink{
  position:absolute; inset:0; width:100%; height:100%; pointer-events:none;
  fill:none; stroke:var(--brand-deep); stroke-width:5;
  stroke-linecap:round; stroke-linejoin:round;
}
.annot__word{
  fill:var(--brand-deep); stroke:none;
  font-family:"Caveat",ui-rounded,var(--f); font-size:52px; font-weight:600;
}

/* ── project list ─────────────────────────────────────────────────────
   Two treatments, not one. A featured project gets a band: its own
   coloured slab with the screenshot floating whole beside the words.
   Everything else gets a row: a thumbnail, a title and a line.

   The reason is that the old single treatment gave seven projects the
   same weight and the same height, which is a lot of scrolling in
   exchange for no signal about which ones are worth reading. The band
   also stops cropping the screenshots, which was the real cost of
   forcing every panel into one shape. */

.proj{display:grid; align-items:center}

/* ── featured: the band ──────────────────────────────────────────── */
/* A band is no longer a card on the page; it is a piece of the page. The
   picture runs out past the gutter to the edge of the screen, alternating
   sides, and the only thing between one band and the next is a rule. Three
   dark rounded cards in a column were three objects of identical weight,
   and because each screenshot floated at its own size inside its own box,
   the three never shared a baseline. */
.proj--band{
  grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);
  gap:clamp(22px,4vw,64px);
  background:transparent; color:inherit;
  border-radius:0; padding:0;
  padding-block:clamp(26px,3.4vw,50px);
}
.proj--band + .proj--band{border-top:1px solid var(--line)}
.proj--band.proj--flip{grid-template-columns:minmax(0,.98fr) minmax(0,1.02fr)}
.proj--band.proj--flip .proj__panel{order:2}
@media (min-width:901px){
  .proj--band .proj__panel{margin-left:calc(var(--gutter) * -1)}
  .proj--band.proj--flip .proj__panel{margin-left:0; margin-right:calc(var(--gutter) * -1)}
}
@media (max-width:900px){
  .proj--band{grid-template-columns:1fr !important; gap:20px}
  .proj--band.proj--flip .proj__panel{order:0}
}
/* The picture floats rather than filling a frame. The panel keeps a fixed
   box so the bands stay a consistent height, and the screenshot is fitted
   inside it whole: a landscape one fills the width, a portrait one sits
   centred and smaller. Filling the box instead cut the sides off anything
   wide and the whole bottom off anything tall, which on a page whose job
   is showing the work is the wrong trade. The shadow moves to the picture,
   because it is the picture that is floating, not the box. */
.proj--band .proj__panel{
  aspect-ratio:auto; display:block; border-radius:0; background:transparent;
  padding:0; overflow:visible; box-shadow:none;
}
.proj--band .proj__panel::after{display:none}
.proj--band .proj__panel--shot::before{opacity:0}
/* The picture sets the height of its own half of the band, up to a ceiling,
   rather than being poured into a box of a fixed shape. A wide screenshot
   then runs the full width of the column and a tall one is as tall as the
   ceiling allows, which is a good deal bigger than either would be inside
   one shape that had to suit both. */
/* One shape for every screenshot, cropped from the top, where the part of
   an application that identifies it lives. Three pictures at three
   different scales and crops was the most visibly unfinished thing on the
   page, and no amount of spacing around them fixed it. */
.proj--band .proj__panel--shot .proj__shot{
  position:static; inset:auto; width:100%; height:auto;
  aspect-ratio:16/10; max-height:none;
  object-fit:cover; object-position:top center;
  border-radius:6px; border:1px solid var(--line);
  filter:none;
  box-shadow:0 2px 6px rgba(17,17,17,.05), 0 26px 50px -34px rgba(17,17,17,.55);
}
.proj--band .proj__body .proj__client{color:rgba(255,255,255,.6)}
.proj--band .proj__body h3{color:var(--white)}
.proj--band .proj__body p{color:rgba(255,255,255,.76)}
.proj--band .proj__tags span{background:rgba(255,255,255,.1); border-color:rgba(255,255,255,.22); color:#fff}
.proj--band .proj__body .tlink{color:#fff}

/* The band follows the site's own light or dark choice, set under
   Settings. Light keeps the same shape and swaps the ink: the screenshot
   still floats on its own ground rather than filling a frame. The modifier
   is on the band itself, so it applies wherever a band is drawn rather than
   only inside the projects page's list. */
.proj--band--light{background:transparent; color:var(--ink); border:0}
.proj--band--light .proj__body .proj__client{color:var(--grey-2)}
.proj--band--light .proj__body h3{color:var(--heading)}
.proj--band--light .proj__body p{color:var(--grey)}
.proj--band--light .proj__tags span{background:var(--white); border-color:var(--line); color:var(--ink)}
.proj--band--light .proj__body .tlink{color:var(--brand-deep)}

/* ── the rest: index rows ────────────────────────────────────────── */
.proj--row{
  grid-template-columns:200px minmax(0,1fr);
  gap:clamp(20px,2.4vw,34px);
  padding-block:clamp(18px,2.2vw,26px);
  border-top:1px solid var(--line);
  margin-top:0;
}
/* Air between the last band and the first row, so the two groups read as
   two groups. */
.proj--band + .proj--row{margin-top:clamp(44px,6vw,78px)}
/* Same reasoning at thumbnail size. A full page grab shown whole in a
   200px slot is small, but small and complete beats a crop of its top bar. */
.proj--row .proj__panel{
  aspect-ratio:4/3; border-radius:0; padding:0;
  background:transparent; overflow:visible; box-shadow:none;
}
.proj--row .proj__panel::after{display:none}
.proj--row .proj__panel--shot::before{opacity:0}
.proj--row .proj__panel--shot .proj__shot{
  object-fit:contain; object-position:center;
  border-radius:4px;
  box-shadow:0 2px 5px rgba(17,17,17,.10), 0 14px 26px -16px rgba(17,17,17,.5);
}
.proj--row .proj__body{gap:6px}
.proj--row .proj__body h3{font-size:clamp(1.25rem,1.8vw,1.55rem)}
.proj--row .proj__tags{display:none}
.proj--row .proj__body p{font-size:16.5px; line-height:1.5}
.proj__client{font-size:14px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--grey-2)}
#worklist{border-bottom:1px solid var(--line)}

@media (max-width:860px){
  .proj--band,.proj--band.proj--flip{grid-template-columns:1fr; gap:22px}
  .proj--band.proj--flip .proj__panel{order:0}
}
@media (max-width:620px){
  /* Below this a 200px thumbnail leaves about nothing for the words. */
  .proj--row{grid-template-columns:1fr; gap:14px}
}
.proj__panel{
  aspect-ratio:16/9; border-radius:4px; background:var(--charcoal); color:var(--white);
  padding:clamp(22px,3vw,40px); display:flex; flex-direction:column; justify-content:space-between;
  position:relative; overflow:hidden; text-decoration:none;
}
/* z-index keeps the brand bar above the screenshot scrim, which is also a
   positioned layer on a screenshot panel. */
.proj__panel::after{content:""; position:absolute; left:0; right:0; bottom:0; height:5px; background:var(--brand); z-index:3}
/* A screenshot panel. The image is the background layer, the scrim sits over
   it, and the existing panel children stay where they are: nothing about the
   flex layout changes, so a project with a screenshot and one without still
   line up on the same row. */
.proj__panel--shot{background:var(--charcoal); color:var(--white); justify-content:flex-end}
.proj__panel--shot .proj__panel-top{display:block; margin-bottom:8px; opacity:.78}
/* Smaller than on a plain panel, deliberately. The body column beside it
   already carries the project title at full size, and a screenshot of a
   website has its own headline a third of the way down: a display sized
   name here lands on top of it and the two fight. Kept to one line, the
   label sits in the strong part of the scrim and the picture stays legible. */
.proj__panel--shot .proj__panel-name{font-size:clamp(1.15rem,1.7vw,1.45rem); font-weight:700; letter-spacing:-.02em}
/* The selector has to be at least as specific as the ".proj__panel--shot > *"
   rule below, or the image joins the flex flow as a full height item and
   pushes the name off the bottom of the panel. */
.proj__panel--shot .proj__shot{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:top center; z-index:0;
}
/* Both the image and the scrim are positioned, so without these the image
   paints over the scrim simply by coming later in the markup. */
.proj__panel--shot::before{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(to bottom, rgba(17,20,24,0) 16%, rgba(17,20,24,.5) 38%, rgba(17,20,24,.9) 66%, rgba(17,20,24,.95) 100%);
}
.proj__panel--shot > *{position:relative; z-index:2}
.proj:nth-of-type(even) .proj__panel--shot .chip{background:rgba(255,255,255,.1); border-color:rgba(255,255,255,.2); color:#fff}
.proj__panel-chips{display:block; margin-top:16px}
/* The technologies come off the panel on a phone. The panel is 16:9, so at
   400px wide it is about 225 tall, and three pills plus the client name fill
   it edge to edge: the screenshot the panel exists to show ends up as a
   background behind a wall of labels. They are still on the project's own
   page, at full size, where there is room to read them. */
@media (max-width:700px){.proj__panel-chips{display:none}}
.proj__panel-top{font-size:15px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; opacity:.62}
.proj__panel-name{font-size:clamp(1.5rem,3vw,2.3rem); font-weight:800; letter-spacing:-.03em; line-height:1.05}
.proj__body{display:flex; flex-direction:column; gap:16px; align-items:flex-start}
.proj__tags{display:flex; flex-wrap:wrap; gap:7px 10px; font-size:16px; font-weight:600}
.proj__tags span{border:1px solid var(--line); border-radius:999px; padding:4px 13px; line-height:1.25; color:var(--ink); background:var(--white)}
.proj__body p{color:var(--grey); font-size:19px; line-height:1.55}

/* ── tech chips ───────────────────────────────────────── */
.chips{display:flex; flex-wrap:wrap; gap:7px}
.chip{
  display:inline-flex; align-items:center; gap:8px; padding:5px 14px 5px 5px;
  border:1px solid var(--line); border-radius:999px; background:var(--white); color:var(--ink);
  font-size:15px; font-weight:500; line-height:1; white-space:nowrap;
}
.chip__g{
  width:24px; height:24px; border-radius:7px; display:grid; place-items:center; flex:0 0 auto;
  font-size:9.5px; font-weight:700; color:#fff; background:var(--tint,#888); letter-spacing:0;
}
.proj__panel .chip{background:rgba(255,255,255,.1); border-color:rgba(255,255,255,.2); color:#fff}
.chip--btn{cursor:pointer; font-family:var(--f); transition:border-color .18s, background .18s}
.chip--btn:hover{border-color:var(--ink)}
.chip--btn[aria-pressed="true"]{border-color:var(--accent); background:var(--accent-soft)}
.chip--lg{font-size:17px; padding:7px 17px 7px 7px}
.chip--lg .chip__g{width:29px; height:29px; font-size:11px; border-radius:8px}

/* ── quote band ───────────────────────────────────────── */
.quote{max-width:22ch}
.quote blockquote{margin:0; font-size:clamp(1.5rem,3.4vw,2.6rem); font-weight:700; letter-spacing:-.028em; line-height:1.2; max-width:20ch}
.quote__who{margin-top:30px; font-size:22px; font-weight:600}
.quote__role{font-size:19px; color:#B5B1AC}
.quotemark{font-size:clamp(4rem,9vw,7.5rem); line-height:.6; font-weight:800; color:var(--brand-lift)}

/* ── brand grid ───────────────────────────────────────── */
/* A wall of equal tiles. Every cell is the same height and every logo
   is fitted inside the same inner box, so a tall mark and a long
   wordmark sit at the same visual weight instead of one towering over
   the other. Two columns on a phone rather than one tall stack. */
/* The rules are drawn by each cell rather than by a background showing
   through the gaps. A background grid leaves a grey slab wherever the
   last row is short of a full set, which is most counts; a ring on each
   cell draws nothing where there is no cell, and neighbouring rings
   overlap across the 1px gap into a single line. */
/* Twenty marks in twenty bordered cells read as a spreadsheet, and the
   borders are the loudest thing in the section. Without them, at one
   optical size and one tone, the same twenty read as a single row of
   evidence, which is the only reason the section is on the page. */
.brands{display:grid; grid-template-columns:repeat(6,minmax(0,1fr));
  gap:clamp(16px,2.6vw,38px); align-items:center; margin-top:clamp(20px,2.6vw,32px)}
.brands div{display:grid; place-items:center; text-align:center;
  font-size:15px; font-weight:600; color:var(--grey-2); line-height:1.25}
.brands div>span{display:grid; place-items:center; width:100%}
.brands img{width:100%; height:auto; display:block;
  filter:grayscale(1) contrast(.9) opacity(.78); mix-blend-mode:multiply;
  transition:filter .2s ease}
.brands div:hover img{filter:grayscale(0) opacity(1)}
@media (max-width:900px){.brands{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:560px){.brands{grid-template-columns:repeat(3,minmax(0,1fr)); gap:18px}}
/* The marks arrive one after another as the wall comes into view, once,
   and then sit still. The cells and their rules never move: a wall that is
   already built and fills up is a different thing from a wall that draws
   itself, and only the first is worth watching.
   is-staged is added by the script, so with it blocked every logo is simply
   there. --i is the cell's place in the order. */
.brands.is-staged div>span{opacity:0; transform:translateY(7px)}
.brands.is-staged.is-in div>span{
  opacity:1; transform:none;
  transition:opacity .55s ease, transform .55s cubic-bezier(.2,.8,.3,1);
  transition-delay:calc(var(--i,0) * 42ms);
}
@media (prefers-reduced-motion:reduce){
  .brands.is-staged div>span{opacity:1; transform:none}
}

/* ── service list ─────────────────────────────────────── */
/* ── service cards ────────────────────────────────────────
   Two columns, not an auto-fill track count. auto-fill always
   builds as many tracks as fit and then leaves the spare ones
   empty, so a group of two in a three-track grid left a hole on
   the right and squeezed the text to about 145px: every bullet
   wrapped after three words. Two columns also gives each card a
   readable measure. A group with an odd number of services ends
   with one card spanning the full width rather than an orphan
   sitting alone in a half-width column.                        */
.svcgrid{display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(18px,2.2vw,26px); margin-top:clamp(26px,3vw,40px)}
@media (max-width:860px){.svcgrid{grid-template-columns:1fr}}

.svccard{
  border:1px solid var(--line); border-top:3px solid var(--brand); border-radius:5px;
  padding:clamp(24px,2.4vw,34px); background:var(--white);
  display:flex; flex-direction:column; gap:14px; position:relative;
  transition:border-color .2s ease, box-shadow .2s ease, transform .2s ease;
  /* A card is a link target from the area pages. Without this the jump puts
     the card's top edge under the header that stays on screen. */
  scroll-margin-top:clamp(84px,9vw,112px);
}
.svccard:hover{box-shadow:0 3px 10px rgba(17,17,17,.05), 0 18px 40px -28px rgba(17,17,17,.5);
  transform:translateY(-2px)}

/* Heading row: name on the left, the count sitting quietly to the
   right rather than stacked above it eating a line of its own. */
.svccard__top{display:flex; align-items:baseline; justify-content:space-between; gap:16px}
.svccard h3{font-size:clamp(1.15rem,1.5vw,1.35rem); letter-spacing:-.015em}
.svccard__no{font-size:13px; font-weight:700; letter-spacing:.14em; color:var(--grey-2);
  font-variant-numeric:tabular-nums; flex:0 0 auto}

.svccard p{color:var(--grey); font-size:17.5px; line-height:1.55; max-width:46ch}

/* A rule between the summary and the list, so the bullets read as
   "what this includes" rather than as more of the same paragraph. */
.svccard ul{margin:0; padding:16px 0 0 1.15em; border-top:1px solid var(--line);
  color:var(--grey); font-size:16.5px; line-height:1.45;
  display:flex; flex-direction:column; gap:9px}
.svccard li::marker{color:var(--accent)}

/* The odd one out goes full width. Its bullets then run in two
   columns, so a wide card does not become a long thin list. */
.svccard--wide{grid-column:1/-1}
@media (min-width:861px){
  .svccard--wide{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);
    column-gap:clamp(28px,3vw,48px); row-gap:12px; align-items:start}
  .svccard--wide .svccard__top{grid-column:1; align-items:baseline}
  .svccard--wide p{grid-column:1; margin:0}
  .svccard--wide ul{grid-column:2; grid-row:1/4; border-top:0; border-left:1px solid var(--line);
    padding:2px 0 2px calc(1.15em + 22px)}
}

/* ── useful tools ─────────────────────────────────────────
   Close relatives of the service cards, with three differences
   that matter. The whole card is the link, because the only
   thing this page does is send you somewhere else and a card
   where four words are clickable makes people hunt. The icon
   sits beside the name rather than opposite it, because there
   is no second column to balance against. And the accent edge
   is on the left rather than the top, which is what stops a
   grid of two reading as a second run of services.          */
.toolgrid{display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(18px,2.2vw,26px); margin-top:clamp(26px,3vw,40px)}
@media (max-width:860px){.toolgrid{grid-template-columns:1fr}}

.toolcard{
  border:1px solid var(--line); border-left:3px solid var(--brand); border-radius:5px;
  padding:clamp(24px,2.4vw,34px); background:var(--white); color:inherit;
  display:flex; flex-direction:column; gap:14px; text-decoration:none;
  transition:border-color .2s ease, box-shadow .2s ease, transform .2s ease;
  scroll-margin-top:clamp(84px,9vw,112px);
}
.toolcard:hover{box-shadow:0 3px 10px rgba(17,17,17,.05), 0 18px 40px -28px rgba(17,17,17,.5);
  transform:translateY(-2px)}
.toolcard:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

.toolcard__top{display:flex; align-items:flex-start; gap:16px}
.toolcard h2{font-size:clamp(1.15rem,1.5vw,1.35rem); letter-spacing:-.015em; margin:0}
/* Headings get text-wrap:balance from the global rule at the top of this
   file; these two are paragraphs, so they do not, and both were leaving a
   single word stranded on a line of its own. balance evens the lines of a
   short run; pretty only stops the orphan, which is what a real paragraph
   wants. */
.toolcard__line{color:var(--accent); font-size:16px; line-height:1.45; margin:5px 0 0;
  text-wrap:balance}
.toolcard__sum{color:var(--grey); font-size:17.5px; line-height:1.55; max-width:46ch; margin:0;
  text-wrap:pretty}
.toolcard li{text-wrap:pretty}

/* A chip behind the icon, unlike the bare service icons. The service
   icon sits in a card that already has a heading row to anchor it; here
   the icon is the only thing to the left of the name and needs a shape
   of its own or it floats. accent-soft is the same tint the pressed
   filter chips and the sent-message panel use. */
.toolicon{flex:0 0 auto; width:44px; height:44px; border-radius:9px;
  background:var(--accent-soft); color:var(--brand-deep);
  display:grid; place-items:center; margin-top:2px}
.toolicon__svg{width:24px; height:24px}
.toolicon--img img{width:26px; height:26px; object-fit:contain}

.toolcard ul{margin:0; padding:16px 0 0 1.15em; border-top:1px solid var(--line);
  color:var(--grey); font-size:16.5px; line-height:1.45;
  display:flex; flex-direction:column; gap:9px}
.toolcard li::marker{color:var(--accent)}

/* Pushed to the foot by the auto margin, so two cards of unequal
   length still line their "open" up with each other. */
.toolcard__go{margin-top:auto; padding-top:4px; font-weight:700; color:var(--accent);
  display:inline-flex; align-items:center; gap:8px}
/* arrow() ships a bare <svg> and leaves the stroke and the size to the
   CSS, exactly as the buttons and text links do. Without these three
   declarations it renders as a black triangle the height of the card. */
.toolcard__go svg{width:16px; height:16px; fill:none; stroke:currentColor;
  stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round;
  transition:transform .2s ease}
.toolcard:hover .toolcard__go svg{transform:translateX(3px)}

/* An odd number of tools leaves one without a partner, and a half-width
   card with an empty half beside it reads as a page still loading. The
   last one spans instead, with its list moved into the second column so
   a wide card does not become a long thin one. The Services page solves
   the same problem the same way. */
.toolcard--wide{grid-column:1/-1}
@media (min-width:861px){
  /* The words lead, so they get the wider half. It was the other way
     round, copied from the services card, where the right column carries
     a longer list than this one does. */
  .toolcard--wide{display:grid; grid-template-columns:minmax(0,1.15fr) minmax(0,.95fr);
    column-gap:clamp(28px,3vw,48px); row-gap:12px; align-items:start}
  .toolcard--wide .toolcard__top{grid-column:1}
  .toolcard--wide .toolcard__sum{grid-column:1; margin:0}
  .toolcard--wide ul{grid-column:2; grid-row:1/4; border-top:0;
    border-left:1px solid var(--line); padding:2px 0 2px calc(1.15em + 22px)}
  .toolcard--wide .toolcard__go{grid-column:1; margin-top:8px}
}

.toolsnone{color:var(--grey); font-size:18px; margin-top:clamp(26px,3vw,40px)}

/* A tool's picture. Across the top of a half-width card, bleeding to its
   edges; on the wide card, down the left at the full height of the card. */
.toolcard{--tp:clamp(24px,2.4vw,34px)}
.toolcard__shot{display:block; margin:calc(-1 * var(--tp)) calc(-1 * var(--tp)) 4px;
  border-bottom:1px solid var(--line); background:#F1F6FC; line-height:0; overflow:hidden;
  border-radius:3px 4px 0 0}
.toolcard__shot img{width:100%; height:auto; display:block}
@media (min-width:1260px){
  .toolcard--wide.toolcard--shot{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr); row-gap:14px}
  .toolcard--wide.toolcard--shot .toolcard__shot{grid-column:1; grid-row:1 / span 4; align-self:stretch;
    position:relative; margin:calc(-1 * var(--tp)) 0 calc(-1 * var(--tp)) calc(-1 * var(--tp));
    border-bottom:0; border-right:1px solid var(--line); border-radius:3px 0 0 4px}
  /* Full height of the card, no band above or below; trimmed evenly at the
     sides if the words beside it run taller than the picture. */
  .toolcard--wide.toolcard--shot .toolcard__shot::before{content:""; display:block; padding-top:66.667%}
  .toolcard--wide.toolcard--shot .toolcard__shot img{position:absolute; inset:0; width:100%; height:100%;
    object-fit:cover; object-position:center}
  .toolcard--wide.toolcard--shot > :not(.toolcard__shot){grid-column:2}
  .toolcard--wide.toolcard--shot ul{grid-row:auto; border-left:0; border-top:1px solid var(--line);
    padding:16px 0 0 1.15em}
  .toolcard--wide.toolcard--shot .toolcard__go{margin-top:4px}
}
@media (min-width:861px) and (max-width:1259px){
  .toolcard--wide.toolcard--shot .toolcard__shot{grid-column:1 / -1; grid-row:1}
  .toolcard--wide.toolcard--shot .toolcard__top{grid-row:2}
  .toolcard--wide.toolcard--shot .toolcard__sum{grid-row:3}
  .toolcard--wide.toolcard--shot ul{grid-row:2 / 5}
  .toolcard--wide.toolcard--shot .toolcard__go{grid-row:4}
}


/* The closing block. It used to borrow .cta, which caps its heading at
   16ch: right for four words on a full-bleed charcoal band, wrong for a
   whole question on white, which it broke across three lines with half
   the page empty beside it. No cap here, so the sentence runs as one
   line wherever it fits; the global text-wrap:balance on headings evens
   it out at the widths where it cannot. */
.toolsclose h2{max-width:none}
.toolsclose .body{text-wrap:pretty}

/* ── RISE plugins ─────────────────────────────────────────
   The tool card, but not one big link: a plugin card has a
   price, a Buy button and up to two other links, so the card
   stays still on hover and the actions sit in a footer row
   pushed to the bottom, lining up across a row of cards.    */
.plugnote{color:var(--grey); font-size:17px; line-height:1.5; max-width:66ch; margin:0;
  text-wrap:pretty}
.plugcard:hover{transform:none; box-shadow:none}
.plugcard__shot{margin:calc(-1 * clamp(24px,2.4vw,34px)) calc(-1 * clamp(24px,2.4vw,34px)) 4px;
  border-bottom:1px solid var(--line);
  overflow:hidden; border-radius:3px 4px 0 0; background:var(--off)}
.plugcard__shot img{width:100%; height:auto; display:block}
.plugcard__foot{margin-top:auto; padding-top:8px; display:flex; flex-wrap:wrap;
  align-items:center; gap:12px 18px}
.plugcard__price{font-size:clamp(1.3rem,1.8vw,1.55rem); font-weight:700; color:var(--heading);
  letter-spacing:-.02em; font-variant-numeric:tabular-nums}
.plugcard__buy{padding:10px 22px; font-size:17px}
.plugcard__off{color:var(--grey); font-size:15.5px; font-weight:600}
.plugcard__shot{display:block}

/* The hero: the pitch on the left, and on the right a panel saying what
   every licence includes and how buying works, which is the question a
   buyer has before they look at any one plugin. */
.plughero{padding-bottom:clamp(40px,5vw,70px)}
.plughero__grid{display:grid; gap:clamp(28px,4vw,56px); align-items:center}
@media (min-width:900px){.plughero__grid{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr)}}
.plugpanel{background:var(--white); border:1px solid var(--line); border-top:3px solid var(--brand);
  border-radius:6px; padding:clamp(22px,2.4vw,30px); box-shadow:0 18px 40px -30px rgba(17,17,17,.35)}
.plugpanel__h{font-size:1.05rem; margin:0 0 14px; letter-spacing:-.005em}
.plugpanel .plugincl{color:var(--ink); font-size:15.5px}
.plugpanel__note{margin:18px 0 0; padding-top:16px; border-top:1px solid var(--line);
  color:var(--grey); font-size:14.5px; line-height:1.5; text-wrap:pretty}

/* The featured plugin: the first in the order set under Plugins, as a wide
   card with its picture beside the pitch and its first few features. */
.plugfeat{display:grid; gap:0; background:var(--white); border:1px solid var(--line);
  border-left:3px solid var(--brand); border-radius:5px; overflow:hidden; scroll-margin-top:clamp(84px,9vw,112px)}
@media (min-width:1260px){.plugfeat{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr); align-items:stretch}}
.plugfeat__art{display:block; background:#F1F6FC; line-height:0}
/* Beside the words the picture fills the full height of the card, top to
   bottom, with no band above or below it. It is at least its own shape, and
   when the words run taller it is trimmed evenly at the sides rather than
   left floating. */
@media (min-width:1260px){
  .plugfeat__art{position:relative; overflow:hidden}
  .plugfeat__art::before{content:""; display:block; padding-top:66.667%}
  .plugfeat .plugfeat__art img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:center}
}
.plugfeat__art img{width:100%; height:auto; display:block}
.plugfeat__body{padding:clamp(24px,3vw,40px); display:flex; flex-direction:column; gap:14px}
.plugfeat h2{margin:0; font-size:clamp(1.5rem,2.2vw,1.9rem); letter-spacing:-.02em}
.plugfeat__line{margin:0; color:var(--accent); font-size:17px; line-height:1.45; text-wrap:pretty}
.plugfeat__pts{font-size:15.5px; color:var(--grey)}
.plugfeat .plugcard__foot{margin-top:6px; padding-top:0}

/* The rest, three to a row: nine plugins after the featured one fill three
   rows exactly. Two to a row on a tablet, one on a phone. */
.plugrid{--pp:clamp(20px,2vw,26px); display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(18px,2vw,26px); margin-top:clamp(18px,2vw,26px)}
@media (max-width:1000px){.plugrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:600px){.plugrid{grid-template-columns:1fr}}
.plugcard{padding:var(--pp); gap:12px}
.plugcard .plugcard__shot{margin:calc(-1 * var(--pp)) calc(-1 * var(--pp)) 4px}
.plugcard h2{font-size:1.2rem}
.plugcard .toolcard__line{font-size:15.5px; text-wrap:pretty}
.plugcard .plugcard__price{font-size:1.3rem}
.plugcard .plugcard__buy{padding:9px 20px; font-size:16px}
.plugcard__foot{gap:10px 16px}
.plugcard__more{margin-left:auto; color:var(--brand-deep); font-weight:600; font-size:15px;
  text-underline-offset:3px}
.plugcard__name{color:inherit; text-decoration:none}
.plugcard__name:hover{text-decoration:underline; text-underline-offset:4px; text-decoration-thickness:2px}
.plugcard__name:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* What every licence comes with. A row of small ticks under the note on the
   list, a plain column beside the features on a plugin's own page. */
.plugincl{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:9px;
  color:var(--grey); font-size:15.5px; line-height:1.45}
.plugincl li{position:relative; padding-left:24px}
.plugincl li::before{content:""; position:absolute; left:2px; top:.38em; width:11px; height:6px;
  border-left:2px solid var(--accent); border-bottom:2px solid var(--accent); transform:rotate(-45deg)}
.plugincl__h{margin:34px 0 14px; font-size:1rem; letter-spacing:-.005em}

.plugback{color:inherit; text-decoration:none}
.plugback:hover{text-decoration:underline; text-underline-offset:3px}
.plughero__buy{margin-top:26px; padding-top:0}
.plugart{margin:0; border-radius:10px; overflow:hidden}
.plugart img{width:100%; height:auto; display:block}

.plugmore{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(14px,1.8vw,22px);
  margin-top:clamp(22px,3vw,34px)}
@media (max-width:860px){.plugmore{grid-template-columns:1fr}}
.plugmini{display:flex; flex-direction:column; gap:6px; padding:20px 22px; border:1px solid var(--line);
  border-left:3px solid var(--brand); border-radius:5px; text-decoration:none; color:inherit;
  transition:box-shadow .2s ease}
.plugmini:hover{box-shadow:0 3px 10px rgba(17,17,17,.05), 0 18px 40px -28px rgba(17,17,17,.5)}
.plugmini strong{font-size:1.1rem; letter-spacing:-.01em}
.plugmini span{color:var(--grey); font-size:15.5px; line-height:1.45}

/* ── group heading ────────────────────────────────────────
   A rule running to the right edge, so each stage reads as a
   band across the page rather than a label floating above a
   short row of cards.                                        */
.grouphead{display:flex; align-items:baseline; gap:18px; flex-wrap:wrap;
  margin-top:clamp(48px,6vw,86px)}
.grouphead:first-of-type{margin-top:0}
.grouphead h2{font-size:clamp(1.5rem,2.8vw,2.2rem); flex:0 0 auto}
.grouphead span{color:var(--grey); font-size:17px; flex:0 1 auto}
.grouphead::after{content:""; flex:1 1 60px; height:1px; background:var(--line);
  align-self:center; min-width:30px}

/* ── forms ────────────────────────────────────────────── */
.form{display:grid; gap:22px; max-width:620px; margin-top:clamp(28px,4vw,48px)}
.field{display:flex; flex-direction:column; gap:8px}
.field label{font-size:17px; font-weight:600}
.field input,.field select,.field textarea{
  font-family:var(--f); font-size:19px; color:var(--ink); background:var(--white);
  border:2px solid var(--line); border-radius:4px; padding:14px 16px; width:100%;
}
.field textarea{min-height:150px; resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent); outline:none}
.row2{display:grid; grid-template-columns:1fr 1fr; gap:22px}
@media (max-width:620px){.row2{grid-template-columns:1fr}}
.consent{display:flex; gap:13px; align-items:flex-start; font-size:17px; color:var(--grey); line-height:1.45}
.consent input{margin-top:4px; width:20px; height:20px; accent-color:var(--accent); flex:0 0 auto}
.turnstile{border:2px dashed var(--line); border-radius:4px; padding:18px 20px; display:flex; align-items:center; gap:14px; font-size:15px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--grey-2); background:var(--off)}
.turnstile b{width:26px; height:26px; border-radius:7px; background:var(--brand); flex:0 0 auto}
.formnote{font-size:16px; color:var(--grey-2); line-height:1.5; max-width:46em}
.sent{border-left:5px solid var(--accent); background:var(--accent-soft); border-radius:4px; padding:20px 22px; font-size:18px; display:grid; gap:7px; line-height:1.5}
.sent b{font-size:20px}
.sent.bad{border-left-color:var(--bad); background:var(--bad-soft)}

.facts{display:flex; flex-direction:column; border-top:2px solid var(--line)}
.facts div{padding-block:18px; border-bottom:1px solid var(--line)}
.facts dt{font-size:15px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--grey-2); margin-bottom:5px}
.facts dd{margin:0; font-size:20px; font-weight:500}
.facts a{text-decoration:none}
/* Several links in one definition, so they wrap as a sentence rather than
   each taking a line of its own. */
.factlinks{display:block}
.factlinks a{font-weight:500}
.facts a:hover{color:var(--accent)}

/* ── legal ────────────────────────────────────────────── */
.legal{max-width:40em; color:var(--grey); display:flex; flex-direction:column; gap:1.1em; font-size:19px; margin-top:34px}
.legal h2,.legal h3{color:var(--ink); font-size:clamp(1.3rem,2.2vw,1.75rem); margin-top:.7em}
.legal ul{margin:0; padding-left:1.2em; display:flex; flex-direction:column; gap:7px}
.legal li::marker{color:var(--accent)}
.legal table{border-collapse:collapse; width:100%; font-size:17px; min-width:520px}
.legal th,.legal td{text-align:left; padding:12px 14px; border-bottom:1px solid var(--line); vertical-align:top; color:var(--grey)}
.legal th{color:var(--ink); font-weight:700; font-size:15px; letter-spacing:.08em; text-transform:uppercase}
.tablewrap{overflow-x:auto; max-width:100%}
/* A table nobody remembered to wrap still must not break the page. The
   520px floor is there so columns stay readable on a desktop; on a phone
   it was pushing the whole privacy notice 169px wider than the screen, and
   a cramped table is better than a page that scrolls sideways. Pre-dates
   the composition work; found while sweeping the pages for it. */
@media (max-width:620px){
  .legal table{min-width:0; font-size:15px}
  .legal th,.legal td{padding:9px 8px}
  .legal th{font-size:13px; letter-spacing:.05em}
  /* A cookie name is one long unbroken token, and one of them was still
     forcing the table twelve pixels past the screen. */
  .legal th,.legal td{overflow-wrap:anywhere}
}

/* ── footer ───────────────────────────────────────────── */
.foot{background:var(--charcoal); color:var(--white); padding-block:clamp(56px,7vw,92px) 30px}
.foot__cols{display:grid; grid-template-columns:minmax(0,1.25fr) repeat(3,minmax(0,1fr)); gap:clamp(28px,4vw,52px)}
@media (max-width:900px){.foot__cols{grid-template-columns:1fr 1fr}.foot__brand{grid-column:1 / -1}}
@media (max-width:520px){.foot__cols{grid-template-columns:1fr}}
.foot__brand img{height:58px; width:auto}
.foot__brand p{color:#B5B1AC; font-size:18px; margin-top:18px; max-width:28ch; line-height:1.5}
/* Named, not drawn. One row of plain links under the blurb, separated by the
   same middot the disclosure line uses, so it reads as part of the footer
   rather than as a widget bolted to it. */
.foot__social{
  list-style:none; margin:20px 0 0; padding:0;
  display:flex; flex-wrap:wrap; align-items:center; gap:0 14px;
  font-size:17px;
}
.foot__social li{display:flex; align-items:center; gap:14px}
.foot__social li + li::before{content:"·"; color:#6E6A65}
.foot__social a{
  color:#D6D2CD; text-decoration:none;
  padding-block:3px; border-bottom:1px solid transparent;
}
.foot__social a:hover{color:#fff; border-bottom-color:var(--brand-lift)}
/* credential strip: light band, because both supplied badges are dark artwork */
.credstrip{background:var(--off); border-top:1px solid var(--line)}
.credstrip__in{display:flex; align-items:center; justify-content:space-between; gap:24px; flex-wrap:wrap; padding-block:26px}
.credstrip img{display:block}
.cred-fsb{height:34px; width:auto}
.cred-green{height:48px; width:auto}
/* A circle reads smaller than a wordmark of the same height, so the disc is
   set a little taller than the FSB mark to sit as its equal rather than as a
   footnote beside it. */
.cred-pb{height:60px; width:auto}
/* The two marks about the business, kept together at one end of the row. */
.credpair{display:flex; align-items:center; gap:clamp(20px,3vw,38px); flex-wrap:wrap}
@media (max-width:560px){.credstrip__in{justify-content:flex-start; gap:20px}.credpair{gap:20px}.cred-fsb{height:28px}.cred-green{height:36px}.cred-pb{height:46px}}
.fcol h3{font-size:22px; font-weight:800; letter-spacing:.04em; margin-bottom:16px; color:var(--brand-lift); width:fit-content}
.fcol ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:11px; font-size:18px}
.fcol a{color:#D6D2CD; text-decoration:none}
.fcol a:hover{color:#fff}
.fcol .fcol__h2{margin-top:30px}
.foot__base{margin-top:clamp(40px,5vw,64px); padding-top:22px; border-top:1px solid rgba(255,255,255,.14); color:#A5A09B; font-size:13px; line-height:1.6}
.foot__base p{margin:0}

/* ── cookie bar ───────────────────────────────────────── */
.ck{position:fixed; left:16px; right:16px; bottom:16px; z-index:80; max-width:680px; margin-inline:auto; background:var(--white); border:2px solid var(--ink); border-radius:4px; padding:24px 26px; display:grid; gap:16px; box-shadow:0 20px 50px -20px rgba(0,0,0,.35)}
.ck[hidden]{display:none}
.ck h3{font-size:22px}
/* The illustration sits beside the words rather than above them, so the box
   does not get taller, and it is the first thing to go when the screen is
   narrow enough that the words need the full width. */
.ck__head{display:flex; gap:20px; align-items:flex-start}
.ck__head > div:first-child{flex:1 1 auto; min-width:0; display:grid; gap:14px}
.ck__art{flex:0 0 auto; width:132px; margin-top:2px}
.ck__art img{width:100%; height:auto}
@media (max-width:620px){.ck__art{display:none}}
.ck p{font-size:17px; color:var(--grey); line-height:1.5}
.ck__btns{display:flex; flex-wrap:wrap; gap:10px}
.ck .btn{padding:11px 22px; font-size:17px}
.ck__prefs{display:grid; gap:13px; border-top:1px solid var(--line); padding-top:16px}
/* display:grid beats the hidden attribute, so the detail was on show from
   the start and "What is set" appeared to do nothing. */
.ck__prefs[hidden]{display:none}
.pref{display:flex; gap:13px; align-items:flex-start; font-size:16px; color:var(--grey); line-height:1.45}
.pref input{margin-top:3px; width:19px; height:19px; accent-color:var(--accent); flex:0 0 auto}
.pref b{display:block; color:var(--ink); font-size:17px}
.ph{border-bottom:2px dotted var(--grey-2)}
.eyebrow{font-size:17px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--grey-2); margin-bottom:18px}
.sec--dark .eyebrow{color:#8E8983}
.sr{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0)}

/* ── the local pages ────────────────────────────────────────────────
   One card grid for the index and for the services on a town page, a line
   for the travel note, and a plain list of neighbours. Nothing here is a new
   look: it is the service card and the facts list the rest of the site
   already uses, in a different arrangement. */
.arealede{max-width:68ch}
.arealede p{margin-bottom:1.05em}
.arealede h2{font-size:clamp(1.4rem,2.2vw,1.9rem);margin:1.6em 0 .5em}
.arealine{display:flex;align-items:center;gap:9px;margin-top:20px;
          color:var(--grey);font-size:17px}
.arealine__pin{width:20px;height:20px;flex:0 0 auto;fill:none;stroke:var(--brand);
               stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.herobtns{display:flex;flex-wrap:wrap;gap:12px}
.plainlink{color:inherit;text-decoration:none}
.plainlink:hover{color:var(--brand)}

.arealist{display:grid;gap:18px;margin-top:30px;
          grid-template-columns:repeat(auto-fill,minmax(290px,1fr))}
.areacard{display:flex;flex-direction:column;gap:10px;text-decoration:none;
          background:var(--white);border:1px solid var(--line);border-radius:10px;
          padding:24px 24px 22px;transition:border-color .15s,transform .15s}
.areacard:hover{border-color:var(--brand);transform:translateY(-2px)}
.areacard h2,.areacard h3{font-size:clamp(1.2rem,1.6vw,1.4rem)}
.areacard p{color:var(--grey);font-size:16.5px;line-height:1.5;margin:0;flex:1 1 auto}
.areacard .tlink{pointer-events:none}

.qa{display:grid;gap:0;margin-top:26px;max-width:72ch}
.qa__item{padding-block:22px;border-top:1px solid var(--line)}
.qa__item:last-child{border-bottom:1px solid var(--line)}
.qa__item h3{font-size:clamp(1.15rem,1.5vw,1.3rem);margin-bottom:8px}
.qa__item p{color:var(--grey);font-size:17px;line-height:1.6;margin:0}

.nearby{list-style:none;padding:0;margin:18px 0 0;display:flex;flex-wrap:wrap;gap:10px}
.nearby a{display:inline-block;padding:9px 16px;border:1px solid var(--line);
          border-radius:999px;text-decoration:none;font-size:16px;color:var(--grey);
          background:var(--white);transition:border-color .15s,color .15s}
.nearby a:hover{border-color:var(--brand);color:var(--heading)}

/* ══ Phase 2 additions ══════════════════════════════════════════════ */
.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--ink);color:#fff;
  padding:12px 20px;font-weight:600;border-radius:0 0 4px 0}
.skip:focus{left:0}

.artcap{text-align:center;font-size:16px;color:var(--grey-2);margin-top:14px;
  font-weight:600;letter-spacing:.1em;text-transform:uppercase}

.filterlab{font-size:16px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  color:var(--grey-2);margin-bottom:16px}
.workempty{color:var(--grey);padding-block:40px}

/* Filters are links, not buttons, so the pressed state is aria-current. */
.chip--btn[aria-current="true"]{border-color:var(--ink);background:var(--ink);color:#fff}
.chip--btn[aria-current="true"] .chip__g{background:#fff;color:var(--ink)}
.chip__g--img{background:transparent!important;padding:0;display:grid;place-items:center}
.chip__g--img img{width:100%;height:100%;object-fit:contain}

.casehead{font-size:clamp(1.6rem,2.8vw,2.3rem)}
.casebody{margin-top:20px;font-size:20px}
.caselist{margin-top:20px;padding-left:1.15em;display:flex;flex-direction:column;gap:11px;font-size:20px}

.updated{font-size:16px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--grey-2)}
.asidenote{margin-top:18px;font-size:16px;color:var(--grey-2)}
.asidenote a{color:var(--accent)}

/* The other policies, beside a legal page. A list of four, so it is a list
   and not a panel: a rule down the left says "this is an aside" with less
   furniture than a box would, and the page you are on is stated rather than
   linked back to itself. */
.sidenav{padding-left:22px; border-left:2px solid var(--line)}
.sidenav__head{
  font-size:15px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
  color:var(--grey-2); margin:0 0 16px;
}
.sidenav ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px}
.sidenav a{
  font-size:18px; font-weight:600; color:var(--ink); text-decoration:none;
  border-bottom:2px solid transparent; transition:border-color .18s, color .18s;
}
.sidenav a:hover{color:var(--accent); border-bottom-color:var(--accent)}
.sidenav__here{font-size:18px; font-weight:600; color:var(--grey-2)}
@media (max-width:900px){
  .sidenav{padding-left:0; padding-top:24px; border-left:0; border-top:2px solid var(--line)}
}

/* The uploaded artwork is already centred on a common canvas, so
   constraining the canvas is what makes every logo match. */
.brands img{max-width:100%; max-height:100%; width:auto; height:auto; object-fit:contain}

body.menu-open{overflow:hidden}

/* The compact masthead turns the button into a 52px white pill. Two of the
   four variants need to opt out of that fixed square: the word needs its
   natural width, and the dot grid needs its own centring. */
.mast--compact .hamb--word{width:auto; height:52px; padding:0 20px; gap:4px;
  flex-direction:column; justify-content:center; align-items:stretch}
.mast--compact .hamb--word .hamb__word{font-size:14px}
.mast--compact .hamb--dots{justify-content:center; align-content:center; gap:7px; padding:0}
.mast--compact .hamb--dots i{width:8px; height:8px}

/* ── projects filter ────────────────────────────────────────────────
   Two ways in. Service first, because it is the question most visitors
   arrive with; technology second, capped so twenty-two chips do not
   become a wall across three rows.                                    */
.filterrow + .filterrow{margin-top:clamp(20px,2.4vw,30px)}
.filterlab{font-size:14px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--grey-2); margin-bottom:12px}

/* A service chip is words, not a monogram: there is no logo to show. */
.chip--plain{padding-inline:18px}
.chip--extra{display:none}
.chips.is-open .chip--extra{display:inline-flex}

.chip--more{
  border:1px dashed var(--grey-2); background:transparent; color:var(--grey);
  font:inherit; font-size:16px; font-weight:600; cursor:pointer; padding-inline:18px;
}
.chip--more:hover{border-color:var(--ink); color:var(--ink)}

.filterclear{margin-top:clamp(18px,2vw,26px); font-size:16px; color:var(--grey)}
.filterclear strong{color:var(--ink)}
.filterclear a{color:var(--accent); margin-left:10px}

/* No JavaScript: nothing is hidden and the toggle is not offered. */
.no-js .chip--extra{display:inline-flex}
.no-js .chip--more{display:none}

/* ── menu opening ───────────────────────────────────────────────────
   The panel wipes up from the bottom and the items follow, each a beat
   behind the last, so the eye is led down the list rather than hit with
   all five at once. Closing plays the same move in reverse.

   [hidden] still means display:none, so the panel has to stay in the
   flow while it animates: site.js clears hidden first, then adds
   .is-open on the next frame, and on close waits for the transition
   before putting hidden back. */
.overlay{
  clip-path:inset(0 0 100% 0);
  transition:clip-path .42s cubic-bezier(.5,0,.2,1);
}
.overlay.is-open{clip-path:inset(0 0 0 0)}

.overlay__top{opacity:0; transform:translateY(-12px);
  transition:opacity .3s ease .12s, transform .3s ease .12s}
.overlay.is-open .overlay__top{opacity:1; transform:none}

.overlay nav a{
  opacity:0; transform:translateY(26px);
  transition:opacity .34s ease, transform .42s cubic-bezier(.22,.75,.3,1);
}
.overlay.is-open nav a{opacity:1; transform:none}
/* Staggered by hand rather than with a counter, because there are five
   fixed items and a nth-child list is clearer than a custom property. */
.overlay.is-open nav a:nth-child(1){transition-delay:.13s}
.overlay.is-open nav a:nth-child(2){transition-delay:.19s}
.overlay.is-open nav a:nth-child(3){transition-delay:.25s}
.overlay.is-open nav a:nth-child(4){transition-delay:.31s}
.overlay.is-open nav a:nth-child(5){transition-delay:.37s}
.overlay.is-open nav a:nth-child(6){transition-delay:.43s}

.overlay__foot{opacity:0; transition:opacity .3s ease .4s}
.overlay.is-open .overlay__foot{opacity:1}

/* The W mark drifts in behind everything, a touch slower than the wipe. */
.overlay::before{opacity:0; transform:translateX(40px) scale(.97);
  transition:opacity .7s ease .1s, transform .9s cubic-bezier(.22,.75,.3,1) .1s}
.overlay.is-open::before{opacity:.16; transform:none}

/* The hamburger becomes a cross while the panel is open. */
.hamb i{transition:transform .32s cubic-bezier(.5,0,.2,1), opacity .2s ease, width .32s ease}
.hamb[aria-expanded="true"] i:nth-child(1){transform:translateY(6px) rotate(45deg); width:100%}
.hamb[aria-expanded="true"] i:nth-child(2){transform:translateY(-6px) rotate(-45deg); width:100%}
.hamb--bars[aria-expanded="true"] i:nth-child(1){transform:translateY(9px) rotate(45deg)}
.hamb--bars[aria-expanded="true"] i:nth-child(2){opacity:0}
.hamb--bars[aria-expanded="true"] i:nth-child(3){transform:translateY(-9px) rotate(-45deg); width:100%}
.hamb--dots[aria-expanded="true"] i{transform:scale(.55)}
.hamb--word[aria-expanded="true"] i{opacity:0}

@media (prefers-reduced-motion: reduce){
  .overlay,.overlay__top,.overlay nav a,.overlay__foot,.overlay::before,.hamb i{transition:none}
  .overlay{clip-path:none}
}

/* ── menu button, hover ─────────────────────────────────────────────
   Each variant gets a move that suits its own shape rather than one
   generic wiggle applied to everything. All of it is transform and
   opacity only, so nothing reflows and nothing jumps under the cursor.
   Suppressed while the panel is open, where the bars are busy being
   a cross.                                                           */
.hamb:not([aria-expanded="true"]):hover i,
.hamb:not([aria-expanded="true"]):focus-visible i{transition:transform .28s cubic-bezier(.22,.75,.3,1), width .28s ease}

/* two and three bars: the short bar runs out to full width, so the
   stack squares up as you approach it */
.hamb--duo:not([aria-expanded="true"]):hover i:nth-child(2),
.hamb--duo:not([aria-expanded="true"]):focus-visible i:nth-child(2){width:100%}
.hamb--bars:not([aria-expanded="true"]):hover i:nth-child(2),
.hamb--bars:not([aria-expanded="true"]):hover i:nth-child(3),
.hamb--bars:not([aria-expanded="true"]):focus-visible i:nth-child(2),
.hamb--bars:not([aria-expanded="true"]):focus-visible i:nth-child(3){width:100%}
/* and they spread very slightly apart */
.hamb--bars:not([aria-expanded="true"]):hover i:nth-child(1){transform:translateY(-1.5px)}
.hamb--bars:not([aria-expanded="true"]):hover i:nth-child(3){transform:translateY(1.5px)}
.hamb--duo:not([aria-expanded="true"]):hover i:nth-child(1){transform:translateY(-1.5px)}
.hamb--duo:not([aria-expanded="true"]):hover i:nth-child(2){transform:translateY(1.5px)}

/* four dots: they fan out from the centre, one corner at a time */
.hamb--dots:not([aria-expanded="true"]):hover i,
.hamb--dots:not([aria-expanded="true"]):focus-visible i{transition:transform .34s cubic-bezier(.34,1.4,.5,1)}
.hamb--dots:not([aria-expanded="true"]):hover i:nth-child(1){transform:translate(-1.5px,-1.5px)}
.hamb--dots:not([aria-expanded="true"]):hover i:nth-child(2){transform:translate(1.5px,-1.5px); transition-delay:.04s}
.hamb--dots:not([aria-expanded="true"]):hover i:nth-child(3){transform:translate(-1.5px,1.5px); transition-delay:.08s}
.hamb--dots:not([aria-expanded="true"]):hover i:nth-child(4){transform:translate(1.5px,1.5px); transition-delay:.12s}

/* word and rule: the rule sweeps out to meet the width of the word */
.hamb--word .hamb__word{transition:letter-spacing .28s ease}
.hamb--word:not([aria-expanded="true"]):hover .hamb__word{letter-spacing:.2em}
.hamb--word:not([aria-expanded="true"]):hover i:nth-child(3){width:100%}

@media (prefers-reduced-motion: reduce){
  .hamb i,.hamb--word .hamb__word{transition:none}
}

/* Chips are links now, not buttons, so the underline has to go: it made
   a row of them read as body copy rather than controls. */
.chip--btn{text-decoration:none}
.chip--btn:hover{border-color:var(--ink)}
.chip--btn:focus-visible{outline:3px solid var(--accent); outline-offset:3px}

/* ── contact form state ─────────────────────────────────────────── */
/* Honeypot: off-screen rather than display:none, because some bots skip
   anything that is display:none and fill everything else. */
.hp{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}

.req{color:var(--accent); font-weight:700}
.reqnote{font-size:15px; color:var(--grey-2); margin-top:-6px}

.field.is-bad input,.field.is-bad select,.field.is-bad textarea{border-color:var(--bad)}
.fielderr{color:var(--bad); font-size:15px; font-weight:600}
.consent.is-bad{color:var(--bad)}

.sent{display:grid; gap:7px; padding:20px 24px; border-radius:6px; margin-bottom:26px;
  background:var(--accent-soft); border-left:5px solid var(--brand)}
.sent b{font-size:19px; color:var(--heading)}
.sent span{color:var(--grey); font-size:17px; line-height:1.5}
.sent a{color:var(--accent)}
.sent--bad{background:var(--bad-soft); border-left-color:var(--bad)}

.cf-turnstile{margin-block:4px}
.field select:invalid{color:var(--grey-2)}

/* ── service icons ──────────────────────────────────────────────────
   The built-in set is inline SVG stroked with currentColor, so the only
   thing needed to make it follow the brand is to set a colour here. An
   uploaded image keeps whatever colours it arrived with. */
.svccard__top{gap:12px}
.svcicon{width:34px; height:34px; flex:0 0 auto; color:var(--brand-deep); align-self:center}
.svcicon--img{display:grid; place-items:center; width:38px; height:38px}
.svcicon--img img{width:100%; height:100%; object-fit:contain}
.svccard__top h3{flex:1 1 auto}
@media (max-width:520px){.svcicon{width:28px; height:28px}}

/* ── inner page hero illustration ───────────────────────────────────
   These illustrations are wider than they are tall and carry their own
   soft background shape, so they want more width than the portrait does
   and no panel behind them. */
.hero--art .hero--split{grid-template-columns:minmax(0,1fr) minmax(0,.95fr); align-items:center}
.pagehero{margin:0}
.pagehero img{width:100%; max-width:520px; margin-inline:auto; height:auto}
/* Set beside a heading with no standfirst under it. See page-head.php: the
   square legal artwork at the full 520 rendered 520 tall against the 232 of
   the wide banners elsewhere, which is what made those pages look off. */
.pagehero--tight img{max-width:340px}

@media (max-width:900px){
  /* Below the split, the illustration leads rather than trails: on a phone
     it reads as the page's picture, not as something left over. */
  .hero--art .hero--split{grid-template-columns:1fr; gap:28px}
  .hero--art .hero--split > div{order:2}
  .hero--art .pagehero{order:1}
  .pagehero img{max-width:360px}
}

/* An email address is one unbreakable word. In the narrow facts column on a
   phone, helpdesk@wbj.solutions was wider than the screen and pushed the
   whole page sideways. */
.facts dd, .facts dd a, .asidenote, .foot a{overflow-wrap:anywhere}

/* ── service card illustration ──────────────────────────────────────
   A band across the top of the card, bleeding to its edges. The
   illustrations are transparent and carry their own soft shape, so they
   sit on a wash rather than on the card's white, which stops them
   floating in the corner of a plain box. */
/* Every banner is the same box on every card, whatever shape the
   illustration inside it happens to be. The height is fixed rather
   than an aspect ratio, because a full-width card is twice as wide
   as a half-width one and a ratio would make its banner twice as
   tall. The artwork is fitted inside that box instead of filling it,
   so nothing is cropped and nothing is stretched. */
.svcbanner{margin:calc(clamp(24px,2.4vw,34px) * -1) calc(clamp(24px,2.4vw,34px) * -1) 4px;
  background:var(--accent-soft); border-radius:4px 4px 0 0; overflow:hidden;
  display:grid; place-items:center; padding:14px;
  height:clamp(150px,17vw,200px); box-sizing:border-box}
/* No blend mode: the illustrations are already transparent, and multiply
   was pulling the wash up through their whites and tinting them peach. */
.svcbanner img{max-width:min(340px,100%); max-height:100%; width:auto; height:auto;
  object-fit:contain}

/* The card already has a 3px brand top border; the banner sits under it. */
.svccard--img{padding-top:clamp(24px,2.4vw,34px)}

/* A full-width card is a two-column grid whose bullet list is pinned across
   the rows. Adding a banner pushes everything down one row, so the pinned
   list has to move with it or it sits on top of the banner. Both cases are
   stated explicitly rather than left to implicit placement. */
@media (min-width:861px){
  .svccard--wide .svcbanner{grid-column:1/-1; grid-row:1; margin-bottom:14px}
  .svccard--wide.svccard--img .svccard__top{grid-row:2}
  .svccard--wide.svccard--img p{grid-row:3}
  .svccard--wide.svccard--img ul{grid-column:2; grid-row:2/5}
}
@media (max-width:520px){.svcbanner img{max-width:min(260px,100%)}}

/* ── back to top ──────────────────────────────────────────────────
   Hidden until there is enough page behind you to want it, and out of
   the way of the cookie bar, which sits in the same corner. Sized to
   the 44px minimum touch target with room to spare. */
.totop{position:fixed; right:clamp(16px,2.5vw,28px); bottom:clamp(16px,2.5vw,28px);
  z-index:60; width:50px; height:50px; border-radius:50%;
  display:grid; place-items:center; background:var(--brand); color:var(--white);
  box-shadow:0 2px 6px rgba(17,17,17,.12), 0 14px 30px -18px rgba(17,17,17,.7);
  opacity:0; visibility:hidden; transform:translateY(10px);
  transition:opacity .25s ease, transform .25s ease, visibility .25s, background-color .2s ease}
.totop.on{opacity:1; visibility:visible; transform:none}
.totop:hover{background:var(--deep)}
.totop svg{width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:2.2;
  stroke-linecap:round; stroke-linejoin:round}
.totop:focus-visible{outline:3px solid var(--deep); outline-offset:3px}
/* The cookie bar owns the bottom of the screen until it is answered. */
body:has(.ck:not([hidden])) .totop{bottom:clamp(96px,14vh,150px)}
@media (prefers-reduced-motion: reduce){
  .totop{transition:opacity .25s ease, visibility .25s; transform:none}
}
@media print{.totop{display:none}}

/* ── fact find ─────────────────────────────────────────────────────
   One form, four fieldsets. Without JavaScript every step is simply
   on the page and the form still submits in one go, so the stepping
   below is entirely additive: .ff--stepped is set by the script. */
.ff{max-width:none; gap:0}
.ffstep{display:grid; gap:22px; padding-block:clamp(26px,3vw,38px); border-top:1px solid var(--line)}
.ffstep:first-of-type{border-top:0; padding-top:0}
.ffstep__h{font-size:clamp(1.5rem,2.6vw,2rem); letter-spacing:-.02em}
.ffstep__n{color:var(--grey); font-size:17.5px; line-height:1.5; max-width:52ch; margin-top:-12px}

.ff--stepped .ffstep{display:none; border-top:0; padding-top:0}
.ff--stepped .ffstep.is-on{display:grid}

/* The navigation row only exists when stepping does. */
.ffnav{display:none; justify-content:space-between; align-items:center; gap:14px; flex-wrap:wrap; margin-top:8px}
.ff--stepped .ffnav{display:flex}

/* Progress. A list, because that is what it is: four named stages in
   order, with the current one marked. */
.stepper{display:flex; flex-wrap:wrap; gap:8px; list-style:none; padding:0;
  margin:0 0 clamp(26px,3vw,38px); counter-reset:none}
.stepper li{display:flex; align-items:center; gap:9px; padding:8px 16px 8px 9px;
  border:1px solid var(--line); border-radius:999px; background:var(--white);
  font-size:15.5px; font-weight:600; color:var(--grey-2)}
.stepper li span{width:24px; height:24px; border-radius:50%; display:grid; place-items:center;
  background:var(--off); color:var(--grey-2); font-size:13px; font-variant-numeric:tabular-nums}
.stepper li.is-done{color:var(--grey)}
.stepper li.is-done span{background:var(--accent-soft); color:var(--accent)}
.stepper li[aria-current]{border-color:var(--brand); color:var(--heading); background:var(--white)}
.stepper li[aria-current] span{background:var(--brand); color:var(--white)}

/* Checkbox and radio sets. Both are the same object with a different
   control in it, so they share their sizing and only the marker differs. */
.checkset,.pillset{border:0; padding:0; margin:0; display:grid; gap:12px}
.checkset legend,.pillset legend{padding:0; font-weight:600; font-size:17.5px; color:var(--heading)}
.checkset__note{font-size:16px; color:var(--grey-2); margin-top:-4px}
.checkgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:8px}
.pillrow{display:flex; flex-wrap:wrap; gap:8px}

.checkopt,.pillopt{display:flex; align-items:center; gap:11px; cursor:pointer;
  border:1px solid var(--line); background:var(--white); border-radius:4px;
  padding:12px 15px; font-size:16.5px; color:var(--ink); line-height:1.3;
  transition:border-color .15s ease, background-color .15s ease}
.pillopt{border-radius:999px; padding:10px 18px}
.checkopt:hover,.pillopt:hover{border-color:var(--grey-2)}
.checkopt input,.pillopt input{width:19px; height:19px; accent-color:var(--accent); flex:0 0 auto; margin:0}
.pillopt input{width:16px; height:16px}
/* :has, so the whole tile reacts rather than only the box inside it.
   Browsers without it lose the tint and keep a perfectly usable control. */
.checkopt:has(input:checked),.pillopt:has(input:checked){border-color:var(--brand); background:var(--accent-soft)}
.checkopt:has(input:focus-visible),.pillopt:has(input:focus-visible){outline:3px solid var(--accent); outline-offset:2px}

@media (max-width:620px){
  .checkgrid{grid-template-columns:1fr}
  .stepper li{font-size:14.5px; padding:6px 13px 6px 7px}
  .ffnav{flex-direction:column-reverse; align-items:stretch}
  .ffnav .btn{justify-content:center}
}

/* A landscape illustration in a split column. The portrait rule caps the
   width at 400px, which on a wide drawing leaves it small and marooned in
   the middle of its half; this lets it use the column it is given. */
.split__art--wide img{max-width:min(560px,100%)}

/* Body copy in two columns across the full measure.

   Safe only while the whole block fits in roughly one screen: past that the
   reader has to scroll to the foot of the first column and back up to the
   head of the second, which is worse than a single column of any length. So
   it collapses to one column on anything narrow, and the block it is used on
   should stay short. Paragraphs never split across the gap, which leaves the
   columns ending at different heights: a ragged bottom edge is a far smaller
   price than a sentence broken in half by a column break. */
.body--cols{
  max-width:none; margin-top:clamp(26px,3vw,38px);
  columns:2; column-gap:clamp(34px,4vw,70px);
}
.body--cols p{break-inside:avoid; margin:0 0 1.05em}
.body--cols p + p{margin-top:0}
.body--cols p:last-child{margin-bottom:0}
@media (max-width:900px){.body--cols{columns:1}}

/* An illustration that comes with its own background, rather than a cutout
   sitting on the page. The frame is what stops it reading as a stray panel. */
.framed img{
  width:100%; border-radius:14px;
  box-shadow:0 2px 6px rgba(17,17,17,.07), 0 24px 50px -30px rgba(17,17,17,.6);
}
/* ── project gallery ──────────────────────────────────────────────────
   Every picture sits in a window of one shape. That is what lets the
   gallery step between a full page screen grab and a photograph of a
   touchscreen without the page jumping under the reader's thumb. */
/* Shown whole by default, which is what a browser with the script blocked
   gets: long, but complete. The script caps it and adds the button. */
.shotwin{
  position:relative; overflow:hidden; border-radius:14px;
  background:var(--off);
  box-shadow:0 2px 6px rgba(17,17,17,.07), 0 24px 50px -30px rgba(17,17,17,.6);
}
.framed .shotwin img{
  display:block; width:100%; height:auto; border-radius:0; box-shadow:none;
}

/* Wider than the screen it is being read on.
   A desktop screenshot is landscape. Fitted to a 390px phone it comes out
   about 214 tall, which shows that a screenshot exists without showing what
   is in it. So on a narrow screen the picture is rendered at a width its own
   interface is legible at and the frame becomes something to swipe across,
   with a fade on the right to say there is more.
   Above the breakpoint this does nothing at all: the rail is the full width
   and there is nothing to scroll. */
.shotpan{position:relative}
.shotpan__rail{overflow-x:auto; overflow-y:hidden; border-radius:14px; -webkit-overflow-scrolling:touch}
@media (max-width:700px){
  .shotpan__rail{scrollbar-width:thin}
  .framed .shotpan__rail img{width:900px; max-width:none; border-radius:14px}
  /* The fade sits over the right edge of the frame, and goes as soon as the
     reader has swiped, because by then they know. */
  .shotpan::after{
    content:""; position:absolute; top:0; right:0; bottom:0; width:56px; z-index:2;
    pointer-events:none; border-radius:0 14px 14px 0;
    background:linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,.92));
  }
  .shotpan__hint{
    position:absolute; right:12px; bottom:12px; z-index:3; pointer-events:none;
    font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
    color:#fff; background:rgba(17,20,24,.72); border-radius:999px; padding:5px 12px;
    transition:opacity .25s;
  }
  .shotpan.is-panned::after,
  .shotpan.is-panned .shotpan__hint{opacity:0}
}
@media (min-width:701px){.shotpan__hint{display:none}}

/* Taller than anybody wants on one page. The foot fades rather than being
   cut, because a hard edge reads as the end of the picture and a fade reads
   as more of it, and one button lets the rest out. --cap is set by the
   script from the height of the window it is being read in. */
.shotwin--cap{max-height:var(--cap,620px)}
.shotwin--cap::after{
  content:''; position:absolute; left:0; right:0; bottom:0;
  height:min(30%, 180px); pointer-events:none;
  background:linear-gradient(to bottom, rgba(255,255,255,0), var(--white) 88%);
}
.sec--off .shotwin--cap::after{background:linear-gradient(to bottom, rgba(250,247,243,0), var(--off) 88%)}
.shotmore{display:flex; justify-content:center; margin-top:-24px; position:relative; z-index:2}
.shotmore__btn{
  font:inherit; font-size:15px; font-weight:600; cursor:pointer;
  padding:11px 22px; border-radius:999px;
  border:1px solid var(--line); background:var(--white); color:var(--ink);
  box-shadow:0 10px 26px -12px rgba(17,17,17,.45);
  transition:border-color .18s, transform .18s;
}
.shotmore__btn:hover{border-color:var(--ink); transform:translateY(-1px)}

.gal{margin-top:clamp(34px,5vw,56px)}
/* A figure carries the browser's own 40px side margins, which on a phone is
   a quarter of the picture given away. */
.gal__slide{margin:0}
/* Without the script every slide is simply on the page in turn. */
.gal__slide + .gal__slide{margin-top:clamp(26px,4vw,44px)}
.gal.is-slider .gal__slide + .gal__slide{margin-top:0}
.gal.is-slider .gal__slide[hidden]{display:none}
.gal__bar{
  display:flex; align-items:center; justify-content:space-between;
  gap:20px; margin-top:20px; flex-wrap:wrap;
}
/* Thumbnails, not dots. A dot says there is another picture; a thumbnail
   says which one, which is what somebody deciding whether to click wants
   to know. */
.gal__thumbs{display:flex; gap:10px; flex-wrap:wrap}
.gal__thumb{
  width:78px; height:52px; padding:0; overflow:hidden; cursor:pointer;
  border:1px solid var(--line); border-radius:8px; background:var(--off);
  opacity:.55; transition:opacity .18s, border-color .18s, box-shadow .18s;
}
.gal__thumb img{width:100%; height:100%; object-fit:cover; object-position:top center; display:block}
.gal__thumb:hover{opacity:.85; border-color:var(--grey-2)}
.gal__thumb[aria-current="true"]{
  opacity:1; border-color:var(--ink);
  box-shadow:0 0 0 1px var(--ink);
}
.gal__step{display:flex; align-items:center; gap:6px; margin-left:auto}
.gal__btn{
  width:36px; height:36px; flex:0 0 auto; display:grid; place-items:center;
  border:1px solid var(--line); border-radius:999px; background:var(--white);
  color:var(--ink); cursor:pointer; line-height:0;
  transition:border-color .18s, background .18s, opacity .18s;
}
.gal__btn svg{width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round}
.gal__btn:hover{border-color:var(--ink)}
.gal__btn[disabled]{opacity:.3; cursor:default}
.gal__btn[disabled]:hover{border-color:var(--line)}
.gal__count{
  font-size:15px; color:var(--grey); font-variant-numeric:tabular-nums;
  min-width:62px; text-align:center;
}
@media (max-width:560px){
  .gal__thumb{width:58px; height:40px}
  .gal__step{margin-left:0}
}

/* ── project page pictures ──────────────────────────────────────────
   Every picture in one slider under the title, the big one beside a rail of
   thumbnails. The rail rather than a row of dots because the whole set is
   then visible at once, and down the left rather than under the picture
   because it costs 112px of width instead of a whole row of height. */
/* The project lede: the summary on the left, what it was built with on the
   right, separated by a rule. The hero was two thirds empty on its right
   with the chips sitting under the paragraph like a footnote. */
.prjlede{
  display:grid; grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(28px,4vw,64px); align-items:start;
}
.prjlede .hero__sub{max-width:34em}
.prjtech{margin-top:38px; padding-left:clamp(20px,2.4vw,34px); border-left:1px solid var(--line)}
.prjtech__lab{
  display:block; font-size:14px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--grey-2); margin-bottom:16px;
}
.prjtech .chips{gap:10px}
/* Below this the two columns are both too narrow, so the technologies go
   back under the paragraph, still labelled, with the rule above instead of
   beside. */
@media (max-width:820px){
  .prjlede{grid-template-columns:minmax(0,1fr); gap:0}
  .prjtech{
    margin-top:30px; padding-left:0; border-left:0;
    padding-top:26px; border-top:1px solid var(--line);
  }
}

.prjgal{margin-top:clamp(34px,5vw,56px)}
.prjgal .gal{margin-top:0}
/* The grid arrangement, kept because it is one class away: swap data-gal on
   the gallery for gal--grid and every picture is on the page at once instead
   of one at a time. The rail is what the project page uses. */
.gal--grid{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(20px,2.4vw,34px); align-items:start;
}
/* A grid item will not shrink below its content unless told to, and the pan
   window's rail is deliberately wider than the screen. */
.gal--grid .gal__slide{margin:0; min-width:0}
.gal--grid .gal__slide:first-child,
.gal--grid .gal__slide:last-child:nth-child(even){grid-column:1 / -1}
.gal--grid .gal__slide:not(:first-child) .galcap{font-size:15.5px; margin-top:10px}
@media (max-width:700px){
  .gal--grid{grid-template-columns:minmax(0,1fr)}
  .gal--grid .gal__slide{grid-column:auto}
}
/* No measure on this one. It runs under a picture that is already as wide
   as the page allows, and a 56ch limit was breaking a one line caption onto
   a second line with a single word on it. */
.galcap{margin-top:14px; font-size:16.5px; line-height:1.5; color:var(--grey)}
/* is-slider is added by the script. Without it the pictures are simply on
   the page one under the next and none of this applies. */
.prjgal .gal.is-slider{
  display:grid; grid-template-columns:112px minmax(0,1fr);
  gap:clamp(18px,2vw,28px); align-items:start;
}
.prjgal .gal.is-slider .gal__slide{grid-column:2; grid-row:1; margin:0}
.prjgal .gal.is-slider .gal__bar{
  grid-column:1; grid-row:1; flex-direction:column; align-items:stretch;
  margin:0; gap:14px;
}
.prjgal .gal.is-slider .gal__thumbs{flex-direction:column; flex-wrap:nowrap; gap:10px}
.prjgal .gal.is-slider .gal__thumb{width:112px; height:74px}
.prjgal .gal.is-slider .gal__step{
  justify-content:space-between; margin-left:0;
  padding-top:12px; border-top:1px solid var(--line);
}
.prjgal .gal.is-slider .gal__count{min-width:0; font-size:14px; white-space:nowrap}
@media (max-width:760px){
  .prjgal .gal.is-slider{display:block}
  .prjgal .gal.is-slider .gal__bar{flex-direction:row; align-items:center; margin-top:18px}
  .prjgal .gal.is-slider .gal__thumbs{flex-direction:row; flex-wrap:wrap}
  .prjgal .gal.is-slider .gal__thumb{width:78px; height:52px}
  .prjgal .gal.is-slider .gal__step{margin-left:auto; padding-top:0; border-top:0}
}
@media (max-width:560px){
  .prjgal .gal.is-slider .gal__thumb{width:58px; height:40px}
  .prjgal .gal.is-slider .gal__step{margin-left:0}
}

/* art() wraps an illustration in <picture> so the browser can take the WebP.
   A <picture> is an inline box, which quietly broke every rule written for
   the image itself: the service banners size their artwork with
   max-height:100%, and that started resolving against the picture's own
   auto height rather than the fixed-height banner, so the cards went back to
   varying in height. display:contents removes the picture from the box tree
   entirely, leaving the img as the direct child it was before. The picture
   carries no semantics of its own, so nothing is lost by doing this. */
picture{display:contents}

/* ── rotating last word of a heading ──────────────────────────────
   Only ever styled once the script has taken it over: with JavaScript
   off this is an ordinary word in an ordinary heading, and a caret
   blinking beside a word that never changes would be a lie. */
.rot--live{position:relative; white-space:nowrap}
.rot--live::after{
  content:""; display:inline-block; width:.055em; height:.82em;
  margin-left:.05em; vertical-align:-.06em; background:currentColor;
  animation:caret 1.05s steps(1) infinite;
}
@keyframes caret{0%,45%{opacity:1} 46%,100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.rot--live::after{display:none}}

/* The work delivered under a service, at the foot of its card. Names rather
   than cards: this is a footnote proving the service is real, not a second
   projects page inside the services page. */
.svcwork{margin-top:22px; padding-top:18px; border-top:1px solid var(--line)}
.svcwork__lab{
  display:block; font-size:13.5px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--grey-2); margin-bottom:10px;
}
/* One per line. The card is about 330px wide and client names run from
   "Avoke" to "Gordons Partnership LLP", so a wrapping row left one name on
   the first line and two on the second whatever the order. */
.svcwork__list{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:7px; font-size:16.5px;
}
.svcwork__list a{color:var(--brand-deep); text-decoration:none; border-bottom:1px solid transparent}
.svcwork__list a:hover{border-bottom-color:currentColor}

/* ══ home page composition ══════════════════════════════════════════
   The bands used to be five containers of the same width with the same
   padding and a heading in the same place, separated only by the ground
   alternating white and cream. These give them different jobs, different
   heights and, in two cases, an edge to run off. */

/* The hero sits on cream so the proof strip under it can be white: the
   change of ground is what separates them, with no rule and no gap. */
.hero--band{background:var(--off)}

/* The illustration stands on the foot of the band rather than floating in
   the middle of a column. A cut-out drawing has no hard edge to crop, so
   bleeding it off the side does nothing; standing it on the ground does. */
@media (min-width:901px){
  /* Centred against the words rather than stood on the floor of the band.
     Anchoring it to the bottom was right when the headline was three lines;
     at five it left the top two thirds of the right-hand column empty and
     pushed the drawing down beside the route cards, which is the least
     interesting thing for it to be next to. Centred it sits with the
     headline, and the nudge keeps it low enough to still feel grounded.
     A transform rather than a margin, so the nudge cannot affect layout. */
  .hero--floor .hero--split{align-items:center}
  .hero--floor .hero__art{transform:translateY(5%)}
  .hero--floor .hero__words{padding-bottom:0}
}
/* Bigger, and therefore higher: the feet stay on the floor of the band, so
   every pixel the drawing gains it gains upward. The column it sits in is
   widened a little and the drawing is allowed past the right-hand gutter,
   which costs the headline almost nothing and is where the artwork has its
   empty sky anyway. */
.hero--floor .hero__art{
  max-width:640px; margin-inline-start:auto;
  margin-right:calc(var(--gutter) * -0.55);
}
@media (min-width:901px){
  .hero--floor .hero--split{grid-template-columns:minmax(0,1.14fr) minmax(0,.86fr)}
}
/* The shared rule caps every hero illustration at 340px, which is right for
   one standing in the middle of a column and far too small for one standing
   on the floor of the band. */
.hero--floor .hero__art img{max-width:none; width:100%}
@media (max-width:900px){.hero--floor .hero__art{max-width:320px; margin-inline:auto}}
/* The labels are drawn on the artwork's own grid, so the wrapper has to be
   exactly as wide as the picture or the arrows drift off their subjects. */
.hero--floor .annot{max-width:none}

/* Short, wide, and the first thing after the hero. */
.sec--strip{padding-block:clamp(30px,3.6vw,52px)}
.strip__head{display:flex; flex-wrap:wrap; gap:8px 22px; align-items:baseline;
  justify-content:space-between}
.strip__head .eyebrow{margin:0}
.strip__head .tlink{font-size:16px}

/* The work: a heading with room to breathe, then the bands, then one way on. */
.workhead{max-width:52ch}
.workhead p{margin-top:14px; color:var(--grey); font-size:18px; line-height:1.55}
.workfoot{margin-top:clamp(30px,4vw,52px)}
/* A picture that runs past the right-hand gutter is meant to be cut off by
   the screen, not to widen the document. clip rather than hidden, because
   hidden makes the section a scroll container and that is what breaks the
   sticky column on the case study and contact pages. */
.sec:has(> .proj--band){overflow-x:clip}
.hero--band{overflow-x:clip}

/* The story: the picture on the outside edge, the words on the measure. */
.split--story{grid-template-columns:minmax(0,.86fr) minmax(0,1.14fr); align-items:center}
@media (min-width:861px){
  .split--story .storyart{margin-left:calc(var(--gutter) * -1)}
}
@media (max-width:860px){
  .split--story{grid-template-columns:1fr}
  .split--story .storyart{order:-1}
}
.storyart img{width:100%; height:auto; display:block; border-radius:10px}

/* ════════════════════════════════════════════════════════════════════
   Home, second composition: the work shown running.
   ════════════════════════════════════════════════════════════════════ */

/* ── inside the work: one window, a tab per system ────────────────────
   The frame is drawn rather than photographed, so it stays sharp at any
   width and costs nothing to download. The traffic lights are a generic
   window, not anybody's product. */
.ins__head{display:flex; justify-content:space-between; align-items:flex-end; gap:24px;
  margin-bottom:clamp(24px,3vw,36px)}
.ins__head h2{margin-top:8px}
.ins__intro{color:var(--grey); max-width:42ch; font-size:17px; text-wrap:pretty}
@media (max-width:760px){.ins__head{flex-direction:column; align-items:flex-start}}

.ins-win{border-radius:14px; background:var(--charcoal); overflow:hidden;
  box-shadow:0 40px 80px -50px rgba(36,26,18,.7), 0 2px 0 rgba(0,0,0,.04)}
.ins-win__chrome{display:flex; align-items:center; gap:14px; padding:12px 16px 0}
.ins-win__dots{display:flex; gap:7px; flex:0 0 auto}
.ins-win__dots i{width:11px; height:11px; border-radius:50%; display:block; background:#E35A4F}
.ins-win__dots i:nth-child(2){background:#E2B13C}
.ins-win__dots i:nth-child(3){background:#5DB85A}
.ins-win__tabs{display:flex; gap:4px; overflow-x:auto; scrollbar-width:none; min-width:0}
.ins-win__tabs::-webkit-scrollbar{display:none}

.ins-tab{appearance:none; border:0; background:transparent; color:rgba(255,255,255,.62);
  font:600 15px var(--f); padding:10px 16px 12px; border-radius:9px 9px 0 0; cursor:pointer;
  white-space:nowrap; position:relative; transition:color .2s}
.ins-tab:hover{color:#fff}
.ins-tab[aria-selected="true"]{background:#F2EEE8; color:var(--heading)}
.ins-tab:focus-visible{outline:2px solid var(--brand); outline-offset:-3px}
/* The bar under the open tab fills over the six seconds before the next
   one opens, so a moving window never changes without warning. It only
   runs while the window is advancing on its own. */
.ins-tab__bar{position:absolute; left:14px; right:14px; bottom:4px; height:2px; border-radius:2px;
  background:var(--brand); transform:scaleX(0); transform-origin:left}
.ins-win.is-auto .ins-tab[aria-selected="true"] .ins-tab__bar{animation:ins-fill 6s linear forwards}
.ins-win.is-paused .ins-tab__bar{animation-play-state:paused}
@keyframes ins-fill{to{transform:scaleX(1)}}

/* The inside of the window: a warm grey a step down from the page, so
   the white screenshot reads as a screen inside a frame. */
.ins-win__view{background:#F2EEE8; padding:12px}
.ins-pane{animation:ins-in .45s ease}
.ins-pane[hidden]{display:none}
@keyframes ins-in{from{opacity:0}to{opacity:1}}
.ins-url{display:flex; align-items:center; gap:8px; margin:0 0 12px; background:var(--white);
  border-radius:999px; padding:7px 16px; font-size:14px; color:var(--grey)}
.ins-url svg{width:13px; height:13px; flex:0 0 auto; fill:none; stroke:currentColor;
  stroke-width:2.2; stroke-linecap:round}
.ins-url span{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.ins-screen{aspect-ratio:16/9; border-radius:8px; overflow:hidden; background:var(--white)}
.ins-screen img{width:100%; height:100%; object-fit:cover; object-position:top center}
.ins-cap{display:grid; grid-template-columns:minmax(0,1fr) auto; gap:6px 28px;
  padding:20px 6px 8px; align-items:end}
.ins-cap .eyebrow{color:var(--brand-deep)}
.ins-cap h3{font-size:clamp(1.3rem,2vw,1.7rem); margin-top:4px}
.ins-cap p{grid-column:1; color:var(--grey); font-size:16.5px; max-width:70ch; text-wrap:pretty}
.ins-cap .tlink{grid-column:2; grid-row:1/3; align-self:end; white-space:nowrap}
@media (max-width:760px){
  .ins-cap{grid-template-columns:1fr}
  .ins-cap .tlink{grid-column:1; grid-row:auto}
  .ins-win__dots{display:none}
}

/* The client strip straight after the window shares its white, so the
   two read as one statement rather than two bands with a seam. */
.sec--joined{padding-top:0}

/* ── the work: a list, and the work beside it ─────────────────────── */
.wl{display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  gap:clamp(28px,4vw,64px); margin-top:clamp(28px,3vw,44px); align-items:start}
.wl__list{border-top:1px solid var(--line)}
.wl__row{display:block; position:relative; padding:20px 8px 20px 0; text-decoration:none;
  border-bottom:1px solid var(--line); transition:padding .3s ease}
.wl__row small{display:block; font-size:13px; font-weight:700; letter-spacing:.12em;
  text-transform:uppercase; color:var(--grey-2)}
.wl__row > span{display:block; margin-top:4px; font-size:clamp(19px,1.7vw,23px); font-weight:700;
  letter-spacing:-.01em; line-height:1.25; color:var(--heading); text-wrap:balance; transition:color .2s}
.wl__row::before{content:""; position:absolute; left:0; top:50%; width:7px; height:7px;
  border-radius:50%; background:var(--brand); transform:translateY(-50%) scale(0); transition:transform .25s}
.wl__row.on{padding-left:20px}
.wl__row.on::before{transform:translateY(-50%) scale(1)}
.wl__row.on > span, .wl__row:hover > span{color:var(--brand-deep)}
.wl__row:focus-visible{outline:2px solid var(--brand); outline-offset:2px}

/* Sticky, so the picture stays beside the row being read however far
   down the list the reader is. Clear of the floating header. */
.wl__preview{position:sticky; top:clamp(96px,10vw,120px)}
.wl__frame{position:relative; aspect-ratio:16/9; border-radius:12px; overflow:hidden;
  background:var(--white); box-shadow:0 34px 64px -42px rgba(36,26,18,.6)}
.wl__frame img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  object-position:top center; opacity:0; transition:opacity .45s ease}
.wl__frame img.on{opacity:1}
.wl__note{display:none; margin-top:18px; color:var(--grey); font-size:17px; text-wrap:pretty}
.wl__note.on{display:block}
/* On a phone there is no pointer to hover with, so the preview would sit
   on the first project for ever. The list stands on its own instead. */
@media (max-width:900px){
  .wl{grid-template-columns:1fr}
  .wl__preview{display:none}
  .wl__row.on{padding-left:0}
  .wl__row.on::before{transform:translateY(-50%) scale(0)}
  .wl__row.on > span{color:var(--heading)}
}

/* ── services: four stages, one at a time ─────────────────────────── */
.stg{margin-top:clamp(28px,3vw,40px)}
.stg__list{display:none}
.stg__panes{display:grid; gap:16px}
/* Only a tab set once the script has made it one. Without it, the stage
   buttons would do nothing, so they are not drawn and every stage stacks. */
.stg.is-tabs{display:grid; grid-template-columns:minmax(0,.75fr) minmax(0,1.25fr);
  gap:clamp(24px,4vw,64px); align-items:start}
.stg.is-tabs .stg__list{display:grid; gap:10px}
.stg__tab{appearance:none; text-align:left; font-family:var(--f); cursor:pointer;
  display:grid; grid-template-columns:minmax(0,1fr) auto; column-gap:12px;
  border:1px solid var(--line); background:var(--off); border-radius:12px; padding:18px 20px;
  transition:border-color .25s, background .25s, transform .25s, box-shadow .25s}
.stg__tab b{font-size:22px; color:var(--heading); letter-spacing:-.015em; line-height:1.2}
.stg__tab span{grid-column:1; color:var(--grey); font-size:15px; margin-top:2px; line-height:1.4}
.stg__tab em{grid-column:2; grid-row:1; font-style:normal; font-size:13px; font-weight:700;
  color:var(--grey-2); margin-top:6px}
.stg__tab:hover{transform:translateX(3px)}
.stg__tab[aria-selected="true"]{background:var(--white); border-color:var(--brand);
  box-shadow:inset 4px 0 0 var(--brand)}
.stg__tab:focus-visible{outline:2px solid var(--brand); outline-offset:2px}

.stg__pane{display:flow-root; border:1px solid var(--line); border-radius:14px;
  background:var(--off); padding:clamp(22px,2.6vw,34px); animation:ins-in .4s ease}
.stg__pane[hidden]{display:none}
.stg__art{float:right; width:min(40%,230px); margin:-6px -6px 10px 20px}
.stg__row{padding:16px 0; border-top:1px solid var(--line)}
/* The first row, whether or not the stage has a picture in front of it:
   :first-of-type alone counts the picture's wrapper, which is also a div,
   and drew a rule above the first service of every illustrated stage. */
.stg__row:first-of-type, .stg__art + .stg__row{border-top:0; padding-top:4px}
.stg__row h3{font-size:20px}
.stg__row p{color:var(--grey); font-size:16.5px; margin-top:4px; max-width:58ch; text-wrap:pretty}
.stg__pane .tlink{margin-top:14px}
@media (max-width:900px){
  .stg.is-tabs{grid-template-columns:1fr}
  .stg.is-tabs .stg__list{grid-template-columns:repeat(2,minmax(0,1fr))}
  .stg__tab b{font-size:19px}
  .stg__art{display:none}
}
@media (max-width:420px){.stg.is-tabs .stg__list{grid-template-columns:1fr}}

@media (prefers-reduced-motion:reduce){
  .ins-pane, .stg__pane{animation:none}
  .wl__row, .wl__frame img, .stg__tab{transition:none}
}

/* ════════════════════════════════════════════════════════════════════
   Colour schemes. The tokens come from Palette::css() in the head; these
   are the few places that carried a warm colour of their own.
   ════════════════════════════════════════════════════════════════════ */
:is([data-scheme="violet"],[data-scheme="dark"]) .sec--dark .body,
:is([data-scheme="violet"],[data-scheme="dark"]) .sec--dark .lede{color:#CEC8D8}
:is([data-scheme="violet"],[data-scheme="dark"]) .sec--dark .eyebrow{color:#958FA3}
:is([data-scheme="violet"],[data-scheme="dark"]) .ins-win__view,
:is([data-scheme="violet"],[data-scheme="dark"]) .ins-tab[aria-selected="true"]{background:#EEEBF4}

/* ── Darker: the page opens and closes on deep violet-black ───────────
   Two faint glows, one from each end of the logo, so the dark has depth
   rather than being a flat fill. Every text colour here was checked
   against #120E18: body 12:1, the orange accents 9:1, small print 7:1. */
[data-scheme="dark"] .hero.sec--dark{background:
  radial-gradient(1100px 620px at 82% 38%, rgba(109,10,182,.42), transparent 62%),
  radial-gradient(900px 520px at 6% 100%, rgba(238,129,27,.16), transparent 62%),
  #120E18}
[data-scheme="dark"] .hero.sec--dark .hero__sub{color:#CFC9D8}
[data-scheme="dark"] .hero.sec--dark .hello,
[data-scheme="dark"] .hero.sec--dark .eyebrow{color:#F3A35A}
[data-scheme="dark"] .hero.sec--dark .trust,
[data-scheme="dark"] .hero.sec--dark .fnote:not(.fnote--pop){color:#A39DB0}
[data-scheme="dark"] .hero.sec--dark .fnote:not(.fnote--pop) a{color:#F3A35A}
/* The footnote bubble is charcoal, which on a near-black hero would not
   read as a layer at all. A hairline gives it an edge. */
[data-scheme="dark"] .fnote--pop{box-shadow:0 0 0 1px rgba(255,255,255,.16), 0 20px 44px -18px rgba(0,0,0,.8)}
/* The footnote asterisk is brand-coloured, which on the dark is violet on
   near-black and effectively invisible. */
[data-scheme="dark"] .hero.sec--dark .arealine{color:#A39DB0}
[data-scheme="dark"] .hero.sec--dark .arealine__pin{color:#F3A35A; stroke:#F3A35A}
/* Some page illustrations are drawn on a white ground rather than cut out,
   so on the dark they arrive as a white rectangle. Rounded and lifted they
   read as a framed picture placed on the page, which is what they are.
   The artwork itself is untouched. */
[data-scheme="dark"] .hero.sec--dark .pagehero img{border-radius:16px;
  box-shadow:0 34px 70px -34px rgba(0,0,0,.75)}
[data-scheme="dark"] .hero.sec--dark .fnmark{color:#F3A35A}
[data-scheme="dark"] .hero.sec--dark .fnmark:hover{background:rgba(255,255,255,.12)}
[data-scheme="dark"] .hero.sec--dark .annot__ink{stroke:#F3A35A}
[data-scheme="dark"] .hero.sec--dark .annot__word{fill:#F3A35A}
[data-scheme="dark"] .hero.sec--dark .route{background:rgba(255,255,255,.06); border-color:rgba(255,255,255,.14)}
[data-scheme="dark"] .hero.sec--dark .route:hover{border-color:#F3A35A}
[data-scheme="dark"] .hero.sec--dark .route b{color:#fff}
[data-scheme="dark"] .hero.sec--dark .route span{color:#CFC9D8}
[data-scheme="dark"] .hero.sec--dark .route i{color:#F3A35A}
[data-scheme="dark"] .sec--dark.cta,
[data-scheme="dark"] .foot{background:#120E18}


/* ── my licences ──────────────────────────────────────────
   Signed out, the form sits in the hero beside the pitch.
   Signed in, one card per licence: artwork on the left, the
   key, the facts and the sites on the right. Same family as
   the plugin cards, a violet edge on the left.               */
.accthero{padding-bottom:clamp(34px,4vw,60px)}
.accthero__grid{display:grid; gap:clamp(26px,4vw,56px); align-items:center}
@media (min-width:900px){.accthero__grid{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr)}}
.accthero__grid--in{grid-template-columns:none; display:flex; justify-content:space-between;
  align-items:flex-end; flex-wrap:wrap}
.acct__out{margin:0 0 6px}
.acct__form{background:var(--white); border:1px solid var(--line); border-top:3px solid var(--brand);
  border-radius:6px; padding:clamp(22px,2.6vw,32px); display:flex; flex-direction:column; gap:14px;
  box-shadow:0 18px 40px -30px rgba(17,17,17,.35)}
.acct__form h2{margin:0; font-size:1.3rem}
.acct__form p{margin:0; color:var(--grey); font-size:15.5px; line-height:1.55}
.acct__form .field{margin:0}
.acct__notice{margin:0 0 clamp(20px,2.4vw,28px); padding:14px 18px; border-radius:6px; font-size:16px;
  line-height:1.5; border-left:4px solid var(--brand); background:var(--white); max-width:80ch}
.acct__notice--bad{border-left-color:#B4342A; background:#FCEDEA}

.accthelp{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(18px,3vw,40px)}
@media (max-width:860px){.accthelp{grid-template-columns:1fr}}
.accthelp--in{margin-top:clamp(36px,4vw,56px); padding-top:clamp(26px,3vw,36px); border-top:1px solid var(--line)}
.accthelp h3{margin:0 0 8px; font-size:1.05rem; letter-spacing:-.005em}
.accthelp p{margin:0; color:var(--grey); font-size:15.5px; line-height:1.55; text-wrap:pretty}
.acct__empty{background:var(--white); border:1px solid var(--line); border-radius:6px;
  padding:clamp(24px,3vw,40px); max-width:640px}
.acct__empty h2{margin:0 0 10px; font-size:1.4rem}
.acct__empty p{color:var(--grey); font-size:16.5px; line-height:1.55}
/* A grid: as many cards to a row as fit at a readable width, so two
   licences sit side by side and a long list runs three across. */
.acct__list{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr));
  gap:clamp(16px,2vw,24px); align-items:stretch}

.lic{background:var(--white); border:1px solid var(--line); border-left:3px solid var(--brand);
  border-radius:5px; overflow:hidden; display:flex; flex-direction:column}
.lic--off{border-left-color:#B4342A}

.lic__art{display:block; background:#F1F6FC; line-height:0; border-bottom:1px solid var(--line)}
.lic__art img{width:100%; height:auto; display:block}
.lic__body{padding:clamp(20px,2.2vw,26px); display:flex; flex-direction:column; gap:16px; min-width:0; flex:1 1 auto}
.lic__foot{margin-top:auto; padding-top:4px}
.lic__head{display:flex; justify-content:space-between; align-items:flex-start; gap:14px 24px; flex-wrap:wrap}
.lic__head h2{margin:0; font-size:1.25rem; letter-spacing:-.015em}
.lic__head h2 a{color:inherit; text-decoration:none}
.lic__head h2 a:hover{text-decoration:underline; text-underline-offset:4px}
.lic__meta{margin:8px 0 0; display:flex; flex-wrap:wrap; align-items:center; gap:6px 14px;
  color:var(--grey); font-size:14.5px}
.lic__pill{display:inline-block; padding:2px 10px; border-radius:999px; font-size:12px; font-weight:700;
  letter-spacing:.04em; text-transform:uppercase}
.lic__pill--ok{background:#E6F4EA; color:#1E7A3C}
.lic__pill--bad{background:#FCEDEA; color:#B4342A}
.lic__dl{padding:10px 20px; font-size:16px}
.lic__label{display:block; margin:0 0 6px; font-size:12px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--grey)}

.lic__facts{display:flex; flex-direction:column; gap:14px}
.lic__key{min-width:0}
.lic__keyrow{display:flex; align-items:stretch; border:1px solid var(--line); border-radius:6px;
  background:var(--off); overflow:hidden; max-width:100%}
.lic__keyrow code{padding:9px 12px; font-size:14px; letter-spacing:0; white-space:nowrap; flex:1 1 auto; overflow-x:auto;
  user-select:all}
.lic__copy{border:0; border-left:1px solid var(--line); background:var(--white); padding:0 14px;
  font:inherit; font-size:14px; font-weight:600; color:var(--brand-deep); cursor:pointer}
.lic__copy:hover{background:var(--off)}
.lic__copy:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
.lic__dl2{display:grid; grid-template-columns:repeat(3,auto); justify-content:space-between; gap:8px 18px; margin:0}
.lic__dl2 div{display:flex; flex-direction:column; gap:3px}
.lic__dl2 dt{font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--grey)}
.lic__dl2 dd{margin:0; font-weight:600; font-size:15.5px; font-variant-numeric:tabular-nums}
.lic__sub{font-weight:500; color:var(--grey); font-size:14px}
.lic__ended{color:#B4342A}

.lic__sites ul{list-style:none; margin:0; padding:0; border-top:1px solid var(--line)}
.lic__sites li{display:flex; flex-wrap:wrap; align-items:center; gap:6px 12px; padding:11px 0;
  border-bottom:1px solid var(--line)}
.lic__dot{width:8px; height:8px; border-radius:50%; background:#1E7A3C; flex:0 0 auto}
.lic__dot--stg{background:var(--grey)}
.lic__domain{font-weight:600; overflow-wrap:anywhere}
.lic__tag{font-size:11.5px; font-weight:700; padding:2px 8px; border-radius:999px; background:var(--off); color:var(--grey)}
.lic__when{color:var(--grey); font-size:14px; margin-right:auto}
.lic__release{margin:0}
.lic__releasebtn{background:none; border:1px solid var(--line); border-radius:999px; padding:5px 13px;
  font:inherit; font-size:14px; font-weight:600; color:var(--ink); cursor:pointer}
.lic__releasebtn:hover{border-color:#B4342A; color:#B4342A}
.lic__releasebtn:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.lic__none{margin:0; color:var(--grey); font-size:15px; line-height:1.5; padding:12px 14px;
  background:var(--off); border-radius:6px}
.thanks__steps{margin:0; padding-left:1.3em; color:var(--grey); font-size:16px; line-height:1.6}
/* On a phone the key is wider than the card, so it wraps rather than scrolls. */
@media (max-width:480px){.lic__keyrow code{white-space:normal; overflow-wrap:anywhere}}


/* ── home page: showing the whole of what WBJ does ────────────────────── */
/* A tighter hero: the headline no longer runs to four lines and the band
   no longer opens on a quarter of a screen of nothing. */
.hero{padding-top:clamp(140px,13vw,200px); padding-bottom:clamp(48px,6vw,88px)}
.hero--split h1{font-size:clamp(2.3rem,4.5vw,3.9rem)}
.hero h1{max-width:17ch}
.hero__sub{margin-top:26px}
.routes{margin-top:28px}

/* What I do: six tiles, each with the proof underneath. */
.does__grid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px;
  margin-top:clamp(26px,3vw,40px)}
.do{display:flex; flex-direction:column; gap:10px; padding:clamp(22px,2.2vw,30px);
  background:var(--white); border:1px solid var(--line); border-radius:14px; text-decoration:none;
  color:inherit; transition:border-color .2s, transform .2s, box-shadow .2s}
.do:hover{border-color:var(--brand); transform:translateY(-2px);
  box-shadow:0 22px 40px -30px rgba(36,26,18,.45)}
.do:focus-visible{outline:2px solid var(--brand); outline-offset:3px}
.do h3{font-size:clamp(1.25rem,1.8vw,1.5rem)}
.do p{margin:0; color:var(--grey); font-size:16.5px; line-height:1.5; text-wrap:pretty}
.do__proof{margin-top:auto; padding-top:14px; border-top:1px solid var(--line);
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  font-size:14.5px; font-weight:700; color:var(--brand-deep)}
.do__proof svg{width:15px; height:15px; flex:none}
@media (max-width:980px){.does__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:620px){.does__grid{grid-template-columns:1fr}}

/* Plugins on the home page. */
.homeplug__grid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:18px;
  margin-top:clamp(26px,3vw,40px)}
.hpl{display:flex; flex-direction:column; gap:8px; text-decoration:none; color:inherit;
  background:var(--white); border:1px solid var(--line); border-radius:14px; overflow:hidden;
  padding-bottom:clamp(18px,2vw,26px); transition:border-color .2s, transform .2s}
.hpl:hover{border-color:var(--brand); transform:translateY(-2px)}
.hpl:focus-visible{outline:2px solid var(--brand); outline-offset:3px}
.hpl__art{display:block; position:relative; padding-top:62.5%; background:var(--off);
  border-bottom:1px solid var(--line); margin-bottom:10px}
.hpl__art img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.hpl h3,.hpl p{padding-inline:clamp(18px,2vw,26px)}
.hpl h3{font-size:clamp(1.15rem,1.6vw,1.35rem)}
.hpl p{margin:0; color:var(--grey); font-size:16px; line-height:1.5; text-wrap:pretty}
@media (max-width:900px){.homeplug__grid{grid-template-columns:1fr}}

/* Closing band: the words on the left, the quickest ways to a person on
   the right, instead of half a band of nothing. */
.cta__split{display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,.7fr);
  gap:clamp(28px,5vw,80px); align-items:center}
.cta__card{border:1px solid rgba(255,255,255,.18); border-radius:16px;
  padding:clamp(24px,2.6vw,36px); display:flex; flex-direction:column; gap:4px;
  background:rgba(255,255,255,.04)}
.cta__k{margin:14px 0 0; font-size:13px; font-weight:700; letter-spacing:.12em;
  text-transform:uppercase; color:rgba(255,255,255,.55)}
.cta__k:first-child{margin-top:0}
.cta__tel{font-size:clamp(1.6rem,2.6vw,2.2rem); font-weight:800; letter-spacing:-.02em;
  color:var(--white); text-decoration:none}
.cta__mail{font-size:clamp(1.05rem,1.5vw,1.3rem); font-weight:700; color:var(--brand-lift);
  text-decoration:none; overflow-wrap:anywhere}
.cta__tel:hover,.cta__mail:hover{text-decoration:underline; text-underline-offset:4px}
.cta__town{margin:0; color:#C9C5C0; font-size:16.5px; line-height:1.45}
@media (max-width:900px){.cta__split{grid-template-columns:1fr}}

.do__proof svg{fill:none; stroke:currentColor; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round}
.does{padding-bottom:clamp(36px,4.4vw,60px)}
.does + .ins{padding-top:clamp(24px,3vw,40px)}

/* ── inside the work: a soft cross-fade between projects ─────────────── */
.ins-win.is-fade .ins-win__view{display:grid}
.ins-win.is-fade .ins-pane{grid-area:1/1; min-width:0; animation:none; opacity:0; visibility:hidden;
  transition:opacity .7s ease, visibility 0s linear .7s}
.ins-win.is-fade .ins-pane.is-on{opacity:1; visibility:visible; transition:opacity .7s ease, visibility 0s}
.ins-tab, .ins-tab[aria-selected="true"]::after{transition:color .4s ease, opacity .4s ease}
@media (prefers-reduced-motion:reduce){
  .ins-win.is-fade .ins-pane, .ins-win.is-fade .ins-pane.is-on{transition:none}
}

/* ── projects page: pictures first, no boxes ─────────────────────────── */
.pgrid{display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(36px,4vw,56px) clamp(22px,2.6vw,36px)}
.pj{display:flex; flex-direction:column; gap:18px; text-decoration:none; color:inherit; min-width:0}
.pj--lead{grid-column:1/-1; display:grid; grid-template-columns:minmax(0,2fr) minmax(0,1fr);
  align-items:center; gap:clamp(22px,3vw,48px)}
.pj__pic{display:block; position:relative; padding-top:62.5%; border-radius:16px; overflow:hidden;
  background:var(--off); box-shadow:0 30px 56px -40px rgba(36,26,18,.55), 0 1px 0 rgba(0,0,0,.04);
  transition:transform .35s cubic-bezier(.2,.8,.3,1), box-shadow .35s}
.pj--lead .pj__pic{padding-top:56.25%}
.pj:hover .pj__pic{transform:translateY(-5px); box-shadow:0 44px 70px -42px rgba(36,26,18,.6)}
.pj:focus-visible{outline:none}
.pj:focus-visible .pj__pic{outline:3px solid var(--brand); outline-offset:4px}
.pj__pic img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:top center}
.pj__blank{position:absolute; inset:0; display:grid; place-items:center; padding:24px; text-align:center;
  font-weight:800; font-size:clamp(1.2rem,2vw,1.6rem); color:var(--grey-2); letter-spacing:-.01em}
.pj__go{position:absolute; right:16px; bottom:16px; width:44px; height:44px; border-radius:50%;
  display:grid; place-items:center; background:var(--brand); color:#fff;
  box-shadow:0 10px 24px -10px rgba(0,0,0,.5); transform:translateY(8px); opacity:0;
  transition:transform .3s cubic-bezier(.2,.8,.3,1), opacity .3s}
.pj__go svg{width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round}
.pj:hover .pj__go, .pj:focus-visible .pj__go{transform:none; opacity:1}
@media (hover:none){.pj__go{transform:none; opacity:1}}
.pj__words{display:flex; flex-direction:column; gap:6px; padding-inline:4px}
.pj__words small{font-size:13px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; color:var(--brand-deep)}
.pj__words strong{font-size:clamp(1.3rem,1.9vw,1.6rem); font-weight:800; line-height:1.15; letter-spacing:-.02em;
  color:var(--heading); text-wrap:balance}
.pj--lead .pj__words strong{font-size:clamp(1.5rem,2.4vw,2.1rem)}
.pj--lead .pj__words{gap:10px}
.pj__sum{color:var(--grey); font-size:16.5px; line-height:1.5; text-wrap:pretty; max-width:56ch;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden}
.pj--lead .pj__sum{-webkit-line-clamp:4; font-size:17.5px}
@media (max-width:1000px){
  .pgrid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:760px){
  .pj--lead{display:flex; flex-direction:column; gap:18px}
  .pj--lead .pj__pic{padding-top:62.5%}
}
@media (max-width:640px){
  .pgrid{grid-template-columns:1fr; gap:36px}
}
@media (prefers-reduced-motion:reduce){.pj__pic, .pj__go{transition:none}}
/* The grid sits closer to the filter bar: a third of a screen of nothing
   between the two read as the page having ended. */
.filterbar + .zag + .sec, .filterbar + .sec{padding-top:clamp(36px,4.4vw,60px)}

/* ── anonymised screenshots ───────────────────────────────────────
   An asterisk after the title of a project whose screenshots have had
   their names and details changed, and the line of small print it points
   at, printed once under any list that holds such a project. */
.anon{color:var(--brand-deep); font-weight:700; font-size:.6em; line-height:0; vertical-align:super; margin-left:.12em}
.sec--dark .anon, .cin-card__ov .anon, .wl__row .anon{color:var(--brand-lift)}
.anonnote{margin-top:22px; font-size:14px; line-height:1.5; color:var(--grey-2); max-width:60ch}
.anonnote .anon{font-size:1em; vertical-align:baseline; line-height:1; margin:0 .2em 0 0}
.sec--dark .anonnote{color:#8E8983}

/* ── the footer strap ───────────────────────────────────────────────
   A label and one sentence across the top of the footer, over a rule,
   so the last thing on every page is how the business works. */
.foot__strap{display:flex; flex-wrap:wrap; align-items:baseline; gap:8px 22px; margin:0 0 clamp(36px,4.5vw,56px);
  padding-bottom:clamp(22px,3vw,32px); border-bottom:1px solid rgba(255,255,255,.14)}
.foot__straplabel{font-size:13px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--brand-lift)}
.foot__straptext{font-size:clamp(1.25rem,2vw,1.75rem); font-weight:700; letter-spacing:-.02em; color:var(--white); line-height:1.2}

/* One line of small print under the client logos and the projects list,
   saying the people named agreed to be. */
.permnote{margin-top:18px; font-size:13px; line-height:1.5; color:var(--grey-2)}
.anonnote + .permnote{margin-top:6px}

/* Numbered clauses on a legal page: the number hangs in a gutter so the
   text lines up, and sub-clauses step in once per level. */
.legal .clause{position:relative; padding-left:3.2em; margin:0}
.legal .clause .cn{position:absolute; left:0; top:0; width:2.8em; color:var(--ink); font-weight:700; font-variant-numeric:tabular-nums}
.legal .clause--a{margin-left:3.2em}
.legal .clause--i{margin-left:6.4em}
.legal .clause--a .cn,.legal .clause--i .cn{font-weight:600; color:var(--grey)}
.legal .clause + .clause{margin-top:-.3em}
.legal .legal__version{font-size:15px; color:var(--grey-2); margin:0}
@media (max-width:560px){.legal .clause{padding-left:2.6em}.legal .clause--a{margin-left:1.6em}.legal .clause--i{margin-left:3.2em}}
