
:root{
  --ink:#15161B; --paper:#F7F8FD; --line:#E1E4EB; --brand:#2562FF;
  /* Light theme. Surfaces step down from white through the two brand neutrals; no new
     hues enter the system. --s0-0 is --s0 at zero alpha -- needed because a gradient to
     the transparent keyword interpolates through transparent BLACK in some engines and
     greys the fade out. */
  --s0:#FFFFFF; --s0-0:rgba(255,255,255,0); --s1:#F7F8FD; --s2:#EDEFF7;
  --hair:#E1E4EB; --hair2:#ECEEF4;
  /* tx2/tx3 are the two muted greys. Both clear 4.5:1 on white -- #6B7080 is the floor,
     anything lighter fails body-text contrast. */
  --tx:#15161B; --tx2:#565B6B; --tx3:#6B7080;
  --shade:21,22,27;                       /* --ink as an rgb triple, for tinted shadows */
  --maxw:1280px; --gut:clamp(20px,5vw,56px); --r:26px;
  --ease:cubic-bezier(.32,.72,0,1); --ease-out:cubic-bezier(.16,1,.3,1);
  /* Where anything sticky parks. The nav sits in the flow now, so this is breathing room
     rather than clearance -- back when the nav was a fixed pill everything sticky had to
     be pushed below it, and the offset it left behind is what put a 72px window of
     scrolling card text above the old filter bar. */
  --stick:24px;
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%; scroll-behavior:smooth; scroll-padding-top:22px}
body{
  margin:0; background:var(--s0); color:var(--tx);
  font-family:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;
  font-size:16px; line-height:1.6; -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
/* height:auto is not optional. Every img carries width/height so the box is reserved
   before load; without it max-width shrinks the width while the height attribute holds. */
img{display:block; max-width:100%; height:auto}
figure{margin:0}                       /* UA default is margin:1em 40px */
a{color:inherit; text-decoration:none}
h1,h2,h3{margin:0; font-weight:800; letter-spacing:-.03em; line-height:1.04; text-wrap:balance}
p{margin:0; text-wrap:pretty}
dl,dd,dt,ul,li{margin:0; padding:0; list-style:none}
:focus-visible{outline:2px solid var(--brand); outline-offset:3px; border-radius:6px}
.wrap{max-width:var(--maxw); margin:0 auto; padding-inline:var(--gut)}
.num{font-variant-numeric:tabular-nums; font-feature-settings:'tnum' 1}

/* ---------- motion primitives ---------- */
.reveal{opacity:1}
.js .reveal{opacity:0; transform:translate3d(0,26px,0); filter:blur(6px)}
.js .reveal.in{
  opacity:1; transform:none; filter:blur(0);
  transition:opacity .9s var(--ease-out), transform .9s var(--ease-out), filter .9s var(--ease-out);
  transition-delay:calc(var(--i,0) * 55ms);
}
/* Reduced motion means *less* motion, not none. Killing every duration also kills the
   fades and colour changes, which carry no movement and are the thing that tells you a
   hover or a focus registered -- strip those and the interface reads as broken rather
   than as calm. So the duration is set per property instead of across the board: the
   moving ones collapse to .01ms, the still ones keep a normal 240ms.

   The moving properties stay *in* the list rather than being dropped from it. Dropping
   them would stop transitionend firing, and anything that waits on that event to know a
   move has finished would hang; .01ms fires it on the next frame. The two lists are
   positional -- eleven properties, eleven durations -- so they must be edited together. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important; animation-iteration-count:1!important;
    transition-property:opacity,color,background-color,border-color,box-shadow,fill,stroke,transform,filter,width,height!important;
    transition-duration:240ms,240ms,240ms,240ms,240ms,240ms,240ms,.01ms,.01ms,.01ms,.01ms!important;
    scroll-behavior:auto!important}
  .js .reveal{opacity:1; transform:none; filter:none}
}

/* ---------- chrome ---------- */
.skip{position:absolute; left:-9999px}
.skip:focus{left:var(--gut); top:12px; z-index:60; background:var(--brand); color:#fff; padding:10px 16px; border-radius:999px}
/* A plain full-width bar carrying the real logo, in the flow of the page rather than
   floating over it. The earlier version was a detached glass pill fixed to the viewport,
   which read as a foreign widget sitting on the design -- and it fought the filter bar
   further down the page, which is sticky because it has to be. One sticky thing per page. */
.nav{border-bottom:1px solid var(--hair); background:var(--s0)}
.nav-in{
  max-width:var(--maxw); margin:0 auto; padding:16px var(--gut);
  display:flex; align-items:center; gap:18px;
}
.nav-logo{display:block; flex:none}
.nav-logo img{display:block; width:auto; height:30px}
@media (max-width:520px){ .nav-logo img{height:25px} }
.nav-links{display:flex; gap:22px; margin-left:auto; font-size:13.5px; color:var(--tx2); font-weight:600}
.nav-links a{transition:color .3s var(--ease)}
.nav-links a:hover{color:var(--tx)}
.btn{
  display:inline-flex; align-items:center; gap:10px; padding:9px 9px 9px 20px;
  background:var(--brand); color:#fff; border-radius:999px; font-weight:700; font-size:14px;
  border:0; cursor:pointer; white-space:nowrap;
  transition:transform .5s var(--ease), box-shadow .5s var(--ease), filter .3s var(--ease);
  box-shadow:0 10px 30px -12px rgba(37,98,255,.85);
}
.btn i{
  width:28px; height:28px; border-radius:50%; background:rgba(255,255,255,.18);
  display:grid; place-items:center; font-style:normal; font-size:13px;
  transition:transform .5s var(--ease), background .3s var(--ease);
}
.btn:hover{filter:brightness(1.06); box-shadow:0 16px 40px -14px rgba(37,98,255,.95)}
.btn:hover i{transform:translate3d(3px,-1px,0) scale(1.06); background:rgba(255,255,255,.28)}
.btn:active{transform:scale(.98)}
.btn.ghost{background:var(--s2); color:var(--tx); box-shadow:none; border:1px solid var(--hair)}
.btn.ghost{background:var(--s0); border-color:var(--hair)}
.btn.ghost i{background:rgba(var(--shade),.07)}
/* Where the live site is gone. It keeps the shape of the button it replaces so the row of
   controls stays even, but it is a <span>, not a link, and it is plainly not clickable --
   a greyed-out button that still looks pressable is a worse lie than no button at all. */
.btn.off{background:transparent; color:var(--tx3); box-shadow:none; border:1px dashed var(--hair);
  padding:9px 20px; cursor:default; font-weight:600}
@media (max-width:860px){ .nav-links{display:none} }

/* ---------- hero ---------- */
/* 150px of top padding was clearance for a nav that used to float over the page. The bar
   is in the flow now, so this is just breathing room. */
.hero{position:relative; padding:78px 0 66px; overflow:hidden}
.hero-bg{position:absolute; inset:0; pointer-events:none; z-index:0}
.hero-bg::before{
  content:''; position:absolute; inset:-20% -10% auto; height:820px;
  /* On white the same alpha that read as a glow on black reads as a solid blue wash. */
  background:radial-gradient(60% 55% at 50% 0%, rgba(37,98,255,.13), rgba(37,98,255,0) 70%);
}
.hero-bg::after{
  content:''; position:absolute; inset:0;
  background-image:linear-gradient(var(--hair2) 1px, transparent 1px),
                   linear-gradient(90deg, var(--hair2) 1px, transparent 1px);
  background-size:64px 64px;
  mask-image:radial-gradient(85% 60% at 50% 12%, #000 20%, transparent 78%);
  -webkit-mask-image:radial-gradient(85% 60% at 50% 12%, #000 20%, transparent 78%);
}
.hero .wrap{position:relative; z-index:1}
.eyebrow{
  display:inline-flex; align-items:center; gap:9px; padding:6px 14px; border-radius:999px;
  background:var(--s1); border:1px solid var(--hair);
  font-size:10.5px; font-weight:700; letter-spacing:.2em; text-transform:uppercase; color:var(--tx2);
}
.eyebrow s{width:6px; height:6px; border-radius:50%; background:var(--brand); text-decoration:none;
  box-shadow:0 0 0 0 rgba(37,98,255,.6); animation:pulse 2.6s var(--ease) infinite}
@keyframes pulse{ 70%{box-shadow:0 0 0 9px rgba(37,98,255,0)} 100%{box-shadow:0 0 0 0 rgba(37,98,255,0)} }
h1{font-size:clamp(40px,7.4vw,86px); margin:26px 0 0; max-width:16ch}
h1 em{font-style:normal; color:var(--brand)}
/* The rotator is a fixed-height window so swapping the word never reflows the headline. */
.rot{display:inline-grid; vertical-align:bottom; overflow:hidden; height:1.05em}
.rot span{
  grid-area:1/1; color:var(--brand); opacity:0; transform:translate3d(0,100%,0);
  transition:opacity .6s var(--ease-out), transform .6s var(--ease-out);
}
.rot span.on{opacity:1; transform:none}
.rot span.out{opacity:0; transform:translate3d(0,-100%,0)}
.hero-sub{margin-top:26px; max-width:56ch; font-size:clamp(15.5px,1.7vw,18.5px); color:var(--tx2)}
.hero-cta{display:flex; flex-wrap:wrap; gap:12px; margin-top:34px}
.kpis{
  display:grid; grid-template-columns:repeat(4,1fr); gap:1px; margin-top:52px;
  background:var(--hair2); border:1px solid var(--hair2); border-radius:var(--r); overflow:hidden;
}
.kpis div{background:var(--s0); padding:26px 22px}
.kpis dt{font-size:clamp(26px,4vw,42px); font-weight:800; letter-spacing:-.04em; line-height:1}
.kpis dd{margin-top:8px; font-size:12.5px; color:var(--tx3); font-weight:600; line-height:1.4}
@media (max-width:760px){ .kpis{grid-template-columns:1fr 1fr} }

/* ---------- marquee ---------- */
.marq{margin-top:70px; border-block:1px solid var(--hair2); padding:20px 0; overflow:hidden;
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.marq-t{display:flex; width:max-content; animation:slide 52s linear infinite}
.marq:hover .marq-t{animation-play-state:paused}
.marq-t span{padding:0 26px; font-size:14px; font-weight:700; color:var(--tx3); white-space:nowrap}
.marq-t span::after{content:'·'; margin-left:26px; color:var(--brand)}
@keyframes slide{ to{transform:translate3d(-50%,0,0)} }

/* ---------- filter rail ---------- */
/* This was a full-width bar stuck across the top of the grid, and two things were wrong
   with it. It parked at the old fixed-nav offset, so a 72px window of card text scrolled
   through the gap above it -- content appearing over the filters, which is backwards. And
   being full width it lay across the grid, hiding whichever row it happened to be passing.
   A left rail solves both by never being over the content in the first place: it sits
   beside the grid and stays put while the cards scroll past it. */
.workzone{position:relative; padding-top:28px}
.work-in{
  display:grid; grid-template-columns:236px minmax(0,1fr);
  gap:clamp(22px,3vw,40px); align-items:start;
}
/* min-width:0 is the load-bearing half of this rule. A grid item defaults to min-width:auto,
   so the horizontal chip scroller inside the rail sizes the whole column to its ~824px of
   content instead of scrolling inside it, and the page gains 460px of sideways scroll. */
.rail{position:sticky; top:var(--stick); min-width:0}
.rail-h{font-size:11px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  line-height:1; color:var(--tx3); margin:0 0 11px 3px}
.chips{display:grid; gap:3px}
/* Full width in the rail, so label and count sit at opposite ends of a row rather than
   riding together in a pill. */
.chip{
  display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%;
  padding:10px 14px; border-radius:13px; background:transparent; border:1px solid transparent;
  color:var(--tx2); font:inherit; font-size:13.5px; font-weight:700; text-align:left;
  cursor:pointer;
  transition:background .3s var(--ease), color .3s var(--ease), border-color .3s var(--ease);
}
.chip:hover{background:var(--s1); border-color:var(--hair); color:var(--tx)}
.chip[aria-pressed="true"]{background:var(--brand); border-color:transparent; color:#fff;
  box-shadow:0 12px 26px -16px rgba(37,98,255,.9)}
.chip u{text-decoration:none; opacity:.5; font-size:12.5px; font-variant-numeric:tabular-nums}
.chip[aria-pressed="true"] u{opacity:.75}
/* The reset button in the empty state is the same control, but inline in a sentence. */
.chip-inline{display:inline-flex; width:auto; padding:8px 15px; border-radius:999px;
  background:var(--s1); border-color:var(--hair); font-size:13px}
.search{position:relative; margin-bottom:20px}
.search input{
  width:100%; padding:10px 15px 10px 36px; border-radius:12px; background:var(--s1);
  border:1px solid var(--hair); color:var(--tx); font:inherit; font-size:13.5px;
  transition:border-color .3s var(--ease), background .3s var(--ease);
}
.search input::placeholder{color:var(--tx3)}
.search input:focus{border-color:var(--brand); background:var(--s2); outline:none}
.search svg{position:absolute; left:13px; top:50%; transform:translateY(-50%); opacity:.45}
/* Below the two-column threshold the rail goes back to being a row -- and stays in the
   flow. Sticking it to the top of a phone screen is what caused the complaint; seven
   filters are worth one swipe at the top of the list, not a permanent band over it. */
@media (max-width:980px){
  .workzone{padding-top:18px}
  .work-in{grid-template-columns:minmax(0,1fr); gap:14px}
  .rail{position:static}
  /* Clipped rather than display:none -- it is the accessible name of the filter group, and
     a name is still worth having when the heading is not worth the vertical space. */
  .rail-h{position:absolute; width:1px; height:1px; margin:0; overflow:hidden; clip-path:inset(50%)}
  /* width:100% is required, not belt-and-braces: as a grid item the scroller sizes to its
     max-content row (~824px) and blows the page out sideways instead of stretching. */
  .chips{display:flex; width:100%; gap:7px; flex-wrap:nowrap; overflow-x:auto;
    scrollbar-width:none; -ms-overflow-style:none; padding-bottom:2px}
  .chips::-webkit-scrollbar{display:none}
  .chip{flex:0 0 auto; width:auto; padding:8px 15px; border-radius:999px; gap:6px;
    background:var(--s1); border-color:var(--hair); font-size:13px}
  .chip[aria-pressed="true"]{background:var(--brand); border-color:transparent}
  .search{margin-bottom:12px}
}

/* Touch targets, and it has to sit after the block above or that block wins on source
   order and the pills stay 39px. Measured: 39px in the phone layout, 44px in the rail --
   both short of what a fingertip reliably hits, and the phone one is worse than the
   number suggests because the pills sit in a row with 7px between them, so a near-miss
   lands on the neighbouring filter rather than on nothing.

   Gated on the pointer rather than the viewport: an iPad in landscape gets the desktop
   rail and a finger, and a laptop at 900px gets the phone layout and a trackpad. The
   viewport does not know which one it is looking at; (pointer:coarse) does. */
@media (pointer:coarse){
  .chip{padding:13px 15px}
  .chip-inline{padding:11px 15px}
}

/* ---------- grid ---------- */
.grid{
  /* min(...,100%) is load-bearing: a bare minmax(320px,1fr) refuses to shrink below
     320px and pushes horizontal overflow on narrow phones. */
  /* 280 rather than 320: the rail takes 236px off the row, and 320 would drop the grid to
     two columns on a 1280 screen. */
  display:grid; grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr)); gap:20px;
  padding:0 0 40px; align-items:start;
}
.card{
  position:relative; min-width:0; background:var(--s1); border:1px solid var(--hair);
  border-radius:var(--r); padding:7px; overflow:hidden;
  transition:opacity .35s var(--ease), transform .45s var(--ease),
             border-color .45s var(--ease), box-shadow .45s var(--ease);
}
/* Spotlight border that tracks the pointer. --mx/--my are set in JS; setting a custom
   property does not invalidate layout, so this stays cheap. */
.card::before{
  content:''; position:absolute; inset:-1px; border-radius:inherit; pointer-events:none;
  background:radial-gradient(280px circle at var(--mx,50%) var(--my,0%), rgba(37,98,255,.16), rgba(37,98,255,0) 62%);
  opacity:0; transition:opacity .5s var(--ease);
}
.card:hover::before{opacity:1}
/* .card.reveal, not .card. Every card carries the reveal class, and .js .reveal.in
   pins opacity:1 and transform:none at specificity (0,3,0); a plain .card:hover is
   (0,2,0) and quietly loses. The hover lift and the filter crossfade below were both
   dead on this page from the moment the reveal was introduced -- measured in Chrome,
   the card read matrix(1,0,0,1,0,0) hovered and unhovered alike. Matching (0,3,0) is
   enough to fix it: these rules sit later in the file, so the tie breaks their way.
   The .reveal is not defensive padding -- card() always emits class="card reveal". */
.card.reveal:hover{border-color:rgba(37,98,255,.4); transform:translateY(-3px);
  box-shadow:0 26px 50px -30px rgba(var(--shade),.35)}
.card.gone{display:none}
.card.reveal.fade{opacity:0; transform:scale(.97)}
.card-shot{
  display:block; position:relative; height:236px; overflow:hidden;
  border-radius:calc(var(--r) - 8px); background:var(--s2); border:1px solid var(--hair);
}
/* Percentages in translateY resolve against the image's own height, so this parks the
   bottom of a full-page capture at the bottom of the 236px window regardless of length. */
.card-shot img{width:100%; transition:transform 6.5s cubic-bezier(.4,0,.35,1)}
.card:hover .card-shot img{transform:translateY(calc(-100% + 236px))}
/* A .flat shot is a single wide screenshot (an automation builder, not a full-page
   capture) -- fill the window and never pan, there is nothing below the fold to show. */
.card-shot.flat img{height:100%; object-fit:cover; object-position:left top}
.card:hover .card-shot.flat img{transform:none}
/* This sits on top of an arbitrary screenshot, so it cannot use the page's light surface
   tokens -- it stays a dark chip with light text, the one place that reads over anything. */
.card-visit{
  position:absolute; left:12px; bottom:12px; z-index:2; padding:7px 13px; border-radius:999px;
  background:rgba(var(--shade),.86); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.16); font-size:11.5px; font-weight:700; color:#fff;
  opacity:0; transform:translateY(6px); transition:opacity .4s var(--ease), transform .4s var(--ease);
}
.card-visit b{color:#8FAEFF}
.card:hover .card-visit, .card-shot:focus-visible .card-visit{opacity:1; transform:none}
.card-body{padding:20px 15px 17px}
.card-tag{font-size:10.5px; font-weight:800; letter-spacing:.16em; text-transform:uppercase; color:var(--brand)}
.card-body h3{font-size:20px; margin-top:9px}
.card-body h3 a{transition:color .3s var(--ease)}
.card-body h3 a:hover{color:var(--brand)}
.card-blurb{margin-top:10px; font-size:13.8px; color:var(--tx2); line-height:1.62}
.card-lead{
  margin-top:14px; padding:10px 13px; border-radius:13px; background:rgba(37,98,255,.07);
  border:1px solid rgba(37,98,255,.22); font-size:12.5px; color:var(--tx2);
}
.card-lead b{font-size:17px; color:var(--brand); font-weight:800; letter-spacing:-.02em; margin-right:4px;
  font-variant-numeric:tabular-nums}
.card-lead i{display:block; font-style:normal; color:var(--tx3); font-size:11px; margin-top:2px}
.card-stats{display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin-top:16px;
  padding-top:15px; border-top:1px solid var(--hair2)}
.card-stats>div{min-width:0}
.card-stats dt{font-size:15.5px; font-weight:800; letter-spacing:-.03em; font-variant-numeric:tabular-nums}
.card-stats dd{font-size:10.5px; color:var(--tx3); margin-top:3px; line-height:1.35; font-weight:600}
.card-stack{display:flex; flex-wrap:wrap; gap:6px; margin-top:15px}
.card-stack li{padding:4px 9px; border-radius:7px; background:var(--s2); border:1px solid var(--hair2);
  font-size:10.5px; font-weight:700; color:var(--tx3)}
.card-study{display:inline-block; margin-top:15px; font-size:12.5px; font-weight:700; color:var(--brand)}
.card-study b{display:inline-block; transition:transform .4s var(--ease)}
.card-study:hover b{transform:translateX(4px)}
/* Two destinations per card: our own write-up of the build, and the client's published
   case study where one exists. The detail page is the primary one, so it keeps the brand
   colour and the case study drops to a secondary grey. */
.card-links{display:flex; flex-wrap:wrap; align-items:center; gap:10px 18px; margin-top:16px;
  padding-top:15px; border-top:1px solid var(--hair2)}
.card-more{font-size:12.5px; font-weight:800; color:var(--brand)}
.card-more b{display:inline-block; transition:transform .4s var(--ease)}
.card-more:hover b{transform:translateX(4px)}
.card-links .card-study{margin-top:0; color:var(--tx2); font-weight:700}
.card-links .card-study:hover{color:var(--tx)}
.empty{padding:70px 0; text-align:center; color:var(--tx3)}
.empty[hidden]{display:none}

/* ---------- sections ---------- */
section{position:relative}
.sec{padding:96px 0}
.sec-head{max-width:62ch}
.sec-head h2{font-size:clamp(28px,4.4vw,50px); margin-top:20px}
.sec-head p{margin-top:18px; color:var(--tx2); font-size:clamp(15px,1.6vw,17.5px)}
.rule{height:1px; background:var(--hair2)}

/* ---------- spotlight ---------- */
/* These figures are full-page captures, so the image column runs ~2000px tall while the
   copy column is ~600px. Centering left half the viewport empty for two full screens of
   scroll. Pinning the copy turns that dead space into the point of the section: the copy
   holds still while the whole site scrolls past it. */
.spot{display:grid; grid-template-columns:1fr 1fr; gap:clamp(24px,4vw,64px); align-items:start; margin-top:54px}
.spot-copy{position:sticky; top:var(--stick)}
.spot-fig{
  border-radius:var(--r); padding:8px; background:var(--s1); border:1px solid var(--hair);
  box-shadow:0 40px 90px -52px rgba(var(--shade),.5);
}
.spot-fig img{border-radius:calc(var(--r) - 9px); border:1px solid var(--hair)}
.spot h3{font-size:clamp(24px,3.4vw,38px)}
.spot p{margin-top:16px; color:var(--tx2); font-size:15.5px}
.jump{display:grid; grid-template-columns:auto auto auto; gap:14px; align-items:end; margin-top:28px}
.jump b{font-size:clamp(34px,6vw,64px); font-weight:800; letter-spacing:-.045em; line-height:.9;
  font-variant-numeric:tabular-nums}
.jump b.dim{color:var(--tx3); font-size:clamp(24px,4vw,40px)}
.jump s{text-decoration:none; color:var(--brand); font-size:26px; padding-bottom:6px}
.jump em{grid-column:1/-1; font-style:normal; font-size:12px; color:var(--tx3); font-weight:600;
  letter-spacing:.1em; text-transform:uppercase}
.pills{display:flex; flex-wrap:wrap; gap:8px; margin-top:26px}
.pills li{padding:7px 13px; border-radius:999px; background:var(--s2); border:1px solid var(--hair2);
  font-size:12px; font-weight:700; color:var(--tx2)}
/* Stacked, a sticky copy block would sit on top of its own screenshot. */
@media (max-width:900px){
  .spot{grid-template-columns:1fr}
  /* Full-page capture at phone width is ~1300px of scroll before the copy starts.
     Crop to a strip and fade the cut so it reads as deliberate. */
  .spot-fig{order:-1; max-height:64vh; overflow:hidden;
    -webkit-mask-image:linear-gradient(#000 74%, transparent);
            mask-image:linear-gradient(#000 74%, transparent)}
  .spot-copy{position:static}
}

/* ---------- flow diagram ---------- */
.flow{display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-top:44px; counter-reset:f}
.flow li{
  position:relative; padding:22px 18px 20px; border-radius:20px; background:var(--s1);
  border:1px solid var(--hair2); min-width:0;
}
.flow li::before{
  counter-increment:f; content:counter(f); display:grid; place-items:center;
  width:28px; height:28px; border-radius:50%; background:var(--brand); color:#fff;
  font-size:12.5px; font-weight:800; margin-bottom:14px;
}
.flow li::after{
  content:''; position:absolute; right:-14px; top:50%; width:14px; height:1px;
  background:linear-gradient(90deg,var(--brand),transparent);
}
.flow li:last-child::after{display:none}
.flow h4{margin:0; font-size:15px; font-weight:800; letter-spacing:-.02em}
.flow p{margin-top:7px; font-size:12.8px; color:var(--tx3); line-height:1.55}
@media (max-width:860px){ .flow{grid-template-columns:1fr 1fr} .flow li::after{display:none} }
@media (max-width:460px){ .flow{grid-template-columns:1fr} }

/* ---------- dashboard mock ---------- */
.dashwrap{margin-top:52px; border-radius:var(--r); padding:9px; background:var(--s1);
  border:1px solid var(--hair); box-shadow:0 50px 110px -60px rgba(var(--shade),.55)}
/* The mock stays dark on purpose -- it is a screen inside a light bezel, the way a laptop
   in a product shot is. Every surface token is re-pointed for the dark interior so the
   shared component rules (.pipe, .bars, .dash-*) keep working unmodified. */
.dash{
  --s1:rgba(255,255,255,.045); --s2:rgba(255,255,255,.075);
  --hair:rgba(255,255,255,.13); --hair2:rgba(255,255,255,.07);
  --tx:#EDEEF2; --tx2:#A9AEBB; --tx3:#878D9C;
  border-radius:calc(var(--r) - 10px); background:#0C0D12; border:1px solid #0C0D12;
  overflow:hidden; color:var(--tx);
}
.dash-bar{display:flex; align-items:center; gap:8px; padding:12px 16px; border-bottom:1px solid var(--hair2);
  background:var(--s1)}
.dash-bar s{width:9px; height:9px; border-radius:50%; background:var(--hair); text-decoration:none}
.dash-url{margin-left:12px; padding:4px 12px; border-radius:999px; background:var(--s2);
  font-size:11.5px; color:var(--tx3); font-weight:600}
.dash-live{margin-left:auto; font-size:10.5px; font-weight:800; letter-spacing:.14em; color:#0CCE6B}
.dash-live s{display:inline-block; background:#0CCE6B; margin-right:6px; animation:pulse 2.4s var(--ease) infinite}
.dash-grid{display:grid; grid-template-columns:repeat(4,1fr); gap:1px; background:var(--hair2)}
.dash-grid>div{background:#0C0D12; padding:20px 18px}
.dash-grid dt{font-size:10.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--tx3); font-weight:700}
.dash-grid dd{margin-top:9px; font-size:clamp(20px,3vw,30px); font-weight:800; letter-spacing:-.04em;
  font-variant-numeric:tabular-nums}
.dash-grid dd u{text-decoration:none; font-size:12px; color:#0CCE6B; margin-left:7px; font-weight:700}
.dash-body{display:grid; grid-template-columns:1.35fr 1fr; gap:1px; background:var(--hair2)}
.dash-body>div{background:#0C0D12; padding:22px 18px}
.dash-h{font-size:12px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; color:var(--tx3)}
.bars{display:flex; align-items:flex-end; gap:7px; height:126px; margin-top:20px}
/* height:100% is load-bearing: align-items:flex-end stops the default stretch, so without
   it the bars are zero-tall and scaleY has nothing to scale. */
.bars i{flex:1; min-width:0; height:100%; border-radius:5px 5px 2px 2px; background:linear-gradient(180deg,var(--brand),rgba(37,98,255,.25));
  transform:scaleY(.04); transform-origin:bottom; transition:transform 1.1s var(--ease-out)}
.in .bars i{transform:scaleY(var(--h,.5))}
.pipe{margin-top:18px}
.pipe li{display:grid; grid-template-columns:1fr auto; gap:10px; align-items:center; padding:11px 0;
  border-bottom:1px solid var(--hair2); font-size:13px; color:var(--tx2)}
.pipe li:last-child{border-bottom:0}
.pipe b{font-variant-numeric:tabular-nums; color:var(--tx); font-weight:800}
.pipe s{display:block; height:3px; border-radius:2px; background:var(--brand); text-decoration:none;
  grid-column:1/-1; transform:scaleX(0); transform-origin:left; transition:transform 1.2s var(--ease-out)}
.in .pipe s{transform:scaleX(var(--w,.5))}
@media (max-width:900px){ .dash-grid{grid-template-columns:1fr 1fr} .dash-body{grid-template-columns:1fr} }

/* ---------- capability band ---------- */
.caps{display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-top:48px}
.caps li{padding:26px 22px; border-radius:20px; background:var(--s1); border:1px solid var(--hair2); min-width:0}
.caps h4{margin:0; font-size:16px; font-weight:800; letter-spacing:-.02em}
.caps p{margin-top:9px; font-size:13.5px; color:var(--tx2); line-height:1.6}
.caps s{display:block; width:30px; height:30px; border-radius:9px; background:rgba(37,98,255,.14);
  border:1px solid rgba(37,98,255,.3); margin-bottom:15px; text-decoration:none;
  display:grid; place-items:center; color:var(--brand); font-size:14px; font-weight:800}
@media (max-width:900px){ .caps{grid-template-columns:1fr 1fr} }
@media (max-width:560px){ .caps{grid-template-columns:1fr} }

/* ---------- cta / footer ---------- */
.cta{padding:110px 0 96px; text-align:center; position:relative; overflow:hidden}
.cta::before{content:''; position:absolute; inset:auto -10% -60% ; height:640px;
  background:radial-gradient(50% 50% at 50% 100%, rgba(37,98,255,.12), rgba(37,98,255,0) 70%)}
.cta .wrap{position:relative}
.cta h2{font-size:clamp(30px,5.4vw,60px); margin-inline:auto; max-width:18ch}
.cta p{margin:20px auto 0; max-width:52ch; color:var(--tx2); font-size:17px}
.cta .hero-cta{justify-content:center}
footer{border-top:1px solid var(--hair2); padding:34px 0; font-size:12.5px; color:var(--tx3)}
.foot{display:flex; flex-wrap:wrap; gap:16px; align-items:center; justify-content:space-between}
.foot a{color:var(--tx2)}

/* ---------- touch: hover is not a state a finger can leave ----------
 * A tap on a touch screen fires :hover and then leaves it applied until something
 * else is tapped. Every rule below moves something or uncovers something, so on a
 * phone they do not read as feedback -- they read as the card having broken. The
 * worst of them is the screenshot scroll: one tap and the image sits parked at the
 * bottom of the shot with no way back.
 *
 * Only transforms and filters are undone. Colour and shadow hovers are left exactly
 * as they are, on purpose and for two reasons: a flash of brand blue under a finger
 * is the one bit of tap feedback this page gets for free, and neither can leave the
 * layout looking wrong the way a stuck 3px lift or a parked screenshot does. Keeping
 * restored values out of here also means retuning a resting shadow upstairs cannot
 * silently desync this block.
 *
 * This lives at the end of the index's own <style> rather than in DETAIL_CSS because
 * that block is what gets lifted into assets/our-work.css -- the index inlines it and
 * never links the file, so anything appended to DETAIL_CSS reaches the detail pages
 * only. Rules for selectors the index has must be here. */
@media (hover:none){
  /* .card.reveal to match the lift it is undoing -- see the specificity note there. */
  .card.reveal:hover{transform:none}
  .card:hover::before{opacity:0}
  .card:hover .card-shot img{transform:none}
  .card:hover .card-visit{opacity:0; transform:translateY(6px)}
  .card-shot:focus-visible .card-visit{opacity:1; transform:none}
  .btn:hover{filter:none}
  .btn:hover i{transform:none}
  .card-study:hover b, .card-more:hover b{transform:none}
  /* Pausing the marquee needs a pointer that can leave. A tap would stop it dead. */
  .marq:hover .marq-t{animation-play-state:running}
}


/* ================================================================
   DETAIL PAGES -- appended to the shared stylesheet only. The index
   inlines the block above and never loads these rules.
   ================================================================ */

.dhero{position:relative; padding:60px 0 60px; overflow:hidden}
.dhero .wrap{position:relative; z-index:1}
.dhero h1{font-size:clamp(34px,5.8vw,68px); max-width:19ch; margin-top:22px}
.dhero .hero-sub{max-width:62ch}
.back{display:inline-flex; align-items:center; gap:9px; font-size:12.5px; font-weight:700;
  color:var(--tx3); transition:color .3s var(--ease)}
.back:hover{color:var(--brand)}
.back b{display:inline-block; font-weight:800; transition:transform .4s var(--ease)}
.back:hover b{transform:translateX(-4px)}
.dtags{display:flex; flex-wrap:wrap; gap:8px; margin-top:26px}
.dtags li{padding:6px 13px; border-radius:999px; background:var(--s1); border:1px solid var(--hair);
  font-size:11px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; color:var(--tx2)}
/* auto-fit rather than the index's fixed four columns: a project carries three stats, or
   four once it has a lead figure, and neither should leave a gap in the strip. */
.dkpis{grid-template-columns:repeat(auto-fit,minmax(min(190px,100%),1fr))}
.dkpis dt{font-size:clamp(22px,3.4vw,36px)}

/* ---------- lead callout ---------- */
.leadbox{display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 16px; margin-top:34px;
  padding:24px 28px; border-radius:20px; background:rgba(37,98,255,.06);
  border:1px solid rgba(37,98,255,.22)}
.leadbox b{font-size:clamp(32px,5vw,52px); font-weight:800; letter-spacing:-.045em; line-height:1;
  color:var(--brand); font-variant-numeric:tabular-nums}
.leadbox span{font-size:15.5px; font-weight:700; color:var(--tx2)}
.leadbox i{width:100%; font-style:normal; margin-top:6px; font-size:11px; font-weight:700;
  letter-spacing:.14em; text-transform:uppercase; color:var(--tx3)}

/* ---------- the two device frames ---------- */
/* Full-page captures run several thousand pixels tall, so they scroll inside their frame
   rather than being cropped or panned on hover: a pointer trick would leave phone
   visitors -- most of this traffic -- unable to see past the fold of a screenshot. */
.frames{display:grid; grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:clamp(22px,3.6vw,52px); margin-top:48px; align-items:start}
@media (max-width:940px){ .frames{grid-template-columns:1fr; justify-items:center} }
.shot-scroll{
  overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
  background:var(--s2); scrollbar-width:thin; scrollbar-color:var(--hair) transparent;
}
.shot-scroll::-webkit-scrollbar{width:8px}
.shot-scroll::-webkit-scrollbar-track{background:transparent}
.shot-scroll::-webkit-scrollbar-thumb{background:var(--hair); border-radius:99px}
.frames figcaption{margin-top:16px; font-size:12px; font-weight:700; color:var(--tx3);
  letter-spacing:.06em}

/* The second surface of a build -- an admin behind a storefront. It is set inside a tinted
   well so it reads as part of this project rather than as the start of a new section, and its
   frames are given a little less room than the ones above them: this is the supporting
   screen, not the headline. */
.also{margin-top:clamp(46px,6vw,78px); padding:clamp(24px,3.4vw,40px); border-radius:28px;
  background:var(--s1); border:1px solid var(--hair)}
.also-head{max-width:64ch}
.also-head h3{margin:12px 0 0; font-size:clamp(22px,2.6vw,30px); font-weight:800;
  letter-spacing:-.03em; line-height:1.12; text-wrap:balance}
.also-head p{margin:10px 0 0; font-size:15px; line-height:1.62; color:var(--tx2)}
.also-head .btn{margin-top:18px}
.also .frames{margin-top:32px; gap:clamp(20px,3vw,42px)}
/* The browser chrome is normally s1 on the page's white; inside an s1 well it would be the
   same tone as its own surround, so it takes the white instead and the well keeps the tint. */
.also .browser{background:var(--s0)}
.also .browser .shot-scroll{max-height:min(560px,62vh)}
.also .dphone{max-width:250px}

.browser{width:100%; border-radius:var(--r); padding:9px; background:var(--s1);
  border:1px solid var(--hair); box-shadow:0 46px 100px -58px rgba(var(--shade),.5)}
.browser-bar{display:flex; align-items:center; gap:8px; padding:5px 6px 13px}
.browser-bar s{width:9px; height:9px; border-radius:50%; background:var(--hair); text-decoration:none}
.browser-url{min-width:0; margin-left:10px; padding:4px 13px; border-radius:999px;
  background:var(--s2); border:1px solid var(--hair2); font-size:11.5px; font-weight:600;
  color:var(--tx3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* max-height, not height: a short page -- an admin screen, a one-screen landing -- would
   otherwise paint the rest of the frame as a grey band under the screenshot. Every tall
   capture still stops at the cap and scrolls. */
.browser .shot-scroll{max-height:min(680px,74vh); border-radius:calc(var(--r) - 10px);
  border:1px solid var(--hair)}

.dphone{width:100%; max-width:276px}
/* The extra top padding is the status strip. Without it the notch floats over whatever the
   captured site puts at the very top -- on A Plus it blacked out half the DOT number. The
   bezel colour shows through as the strip, so it costs no markup. */
.dphone-screen{--bez:10px; --bar:26px; position:relative;
  padding:calc(var(--bez) + var(--bar)) var(--bez) var(--bez); border-radius:40px;
  background:#15161B; box-shadow:0 40px 74px -42px rgba(var(--shade),.6),
  inset 0 0 0 1px rgba(255,255,255,.09)}
.dphone-notch{position:absolute; z-index:2; top:calc(var(--bez) + 8px); left:50%;
  transform:translateX(-50%); width:64px; height:15px; border-radius:99px; background:#2a2c34}
.dphone .shot-scroll{max-height:min(560px,64vh); border-radius:30px}
@media (max-width:940px){ .dphone .shot-scroll{max-height:520px} }

/* ================================================================
   CASE STUDY -- the WordPress /portfolio/ page, folded into this one.
   ================================================================ */

.cs-block{margin-top:clamp(46px,6vw,84px)}
.cs-block:first-of-type{margin-top:clamp(34px,4.4vw,56px)}
.cs-head{max-width:66ch; margin-bottom:26px}
.cs-head .eyebrow{margin-bottom:12px}
.cs-head h3{font-size:clamp(22px,3.1vw,34px); letter-spacing:-.035em; line-height:1.14}
.cs-lede{max-width:70ch; margin-top:14px; font-size:16px; line-height:1.72; color:var(--tx2)}

/* ---------- before / after ---------- */
/* Two captures of the same site years apart. Side by side is the whole point, so they stay
   side by side down to phone width and simply get smaller -- stacking them turns the
   comparison into two unrelated pictures a screen apart.
   Both are full-page captures, and the two are never the same shape: 635x2560 next to
   1920x10300 renders as a thin ribbon beside a three-metre column. A fixed 4:5 box cropped
   to the top of each shows the fold of both at the same size, which is the comparison. */
.cs-ba{display:grid; grid-template-columns:1fr 1fr; gap:clamp(10px,1.8vw,22px)}
.cs-ba-item{position:relative; margin:0; border-radius:var(--r); overflow:hidden;
  background:var(--s1); border:1px solid var(--hair);
  box-shadow:0 34px 74px -46px rgba(var(--shade),.42)}
.cs-ba-item img{display:block; width:100%; height:100%; aspect-ratio:4/5;
  object-fit:cover; object-position:top center}
.cs-ba-tag{position:absolute; top:12px; left:12px; z-index:1; padding:5px 12px; border-radius:999px;
  font-size:10.5px; font-weight:800; letter-spacing:.14em; text-transform:uppercase; color:#fff;
  backdrop-filter:blur(6px)}
.cs-ba-tag.was{background:rgba(21,22,27,.72)}
.cs-ba-tag.now{background:rgba(37,98,255,.9)}

/* ---------- design system ---------- */
.cs-theme{display:grid; grid-template-columns:minmax(0,1.4fr) minmax(0,.8fr) minmax(0,1fr);
  gap:clamp(16px,2.4vw,28px); align-items:stretch}
@media (max-width:860px){ .cs-theme{grid-template-columns:1fr} }
.cs-theme > *{margin:0; padding:24px; border-radius:var(--r); background:var(--s1);
  border:1px solid var(--hair)}
.cs-theme h4, .cs-theme figcaption{font-size:10.5px; font-weight:800; letter-spacing:.16em;
  text-transform:uppercase; color:var(--tx3)}
.cs-type{display:flex; flex-direction:column; justify-content:space-between; gap:18px}
.cs-type img{display:block; width:100%; height:auto; order:-1}
.cs-colors ul{display:flex; flex-wrap:wrap; gap:10px; margin-top:16px}
.cs-colors li{display:flex; align-items:center; gap:9px; padding:6px 12px 6px 6px; border-radius:999px;
  background:var(--s2); border:1px solid var(--hair2)}
.cs-colors s{width:22px; height:22px; border-radius:50%; text-decoration:none;
  box-shadow:inset 0 0 0 1px rgba(var(--shade),.16)}
.cs-colors code{font-size:11.5px; font-weight:700; letter-spacing:.02em; color:var(--tx2);
  font-variant-numeric:tabular-nums}
.cs-logos > div{display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:16px}
.cs-logos img{max-width:150px; height:auto; border-radius:10px}

/* ---------- screen grabs ---------- */
/* Flex rather than a grid: a section can hold one browser frame, or a browser plus a
   phone, or three analytics captures, and each of those wants a different column count. */
.cs-screens{display:flex; flex-wrap:wrap; gap:clamp(18px,2.8vw,40px); align-items:flex-start}
.cs-screens .browser{flex:1 1 460px; min-width:0}
.cs-screens .dphone{flex:0 0 262px}
.browser.flat .shot-flat{border-radius:calc(var(--r) - 10px); border:1px solid var(--hair);
  overflow:hidden; background:var(--s2)}
.browser.flat img{display:block; width:100%; height:auto}

/* ---------- galleries ---------- */
/* Ad creatives are 4:5, brand collateral is a mix of squares and landscapes. Columns give
   a masonry that never leaves a ragged hole, which a fixed grid does with mixed ratios. */
.cs-gallery{columns:3; column-gap:clamp(12px,1.6vw,18px)}
/* Two to four images in a column layout stack unevenly -- three become 2 + 1 with a hole
   under the short side. Few enough to fit one row, so lay them out as one row. */
.cs-gallery.few{columns:auto; display:grid; gap:clamp(12px,1.6vw,18px);
  grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr)); align-items:start}
.cs-gallery.few .cs-zoom{margin:0}
@media (max-width:900px){ .cs-gallery{columns:2} }
@media (max-width:520px){ .cs-gallery{columns:1} }
.cs-zoom{display:block; width:100%; margin:0 0 clamp(12px,1.6vw,18px); padding:0; border:0;
  break-inside:avoid; border-radius:16px; overflow:hidden; background:var(--s1);
  cursor:zoom-in; box-shadow:0 22px 48px -34px rgba(var(--shade),.4);
  transition:transform .5s var(--ease), box-shadow .5s var(--ease)}
.cs-zoom img{display:block; width:100%; height:auto}
.cs-cap{display:block; padding:11px 14px 13px; text-align:left; font-size:12px; line-height:1.45;
  font-weight:600; letter-spacing:-.01em; color:var(--tx3); border-top:1px solid var(--hair)}
.cs-zoom:hover{transform:translateY(-4px); box-shadow:0 34px 66px -34px rgba(var(--shade),.5)}
.cs-zoom:focus-visible{outline:2px solid var(--brand); outline-offset:3px}

.lightbox{max-width:min(1100px,94vw); max-height:92vh; padding:0; border:0; border-radius:20px;
  background:var(--s0); overflow:auto}
.lightbox::backdrop{background:rgba(10,11,15,.78)}
.lightbox img{display:block; width:100%; height:auto}
.lightbox form{position:sticky; top:0; z-index:1; display:flex; justify-content:flex-end}
.lightbox button{margin:10px; width:36px; height:36px; border-radius:50%; border:1px solid var(--hair);
  background:var(--s1); color:var(--tx); font-size:19px; line-height:1; cursor:pointer}

/* ---------- the paid-social metric block ---------- */
.cs-metric{display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  gap:clamp(18px,3vw,44px); align-items:center; padding:clamp(24px,3.4vw,44px);
  border-radius:var(--r); background:rgba(37,98,255,.055); border:1px solid rgba(37,98,255,.2)}
@media (max-width:820px){ .cs-metric{grid-template-columns:1fr} }
.cs-metric-big em{display:block; font-style:normal; font-size:10.5px; font-weight:800;
  letter-spacing:.16em; text-transform:uppercase; color:var(--tx3)}
.cs-metric-big b{display:block; margin-top:10px; font-size:clamp(46px,8vw,84px); font-weight:800;
  letter-spacing:-.05em; line-height:.94; color:var(--brand); font-variant-numeric:tabular-nums}
.cs-metric-big span{display:block; margin-top:10px; font-size:15.5px; font-weight:700; color:var(--tx2)}
.cs-metric-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(150px,100%),1fr));
  gap:12px}
.cs-metric-grid > div{padding:16px 18px; border-radius:16px; background:var(--s0);
  border:1px solid var(--hair)}
.cs-metric-grid dt{font-size:13px; font-weight:800; letter-spacing:-.02em}
.cs-metric-grid dd{margin-top:5px; font-size:12.5px; line-height:1.5; color:var(--tx3)}

/* ---------- results copy ---------- */
.cs-chips{display:flex; flex-wrap:wrap; gap:10px; margin-top:24px}
.cs-chips li{display:flex; align-items:baseline; gap:8px; padding:10px 16px; border-radius:999px;
  background:var(--s1); border:1px solid var(--hair)}
.cs-chips b{font-size:17px; font-weight:800; letter-spacing:-.03em; color:var(--brand)}
.cs-chips span{font-size:12.5px; font-weight:600; color:var(--tx3)}
.cs-list{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr));
  gap:9px 22px; margin-top:26px}
.cs-list li{position:relative; padding-left:26px; font-size:14.5px; line-height:1.6; color:var(--tx2)}
.cs-list li::before{content:""; position:absolute; left:0; top:.52em; width:14px; height:8px;
  border-left:2px solid var(--brand); border-bottom:2px solid var(--brand);
  transform:rotate(-45deg) translateY(-2px)}
.cs-cards-label{margin-top:34px; font-size:10.5px; font-weight:800; letter-spacing:.16em;
  text-transform:uppercase; color:var(--tx3)}
.cs-cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(270px,100%),1fr));
  gap:14px; margin-top:16px}
.cs-cards li{padding:22px 24px; border-radius:18px; background:var(--s1); border:1px solid var(--hair);
  transition:border-color .45s var(--ease), transform .45s var(--ease)}
.cs-cards li:hover{border-color:rgba(37,98,255,.34); transform:translateY(-3px)}
.cs-cards h4{font-size:15px; font-weight:800; letter-spacing:-.025em}
.cs-cards p{margin-top:8px; font-size:13.5px; line-height:1.62; color:var(--tx3)}
.cs-cards.paired{grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr)); gap:16px}
.cs-cards.paired > li{padding:24px 26px}
.cs-cards.paired h4{font-size:11px; font-weight:800; letter-spacing:.16em; text-transform:uppercase;
  color:var(--tx3)}
.cs-cards li ul{margin-top:10px; display:grid; gap:7px}
.cs-cards li ul li{padding:0; border:0; background:none; font-size:13.5px; line-height:1.6;
  color:var(--tx3); padding-left:17px; position:relative}
.cs-cards li ul li:hover{transform:none}
.cs-cards li ul li::before{content:''; position:absolute; left:1px; top:.62em; width:6px; height:6px;
  border-radius:2px; background:var(--brand); opacity:.7}

/* ---------- the design system: palette, type, wireframe ---------- */
/* Three full-width bands rather than a sidebar layout. A palette card is short and a type
   specimen is tall, and side by side one of them always ends in 400px of empty card. */
.dsc{margin-top:16px; padding:26px 28px 28px; border-radius:24px; background:var(--s1);
  border:1px solid var(--hair)}
.dsc:first-of-type{margin-top:40px}
.dsc-h{display:flex; flex-wrap:wrap; align-items:baseline; gap:6px 12px;
  font-size:10.5px; font-weight:800; letter-spacing:.16em; text-transform:uppercase; color:var(--tx3)}
.dsc-h span{font-size:13px; font-weight:700; letter-spacing:-.02em; text-transform:none; color:var(--tx2)}

.sw{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(150px,100%),1fr));
  gap:14px; margin-top:20px}
.sw i{display:block; height:92px; border-radius:15px;
  /* the inset hairline keeps a white swatch from vanishing into a near-white card */
  box-shadow:inset 0 0 0 1px rgba(var(--shade),.14), 0 14px 26px -18px rgba(var(--shade),.6)}
.sw b{display:block; margin-top:12px; font-size:14.5px; font-weight:800; letter-spacing:-.01em;
  font-variant-numeric:tabular-nums}
.sw em{display:block; margin-top:3px; font-style:normal; font-size:11.5px; font-weight:600;
  color:var(--tx3)}

/* The mark photographed where it lives, backdrop included. Lifting a logo onto a white card
   would be a different picture from the one the client signed off: several of these marks are
   white, and a few sit on a photograph. The crop is shown at half its captured width because
   it is taken at 2x -- so it reads at the size the site actually paints it. */
.lgo{display:grid; place-items:center; min-height:118px; margin-top:20px; padding:20px 22px;
  border-radius:16px; background:var(--s0); border:1px solid var(--hair2); overflow:hidden}
.lgo img{display:block; width:100%; height:auto; border-radius:9px}

.tgrid{display:grid; grid-template-columns:minmax(0,1.6fr) minmax(0,1fr); gap:22px; margin-top:20px;
  align-items:center}
/* The specimen is captured at 2x, so its intrinsic width is twice what the site actually
   painted; the inline max-width is that real CSS width. A 24px dashboard heading then sits
   centred at its own size instead of being blown across a 700px card as a smear. */
.spec{display:grid; place-items:center; min-height:132px; padding:18px 20px; border-radius:16px;
  background:var(--s0); border:1px solid var(--hair2); overflow:hidden}
.spec img{display:block; width:100%; height:auto}
.tspec{display:grid; gap:10px}
.tspec > div{display:grid; grid-template-columns:74px minmax(0,1fr); gap:14px; align-items:baseline}
.tspec dt{font-size:10.5px; font-weight:800; letter-spacing:.14em; text-transform:uppercase;
  color:var(--tx3)}
.tspec dd{font-size:13.5px; line-height:1.5; color:var(--tx2); font-variant-numeric:tabular-nums}
.tspec dd b{font-weight:800; color:var(--tx)}

/* align-items:start, so each frame ends where its drawing ends. Stretching them to a common
   row height is worse: a short section then sits in a tall white box with 300px of nothing
   under it, which looks like a rendering fault rather than like a short section. */
.wgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));
  gap:16px; margin-top:20px; align-items:start}
.wgrid figure{padding:14px; border-radius:18px; background:var(--s0); border:1px solid var(--hair2)}
.wf{display:block; width:100%; height:auto}
.wgrid figcaption{display:flex; align-items:baseline; gap:9px; margin-top:14px;
  font-size:12.5px; font-weight:700; letter-spacing:-.01em; color:var(--tx2)}
.wgrid figcaption s{text-decoration:none; font-size:10.5px; font-weight:800; letter-spacing:.1em;
  color:var(--brand); font-variant-numeric:tabular-nums}
/* One grey family for the whole wireframe, with the accent reserved for the call to action --
   a wireframe that colours everything is just a small screenshot. */
.w-bg{fill:var(--s1)}
.w-head{fill:rgba(var(--shade),.42)}
.w-sub{fill:rgba(var(--shade),.26)}
.w-text{fill:rgba(var(--shade),.13)}
.w-media{fill:rgba(var(--shade),.07); stroke:rgba(var(--shade),.16); stroke-width:1.5}
.w-x{stroke:rgba(var(--shade),.13); stroke-width:1.5; fill:none}
.w-field{fill:none; stroke:rgba(var(--shade),.3); stroke-width:2}
.w-cta{fill:var(--brand); opacity:.85}
@media (max-width:820px){
  .tgrid{grid-template-columns:minmax(0,1fr); gap:18px}
  .dsc{padding:22px 20px 24px}
  .sw{grid-template-columns:repeat(auto-fit,minmax(min(128px,100%),1fr)); gap:12px}
  .sw i{height:74px}
}

/* ---------- prev / next ---------- */
.pager{display:grid; grid-template-columns:1fr 1fr; gap:16px; padding-bottom:20px}
.pager a{display:block; padding:24px 26px; border-radius:20px; background:var(--s1);
  border:1px solid var(--hair);
  transition:border-color .45s var(--ease), transform .45s var(--ease), box-shadow .45s var(--ease)}
.pager a:hover{border-color:rgba(37,98,255,.4); transform:translateY(-3px);
  box-shadow:0 26px 50px -30px rgba(var(--shade),.35)}
.pager em{display:block; font-style:normal; font-size:10.5px; font-weight:800;
  letter-spacing:.16em; text-transform:uppercase; color:var(--tx3)}
.pager strong{display:block; margin-top:9px; font-size:17px; font-weight:800; letter-spacing:-.03em}
.pager .next{text-align:right}
@media (max-width:640px){ .pager{grid-template-columns:1fr} .pager .next{text-align:left} }

/* Touch, detail-page half. The shared half sits at the end of the index's <style>,
   which is what becomes this stylesheet; these four selectors exist only here.
   Same reasoning: undo the movement, leave the colour and shadow alone. */
@media (hover:none){
  .back:hover b{transform:none}
  .cs-zoom:hover{transform:none}
  .cs-cards li:hover{transform:none}
  .pager a:hover{transform:none}
}

/* A build shown as a recording of itself scrolling. The well stops being a scroller --
   the clip already carries the movement -- so it is pinned to the capture's own aspect. */
.shot-scroll.vid{overflow:hidden; max-height:none}
.browser .shot-scroll.vid{aspect-ratio:1440/900}
.dphone .shot-scroll.vid{aspect-ratio:390/844}
.also .browser .shot-scroll.vid{max-height:none}
.shot-scroll.vid video{display:block; width:100%; height:100%; object-fit:cover;
  object-position:top; background:var(--s2)}
