/* ============================================================
   ULTRABASIC LABS — the site · "Pink dawn" system
   White canvas · warm cream cards · candy-pink pill CTAs ·
   uppercase weight-900 display type · flat, hairline-separated.
   This is the live design. The retired one is in _archive/v1/.
   ============================================================ */

/* ------------------------------------------------------------ the face */
/* Plus Jakarta Sans, self hosted. It used to come from Google Fonts, which
   cost two extra origins on the critical path and a 440ms render block in
   Lighthouse. Latin and latin ext only: the site is written in English.
   Variable weight 200 to 800, same axis the Google CSS served. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: italic;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/pjs-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: italic;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/pjs-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/pjs-normal-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/pjs-normal-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Merriweather Light, the heading face. Self hosted on the same terms as the
   body face: latin and latin ext only, one static weight. 300 is the only cut
   we ship, so headings must never ask for a heavier number or the browser
   will fake it and the serif will smear. */
@font-face {
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/mw-light-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/mw-light-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ------------------------------------------------------------ breakpoints */
/* The scale, and the reason it is a comment rather than a token.
   Custom properties cannot be read inside a media condition: @media
   (max-width: var(--bp-md)) does not resolve, in any browser, because media
   queries are evaluated before the cascade has a value to give. The scale can
   therefore only be written down and kept to by hand, which is exactly how it
   got away from us — sixteen widths, of which nine were one component each:

     380 520 540 560 620 640 700 720 760 820 860 900 940 960 1040 1100 1120

   Seven now, and each one is a thing rather than a number:

     540   small phone      the narrowest layouts, below the common 375-414
     640   phone            --gutter drops from 32px to 20px here
     760   tablet
     860   small laptop     the heaviest block on the site
     900   desktop          the nav switches; min-width: 901px is its other half
     960   wide components  the ledger and the Insights rack, which need more
                            room than the page does even on the desktop layout
    1100   wide desktop

   Adding an eighth is not forbidden, it is a decision: say which of these it
   is not, in a comment, next to it. */

/* ------------------------------------------------------------ tokens */
/* drives the rotating brand-foil ring (ask bar, ask panel) */
@property --holo-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* The page measure is fluid (see --page below), and three things do arithmetic
   on it: .shell, the ask bar and the cookie card, all of which want a number
   rather than an unresolved clamp(). Registering it as a length makes the
   computed value a real px — which is also what design-system.html prints on
   its ruler. It has to inherit: it is set on :root and read all the way down. */
@property --page {
  syntax: '<length>';
  inherits: true;
  initial-value: 1280px;
}

:root {
  /* The page's own ground, and the white of everything that sits on it.
     They were one colour and one token until the ground went off white; the
     split is what lets a card, a dropdown or a panel still read as raised.
     Anything full-bleed behind content takes --ground; anything that has to
     lift off the page keeps --canvas. */
  --ground: #ffffff;
  --canvas: #ffffff;
  /* the alternating band, one step deeper than the ground. Warm (#f4f2eb)
     while the ground was off white and the two belonged to one family; the
     ground is pure white now, and against white a band whose blue channel
     falls twice as far as its red reads as beige rather than as a shade of
     the page. Same lightness, all three channels level. */
  --band: #f4f4f4;
  /* The seam where the white header and the white footer meet the ground.
     It used to have an easy job: the ground was off white and this only had to
     stop white bleeding into it. The ground is white now, so the header and
     the page are the same colour and this line is the only thing between them
     — it carries the edge on its own rather than reinforcing a step that is
     already visible, so it is a little stronger than it was. Still under
     --hairline, because at that weight both edges read as rules across the
     page. Alpha rather than a hex, so it sits correctly on the ground and on
     the band. */
  --seam: rgba(0, 0, 0, .085);
  /* The dark, as a surface. --ink stays #000 because it is the text colour on
     nearly every page and a purple body copy is not what was asked for; this
     is the other half of the same split --ground / --canvas made, and for the
     same reason. Everything that is dark *behind* something — the announce
     ribbon, the ink bands, the closing panel, the dark buttons and chips, the
     mobile nav — takes this. Anything that is dark *as a mark* keeps --ink.
     The hue is the foil's violet carried nearly to black, so it belongs to the
     same family as the Insights covers and the flares in the closing panel.

     Chroma is what makes this read, not lightness. #191226 and #120c1e were
     both tried and both looked plain black on a screen: at this luminance a
     few points of separation between the channels is not enough for the eye to
     find a hue in — that is the whole difference between a dark purple and a
     black with a rumour of purple in it.

     That reasoning held while the dark was meant to be a purple. It is not
     any more: the studio asked for black, so this is black, and the argument
     above is kept because it is the map back if the hue is ever wanted again.
     #110820 was the last purple value — it is #170b2b taken to 75% of its
     light, and the way back is to scale all three channels together rather
     than walking them toward grey. */
  --void: #000000;
  /* White, again, and a second token for it on purpose. --canvas is a surface
     that lifts off the page; this is ink lying on a dark one. They are the
     same six characters today and they answer to different questions: if the
     dark grounds ever warm up, this moves and --canvas does not. Forty eight
     declarations said color: #fff and sixteen said background: #fff, which is
     one raw colour doing two jobs. What is left as a literal is mask and
     gradient stops, where the white is a mask value and not a colour. */
  --on-void: #ffffff;
  --cream: #fafafa;
  --ink: #000000;
  --charcoal: #333333;
  --deep: #161616;
  --fog: #8c8c8c;
  --mist: #cccccc;
  --hairline: #e5e5e5;
  --lilac: #c4c9dd;
  --bubblegum: #ff83da;
  --bubblegum-deep: #ff64d1;
  --cotton: #ffc5ee;
  --blush: #f7e1f7;
  --ice: #d2f4ff;
  --lime: #e8ff5b;
  --peach: #ffe3d1;
  --butter: #fdf0c0;
  --mint: #d8f4df;
  --peri: #e3e2fc;

  /* Each pastel at full strength, for when the colour has to carry as a line
     rather than as a fill. The pastels themselves disappear at stroke weight,
     and darkening them turns a bright page dour, so these go the other way:
     same hues, saturated rather than deepened. Set on the card that owns the
     icon: `--tone:var(--mint-ink)`. */
  --cotton-ink: #ef1e8f;
  --blush-ink:  #8b5cf6;
  --peach-ink:  #f2600c;
  --butter-ink: #d97706;
  --mint-ink:   #0ca678;
  --ice-ink:    #2e90fa;
  --peri-ink:   #6172f3;
  --lilac-ink:  #6172f3;

  --font: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-head: 'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;
  --font-title: 'Merriweather', Georgia, 'Times New Roman', Times, serif;

  /* The measure. 1280 is the grid the site was drawn on and it holds all the
     way to a 1440 laptop; above that the page takes a constant 90% of the
     screen, which is the same 80px margin a 1440 already had, widening gently
     as the screen does — 104px at 1920, 136px on a 2560 Studio Display. It
     used to stop dead at 1280, then at 1600, and both ceilings read the same
     way on a large monitor: a column of content with a hand's width of nothing
     down either side.

     Nothing below 1440 moves — 90vw - 16px is 1280 exactly at 1440, so the
     floor and the fluid term meet without a step.

     The ceiling is for ultrawides only: past ~2850 the rows stop being a page
     and start being a row of text with a long walk to the end of it. A 34"
     ultrawide gets 2560 and its margins; every normal monitor is on the 90%.

     Prose does NOT follow this. Reading columns are capped in ch wherever they
     sit in an fr column, so a wider page gives the layout more room without
     giving a sentence more characters. Grep `max-width: .*ch` before widening
     anything here. */
  --page: clamp(1280px, 90vw - 16px, 2560px);
  --gutter: 32px;
  --gap-section: 144px;

  --r-card: 4px;
  --r-panel: 8px;
  --r-pill: 9999px;
  /* The bottom sheet, and only that: the mobile nav, the role dialog's scroll
     area, the portal's two-pane, and the body behind an open dialog when it
     scales back. One shape in four places, and it was 20px in three of them
     and 22px in the fourth — the kind of drift nobody sees and everybody
     feels. Its own token rather than --r-panel, because a sheet is rounded
     against the viewport edge at a scale a panel never is. */
  --r-sheet: 20px;

  --ease: cubic-bezier(.22, 1, .36, 1);

  /* ---- BRAND GRADIENT ----------------------------------------------
     The Ultrabasic foil. This is the brand gradient: use it, don't invent
     another one, and don't substitute an even rainbow — the near-black
     anchors are load-bearing, they are what reads as metal rather than as
     a flag. One dark anchor per repeat, ~7% between hue stops.
     The list is written twice so it loops seamlessly however it is used:
     panned across a linear gradient (logo), or wrapped around a conic one
     (ask bar). Compose it as `linear-gradient(100deg, var(--brand-foil))`
     or use --brand-foil-linear below.
     Currently on: the header wordmark hover, the ask bar, the ask panel. */
  --brand-foil:
    #0a0c16 0%,  #1f7fc0 7%,  #4fd6ff 14%, #7f9cf5 21%,
    #a86bff 28%, #ff5cc4 35%, #ffb37a 42%, #6b4a52 47%, #0a0c16 50%,
    #1f7fc0 57%, #4fd6ff 64%, #7f9cf5 71%,
    #a86bff 78%, #ff5cc4 85%, #ffb37a 92%, #6b4a52 97%, #0a0c16 100%;
  --brand-foil-linear: linear-gradient(100deg, var(--brand-foil));
}

/* A dialog is open: the page behind it holds still. --sbw is the scrollbar it
   was using, handed over by site.js so removing it does not shift the layout. */
html.is-dialog { overflow: hidden; }
html.is-dialog body { padding-right: var(--sbw, 0px); }

/* ---- every dialog is a bottom sheet on a phone -------------------------- */
/* A box floating in the middle of a small screen wastes the edges and puts its
   controls where a thumb is not. Narrow, they all sit on the bottom edge the
   way the menu does: full width, rounded at the top, rising into place. */
@media (max-width: 640px) {
  dialog[open] {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    margin: 0;
    width: 100%;
    max-width: 100%;
    max-height: 92dvh;
    border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    border-bottom: 0;
    animation: sheet-up .4s var(--ease);
  }
  dialog[open]::backdrop { animation: sheet-dim .3s var(--ease); }
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: none; } }
@keyframes sheet-dim { from { opacity: 0; } to { opacity: 1; } }
@media (max-width: 640px) and (prefers-reduced-motion: reduce) {
  dialog[open] { animation: none; }
}

/* ------------------------------------------------------------ reset */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  font-weight: 400;
  font-feature-settings: 'liga' 1, 'kern' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
img, svg { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
h1, h2, h3, h4, p, dl, dd, dt, figure, blockquote { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
::selection { background: var(--cotton); color: var(--ink); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 2px; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

.shell { width: 100%; max-width: calc(var(--page) + var(--gutter) * 2); margin: 0 auto; padding: 0 var(--gutter); }
@media (max-width: 640px) { :root { --gutter: 20px; } }

/* ------------------------------------------------------------ type */
/* The heading tiers are vw-driven and every one of them used to stop at the
   size it reached on a 1440 laptop. That was right while the page stopped
   there too; against a measure that now follows the screen it left a 2288px
   page carrying 56px headings — a wide page set in small type, which reads
   worse than a narrow one. The ceilings are the old numbers times ~1.7, the
   same factor the page itself gains between 1440 and 2560, so a heading holds
   its share of the measure at every width. The vw terms are untouched, so
   nothing at or below 1440 moves.

   The rule, applied to every display ceiling in this file: a heading, a title
   or a figure runs on its vw term until 2560 and holds there, which is where
   --page stops too. So `max` is always the vw term times 25.6, and raising one
   means recomputing the other.

   Reading type is the exception and does not follow it. The lede stops at 24px
   and row copy at 19px, because past that a line is not more readable, only
   bigger — and every reading column is capped in ch, so a wider page gives a
   sentence more room without giving it more characters. */
.display {
  font-family: var(--font-title);
  font-size: clamp(42px, 4.6vw, 118px);
  line-height: 1.08;
  letter-spacing: -.012em;
  font-weight: 300;
}
.display b, .display .b, .h-xl b, .h-lg b, .h-md b { font-weight: inherit; }
.display .ln { display: block; overflow: hidden; padding: .16em 0; margin: -.16em 0; }
.display .ln > span { display: inline-block; }
.mark-pink {
  background: var(--cotton);
  color: var(--ink);
  padding: 0 .16em;
  border-radius: var(--r-card);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.h-xl {
  font-family: var(--font-title);
  font-size: clamp(38px, 4vw, 102px);
  line-height: 1.12;
  letter-spacing: -.01em;
  font-weight: 300;
}
.h-lg {
  font-family: var(--font-title);
  font-size: clamp(28px, 2.9vw, 74px);
  line-height: 1.16;
  letter-spacing: -.008em;
  font-weight: 300;
}
.h-md {
  font-family: var(--font-title);
  font-size: clamp(22px, 2vw, 51px);
  line-height: 1.25;
  letter-spacing: -.006em;
  font-weight: 300;
}
/* The lede is the one body-adjacent size that sits directly beside a heading
   everywhere on the site — hero, section head, row, card — so it rises with
   them rather than being stranded next to a 96px h2 on a large monitor. Every
   lede is capped in ch, so a bigger size widens the box and leaves the line
   the same number of characters long. 18px until ~1715, which is where 1.05vw
   overtakes it; a 1440 laptop is unchanged. */
.lead {
  font-size: clamp(18px, 1.05vw, 24px);
  line-height: 1.55;
  color: var(--charcoal);
}
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}
/* An eyebrow heading a run of prose on its own, outside a .sec-head: the
   case studies set "The challenge" and "Our approach" this way. */
.eyebrow--head { color: var(--fog); margin-bottom: 18px; }

/* No stranded last words in the reading tiers. `pretty` only moves the last
   line or two of a paragraph, so nothing above its end changes. */
.lead, .ovw-lede, .ovw-note, .wpanel-desc, .svcrow-desc, .capcell p, .assure-item p, .faq-a { text-wrap: pretty; }
/* The heading tier balances at every width, not only under 760 (the same
   list the 760 rule further down carries, for the reason it gives): the
   author's <br> stays where it is, only the overflow inside one of its
   halves evens out. Balance cannot help when the widest line is the forced
   one, so the three headings that still stranded a word at 1440 were broken
   by hand, the way every headline on the site is. The two inner-page heroes
   keep `pretty`, as they do under 760. */
body:not(.p-app) h2,
.display, .display .ln, .h-xl, .h-lg, .h-md,
.wpanel-title, .lead-title, .dossier-title, .pitem-name,
.rdhead-title, .fpanel-title, .case-next h2, .madlib,
.svcrow-title, .rack-title, .exprow-title, .gwband-line { text-wrap: balance; }
.pagehero .display, .case-hero .display { text-wrap: pretty; }

/* mid-level component titles + display numerals carry the working face.
   .h-md and .wpanel-title used to sit here; both run at headline sizes and
   have moved to the serif, so naming them here would only fight that. */
.svcrow-title,
.stat-num, .wpanel-stat .num,
.quote blockquote {
  font-family: var(--font-head);
}
.svcrow-title,
.stat-num, .wpanel-stat .num { font-weight: 600; }

/* ------------------------------------------------------------ buttons + links */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--r-pill);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .005em;
  padding: 15px 30px;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: background .22s var(--ease), color .22s var(--ease), border-color .22s var(--ease), transform .22s var(--ease);
}
.btn:active { transform: scale(.985); }
/* The inline arrow carries a viewBox and no dimensions, so its only limit was
   max-width:100% — fine in a button sized by its text, 190px across in one
   stretched to a phone's width. Components that want another size still set
   it: a class plus an element beats a bare class. */
.ar { width: 15px; height: 15px; flex: none; }
.btn--light { background: var(--canvas); color: var(--ink); }
.btn--light:hover { background: var(--bubblegum); color: var(--ink); }
.btn--dark { background: var(--void); color: var(--on-void); }
.btn--dark:hover { background: var(--bubblegum); color: var(--ink); }
.btn--ghost { border-color: var(--ink); color: var(--ink); background: transparent; }
.btn--ghost:hover { background: var(--bubblegum); border-color: var(--bubblegum); color: var(--ink); }
.btn--ghost-light { border-color: rgba(255,255,255,.9); color: var(--on-void); background: transparent; }
.btn--ghost-light:hover { background: var(--bubblegum); border-color: var(--bubblegum); color: var(--ink); }
.btn--sm { padding: 11px 22px; font-size: 12px; }

/* The tertiary CTA. Every text-and-arrow link on the site is one of these, so
   the underline lives here rather than being reproduced per page: a hairline
   rule that goes to ink on hover, and an arrow that slides. Buttons stay the
   primary and secondary; this is the third weight and the only one. */
.alink {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .005em;
  color: var(--ink);
}
/* underline on the label only: on the flex container it would run under the
   arrow too and move with it */
.alink > span, .alink-t {
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--mist);
  transition: text-decoration-color .25s var(--ease);
}
.alink:hover > span, .alink:hover .alink-t { text-decoration-color: var(--ink); }
.alink .ar { width: 14px; height: 14px; flex: none; transition: transform .25s var(--ease); }
.alink:hover .ar, a:hover .alink .ar { transform: translateX(4px); }

/* ------------------------------------------------------------ the technical layer */
/* Added 2026-09: the structural components behind the composed home page —
   giant thin measures, hairline-celled grids, glass chips standing on
   photographs, registration marks, the ink bands. Everything is deployed by
   hand, section by section, lives on the same three grounds the site already
   has — white, cream, ink — and hovers land on bubblegum like every other
   interactive surface here. Type stays in the two house faces; there is
   deliberately no mono voice in this layer. */

/* A statement: one editorial sentence at display scale, the working face
   rather than the serif, with the second thought dimmed. The dim span does
   the reading for you — the reference weight against the held claim. */
.statement-line {
  font-family: var(--font-title);
  font-size: clamp(30px, 3.2vw, 82px);
  line-height: 1.16;
  letter-spacing: -.01em;
  font-weight: 300;
  max-width: 26ch;
  text-wrap: pretty;
}
.statement-line .dim { color: var(--fog); }
.statement .alink { margin-top: clamp(26px, 2.6vw, 44px); }

/* ---- the ink band ------------------------------------------------------- */
/* A dark passage in the middle of a light page — but a panel, not a slab.
   It sits inside the shell with the same radius as the closing cta-panel and
   the work cards, wears the foil as a lit top edge the way the announce bar
   does, and holds grain at 4% so the ink reads as a material rather than as
   a hole. Full-bleed was tried first and looked pasted in from another site:
   this page's dark surfaces are all contained, so this one is too. */
.band {
  position: relative;
  isolation: isolate;
  overflow: clip;
  background: var(--void);
  color: var(--on-void);
  border-radius: var(--r-panel);
}
.band--seam::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1.5px;
  z-index: 2;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  animation: foilRibbon 9s linear infinite;
}
.band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: .04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.band > * { position: relative; z-index: 1; }
.band-pad { padding: clamp(56px, 6vw, 104px) clamp(28px, 5vw, 88px); }
.band .eyebrow { color: rgba(255, 255, 255, .55); }
.band .statement-line { color: var(--on-void); margin-top: clamp(22px, 2.4vw, 40px); }
.band .statement-line .dim { color: rgba(255, 255, 255, .42); }

/* ---- measures ----------------------------------------------------------- */
/* The proof row: numerals at their largest size on the site and their
   lightest weight, ruled apart by hairlines, captioned in the mono. The
   thinness is the point — a heavy numeral at this size is a poster, a thin
   one is an instrument reading. 200 is real: the variable face starts there. */
.measures {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0 clamp(28px, 3.2vw, 64px);
  margin-top: clamp(56px, 5.5vw, 100px);
}
.measure { min-width: 0; padding-left: clamp(28px, 3.2vw, 64px); border-left: 1px solid rgba(255, 255, 255, .16); }
.measure:first-child { padding-left: 0; border-left: 0; }
.measure-num {
  font-size: clamp(64px, 8.6vw, 220px);
  font-weight: 200;
  line-height: .95;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.measure-num .unit { font-size: .42em; font-weight: 300; color: rgba(255, 255, 255, .5); letter-spacing: 0; }
.measure-cap {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
  margin-top: 16px;
}
@media (max-width: 760px) {
  .measures { grid-template-columns: 1fr; gap: 0; }
  .measure { padding: 26px 0; border-left: 0; border-top: 1px solid rgba(255, 255, 255, .16); }
  .measure:first-child { padding-top: 0; border-top: 0; }
}

/* ---- capability grid ---------------------------------------------------- */
/* What we ship, as a ruled instrument panel: every cell hairlined, every
   label mono, an index in the corner of each. The grid lines are drawn once
   on the container and once per cell so they never double up. */
.capgrid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border: 1px solid rgba(255, 255, 255, .16);
  /* Rounded on the board, not by clipping it: the tiles lift out over their
     neighbours on hover, and overflow:hidden here would cut the edge tiles
     off at the frame. The cells are transparent at rest, so nothing shows
     past the curve, and a lifted tile carries its own --r-panel. */
  border-radius: var(--r-panel);
  margin-top: clamp(56px, 5.5vw, 100px);
}
/* ---- current in the traces ---------------------------------------------
   The grid is drawn like a circuit board, so the pulses run through its
   internal rules rather than lapping the frame: one shoots along the middle
   horizontal, and one drops down each vertical, staggered so the board
   fires a wire at a time — which wire, which direction and how fast is
   site.js rolling dice, so the board never repeats itself. Same foil comet
   as the quote frame's spark; the wires sit exactly on the grid's own
   hairlines (equal 1fr columns, equal min-height rows). Desktop only —
   narrow screens re-flow the grid and take the rules with them. */
.capgrid-wire {
  position: absolute;
  pointer-events: none;
  opacity: 0;
}
.capgrid-wire--h {
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: linear-gradient(90deg,
      transparent 0%, rgba(31, 127, 192, .3) 8%,
      #1f7fc0 16%, #4fd6ff 21%, #a86bff 25%,
      #ff5cc4 27.5%, #ffb37a 29%, transparent 30%) no-repeat;
  background-size: 30% 100%;
}
.capgrid-wire--v {
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: linear-gradient(180deg,
      transparent 0%, rgba(31, 127, 192, .3) 8%,
      #1f7fc0 16%, #4fd6ff 21%, #a86bff 25%,
      #ff5cc4 27.5%, #ffb37a 29%, transparent 30%) no-repeat;
  background-size: 100% 30%;
}
.capgrid-wire--v1 { left: 25%; }
.capgrid-wire--v2 { left: 50%; }
.capgrid-wire--v3 { left: 75%; }
/* The same wires on a panel's own four edges rather than a grid's internal
   rules: the about page's value panels carry one on each side. */
.capgrid-wire--t { top: 0; }
.capgrid-wire--b { top: auto; bottom: 0; margin-top: 0; margin-bottom: -1px; }
.capgrid-wire--l { left: 0; }
.capgrid-wire--r { left: auto; right: 0; margin-left: 0; margin-right: -1px; }
/* On a single panel the wire is a card edge, not a grid-wide rule, so the
   comet is set longer: a 30% tail on a 630px edge was a short spark, and
   on the 360px sides shorter still. */
.tenets .capgrid-wire--h { background-size: 58% 100%; }
.tenets .capgrid-wire--v { background-size: 100% 68%; }
@media (max-width: 760px) { .capgrid-wire { display: none; } }
@media (prefers-reduced-motion: reduce) { .capgrid-wire { display: none; } }
.capcell {
  position: relative;
  min-height: clamp(120px, 12.5vw, 210px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px 16px;
  text-align: center;
  border-right: 1px solid rgba(255, 255, 255, .16);
  border-bottom: 1px solid rgba(255, 255, 255, .16);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .78);
  transition: background .3s var(--ease), color .3s var(--ease);
  perspective: 520px;
}
/* the dust: a window's word erodes character by character — each letter
   lifts, drifts and blurs away on a left-to-right stagger, then the next
   word assembles into place. site.js splits the label into .fl-ch spans and
   drives them with the Web Animations API; the queue behind it carries
   everything the studio ships, so eight windows show the whole list */
.capcell-label { display: inline-block; }
/* Each window carries the object for whatever word is in it; site.js swaps
   the two together, so the drawing is never under the wrong label. Faces
   take the band's ink, the way every mark on a dark ground does. */
.capgrid:not(.capgrid--facts) .capcell { flex-direction: column; gap: 18px; }
.capgrid:not(.capgrid--facts) .capcell-mark {
  width: clamp(56px, 6vw, 84px);
  aspect-ratio: 1;
  --iso-fill: var(--ink);
}
/* the colour goes on the drawing itself: .iso declares its own, so a colour
   set on the parent never reaches it */
.capgrid:not(.capgrid--facts) .capcell-mark .iso {
  width: 100%;
  height: 100%;
  color: rgba(255, 255, 255, .78);
  transition: color .3s var(--ease);
}
.capgrid:not(.capgrid--facts) .capcell-mark .iso-face { transition: fill .3s var(--ease); }
/* No hover wash on the board: the windows are not links, and a ground that
   changes under the pointer promises a click. The window lifts instead —
   the whole tile grows a little and comes forward over its neighbours on a
   shadow, the drawing growing with it — which is a response, not an
   invitation. Opaque ink under it so the neighbours' rules pass behind. */
.capgrid:not(.capgrid--facts) .capcell { transition: transform .4s var(--ease), box-shadow .4s var(--ease), background .4s var(--ease); }
@media (hover: hover) {
  .capgrid:not(.capgrid--facts) .capcell:hover {
    transform: scale(1.07);
    z-index: 3;
    background: var(--void);
    box-shadow: 0 24px 48px rgba(0, 0, 0, .55), 0 0 0 1px rgba(255, 255, 255, .22);
    border-radius: var(--r-panel);
  }
}
@media (prefers-reduced-motion: reduce) {
  .capgrid:not(.capgrid--facts) .capcell:hover { transform: none; }
}
.iso-foil { position: absolute; width: 0; height: 0; overflow: hidden; }
[data-capicons] { display: none; }
.fl-w { display: inline-block; white-space: nowrap; }
.fl-ch {
  display: inline-block;
  will-change: transform, opacity, filter;
}
.capcell:nth-child(4n) { border-right: 0; }
.capcell:nth-child(n+5) { border-bottom: 0; }
@media (max-width: 760px) {
  .capgrid { grid-template-columns: repeat(2, 1fr); }
  .capcell:nth-child(4n) { border-right: 1px solid rgba(255, 255, 255, .16); }
  .capcell:nth-child(2n) { border-right: 0; }
  .capcell:nth-child(n+5) { border-bottom: 1px solid rgba(255, 255, 255, .16); }
  .capcell:nth-child(n+7) { border-bottom: 0; }
}

/* The facts variant: the same ruled panel carrying four short guarantees
   rather than eight labels. Cells open up — left aligned, a title and one
   line, no hover wash because nothing here is a link. */
.capgrid--facts { grid-template-columns: repeat(2, 1fr); margin-top: clamp(40px, 4.2vw, 72px); }
.capgrid--facts .capcell {
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  gap: 10px;
  min-height: 0;
  padding: clamp(26px, 2.8vw, 44px);
  text-transform: none;
  letter-spacing: normal;
  font-size: inherit;
  font-weight: inherit;
}
.capgrid--facts .capcell:nth-child(2n) { border-right: 0; }
.capgrid--facts .capcell:nth-child(-n+2) { border-bottom: 1px solid rgba(255, 255, 255, .16); }
.capgrid--facts .capcell:nth-child(n+3) { border-bottom: 0; }
.capgrid--facts .capnum { font-size: 11px; font-weight: 700; letter-spacing: .08em; color: rgba(255, 255, 255, .4); }
.capgrid--facts h3 { font-size: 17px; font-weight: 700; letter-spacing: -.016em; color: var(--on-void); }
.capgrid--facts p { font-size: 14px; line-height: 1.6; color: var(--mist); max-width: 44ch; }
@media (max-width: 640px) {
  .capgrid--facts { grid-template-columns: 1fr; }
  .capgrid--facts .capcell:nth-child(n) { border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .16); }
  .capgrid--facts .capcell:last-child { border-bottom: 0; }
}

/* ---- registration marks ------------------------------------------------- */
/* A hairline frame pinned at two corners by set squares, the way a plate is
   registered on a press. The wire rests faint; the current is the child
   .marks-spark — a ring-masked overlay carrying a comet of the foil's own
   hues, keyframed as one pulse: born at the top-left terminal, across the
   top edge, around the corner, down the right edge, and gone — then a beat
   of dark wire before the next. The angles are the frame's own corner
   angles (~±70° for this aspect), so the pulse really does run edge to
   edge rather than lapping. Feathered stops per the foil rules. */
.marks {
  position: relative;
  border: 1px solid rgba(255, 255, 255, .15);
  padding: clamp(28px, 3.6vw, 60px);
  margin-top: clamp(56px, 5.5vw, 100px);
}
.marks-spark {
  position: absolute;
  inset: -2px;
  pointer-events: none;
  border: 2px solid transparent;
  background: conic-gradient(from var(--holo-angle),
      transparent 0deg, transparent 296deg,
      rgba(31, 127, 192, .35) 316deg,
      #1f7fc0 330deg, #4fd6ff 342deg, #a86bff 350deg,
      #ff5cc4 356deg, #ffb37a 359deg,
      transparent 360deg) border-box;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  opacity: 0;
  animation: sparkrun 4.2s linear infinite;
}
/* angles stay monotonic — 468° is 108° after the full turn — or the
   interpolation runs the spark backwards around the left side */
@keyframes sparkrun {
  0%   { --holo-angle: 288deg; opacity: 0; }
  8%   { --holo-angle: 316deg; opacity: 1; }
  52%  { --holo-angle: 468deg; opacity: 1; }
  66%  { --holo-angle: 484deg; opacity: 0; }
  100% { --holo-angle: 484deg; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .marks-spark { display: none; } }
@media (prefers-reduced-motion: reduce) { .marks { animation: none; } }
.marks::before, .marks::after {
  content: "";
  position: absolute;
  width: 7px;
  height: 7px;
  background: var(--canvas);
}
.marks::before { top: -4px; left: -4px; }
.marks::after { bottom: -4px; right: -4px; }
/* The testimonial scene: one voice at a time. The photograph of the
   product being spoken about stands free beside the wired frame, and both
   swap together as the frame rotates through the clients — arrows by hand,
   a slow auto-advance otherwise (site.js). Slides stack in one grid cell so
   the frame never changes height mid-swap. */
.bq {
  display: grid;
  grid-template-columns: minmax(240px, 420px) minmax(0, 1fr);
  gap: clamp(36px, 4.5vw, 88px);
  align-items: center;
}
.bq-photo {
  overflow: hidden;
  border-radius: var(--r-card);
}
.bq-photo { display: grid; }
.bq-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.bq-photo img.is-on { opacity: 1; }
.bq-slides { display: grid; }
.bq-slide {
  grid-area: 1 / 1;
  opacity: 0;
  pointer-events: none;
  transition: opacity .5s var(--ease);
}
.bq-slide.is-on { opacity: 1; pointer-events: auto; }
/* the client's mark opens each voice — dummy tracked wordmarks until real
   logo files land, the same basis as the work plates' marks */
.bq-logo {
  display: block;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--on-void);
  margin-bottom: clamp(18px, 2vw, 30px);
}
.bq-nav {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 22px;
}
.bq-nav button {
  width: 34px;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, .25);
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--on-void);
  transition: background .25s var(--ease), border-color .25s var(--ease), color .25s var(--ease);
}
.bq-nav button:hover { background: var(--bubblegum); border-color: var(--bubblegum); color: var(--ink); }
.bq-nav svg { width: 13px; height: 13px; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .bq-photo img { animation: platedrift linear both; animation-timeline: view(); }
  }
}
@media (max-width: 860px) {
  .bq { grid-template-columns: 1fr; gap: 28px; }
  .bq-photo img { aspect-ratio: 16 / 10; }
}
.band-quote blockquote {
  font-size: clamp(21px, 2vw, 44px);
  line-height: 1.35;
  font-weight: 400;
  max-width: 44ch;
}
.band-quote blockquote b { font-weight: 650; }
.band-quote-who {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: clamp(24px, 2.6vw, 40px);
  font-size: 13px;
  font-weight: 600;
  color: rgba(255, 255, 255, .6);
}
.band-quote-who .alink { margin-left: auto; color: var(--on-void); }
@media (max-width: 640px) { .band-quote-who { flex-wrap: wrap; } .band-quote-who .alink { margin-left: 0; } }
/* The same scene on the white canvas: the wired frame in mist with ink
   terminals, the voice in ink, the arrows outlined in mist. The photograph
   and the spark need nothing. Used where a page already carries its ink
   passage and a second one would run the whole stretch black. */
.bq--light .marks { border-color: var(--mist); }
.bq--light .marks::before, .bq--light .marks::after { background: var(--ink); }
.bq--light .bq-logo { color: var(--ink); }
.bq--light .band-quote-who { color: var(--fog); }
.bq--light .band-quote-who .alink { color: var(--ink); }
.bq--light .bq-nav button { border-color: var(--mist); color: var(--ink); }


/* ---- chips on photographs ----------------------------------------------- */
/* The glass chip: a CTA that stands on the image itself rather than under it.
   Label on the left, the diagonal on the far right, real glass in between —
   blur, a lit top edge, a hairline. Pill-shaped like every other CTA on the
   site; the glass is what marks it as standing on a photograph. Inside a
   linked card it is a span and the card's hover drives it; on its own it is
   the link. */
.glasschip {
  position: absolute;
  left: 18px;
  bottom: 18px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
  min-width: 176px;
  padding: 12px 16px 12px 20px;
  border-radius: var(--r-pill);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: .005em;
  color: var(--on-void);
  background: rgba(20, 20, 20, .44);
  border: 1px solid rgba(255, 255, 255, .22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
  transition: background .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.glasschip .ar { width: 13px; height: 13px; flex: none; }
.glasschip:hover,
a:hover > figure .glasschip, a:hover > .glasschip,
.wpanel:hover .glasschip {
  background: var(--bubblegum);
  border-color: var(--bubblegum);
  color: var(--ink);
}

/* The photograph that rides with the cursor over the home service index.
   Fixed, inert, above the rows; site.js builds it only on fine pointers with
   motion allowed, so at rest — and on every phone — this rule costs nothing.
   The row's own pastel wash stays underneath it. */
.svcfloat {
  position: fixed;
  top: 0;
  left: 0;
  width: clamp(260px, 24vw, 420px);
  aspect-ratio: 16 / 10;
  border-radius: var(--r-panel);
  overflow: hidden;
  pointer-events: none;
  z-index: 40;
  opacity: 0;
  transition: opacity .3s var(--ease);
  will-change: transform;
}
.svcfloat img { width: 100%; height: 100%; object-fit: cover; }
.svcfloat.is-on { opacity: 1; }

/* ---- scroll drift ------------------------------------------------------- */
/* The hero plate breathes on scroll: the photograph pans a few percent inside
   its crop as the plate crosses the viewport. Scroll-driven, no script, and
   only where the engine and the visitor both allow it. The scale hides the
   pan; without it the drift would pull the edges into view. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-plate img {
      animation: platedrift linear both;
      animation-timeline: view();
    }
  }
}
@keyframes platedrift {
  from { transform: translateY(-3.5%) scale(1.09); }
  to { transform: translateY(3.5%) scale(1.09); }
}
/* ---- composed scenes --------------------------------------------------- */
/* The structural half of the technical layer: whole sections composed as
   scenes rather than as stacked blocks. One idea per viewport, negative
   space as the material, images at mixed scales. Home carries all of them;
   deploy elsewhere by the section, never by the page. */

/* The hero's CTA pair: wide pills with the label on the left wall and the
   glyph at the far right — the same silhouette as every button on the site,
   sized by floor plan rather than by text. Ink primary, bubblegum second;
   hovers trade grounds. */
.blockchip {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 30px;
  min-width: 178px;
  padding: 15px 18px 15px 22px;
  border-radius: var(--r-pill);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: .005em;
  transition: background .25s var(--ease), color .25s var(--ease), transform .25s var(--ease);
}
.blockchip:active { transform: scale(.98); }
.blockchip .ar { width: 13px; height: 13px; flex: none; }
.blockchip--dark { background: var(--void); color: var(--on-void); }
.blockchip--dark:hover { background: var(--bubblegum); color: var(--ink); }
.blockchip--pink { background: var(--bubblegum); color: var(--ink); }
.blockchip--pink:hover { background: var(--void); color: var(--on-void); }

/* The measurement scene: one sentence, then one number at the largest scale
   on the site, thin as wire, captioned in the mono. The air around it is the
   composition — do not put anything else in this section. */
.scene { padding: clamp(110px, 12vw, 210px) 0; }
.scene .eyebrow { color: var(--fog); }
.scene .statement-line { margin-top: clamp(22px, 2.4vw, 40px); }
.scene-num {
  font-size: clamp(96px, 15vw, 350px);
  font-weight: 200;
  line-height: .9;
  letter-spacing: -.04em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The composition is a diagonal: the sentence holds the top left, and the
   floor is one row — the partner marks standing on the left, the reading on
   the right — so neither half of the width is ever empty. Narrow, the row
   stacks and everything returns to the left rail. */
.scene-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 44px clamp(40px, 6vw, 120px);
  margin-top: clamp(64px, 7.5vw, 130px);
}
.scene-partners .eyebrow { color: var(--fog); }
.scene-partners .platforms-list { margin-top: 28px; }
@media (min-width: 901px) {
  .scene-num, .scene-cap { text-align: right; }
}
@media (max-width: 900px) {
  .scene-row { grid-template-columns: 1fr; }
  .scene-partners { order: 1; }
}
.scene-num .unit { font-size: .4em; font-weight: 300; color: var(--fog); letter-spacing: -.01em; }
.scene-cap {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--fog);
  margin-top: 18px;
  /* the caption must never be the widest thing in its column — unwrapped it
     was setting the column's width and squeezing the partner marks into a
     second line on the other side of the row */
  max-width: 36ch;
  margin-left: auto;
}
@media (max-width: 900px) { .scene-cap { margin-left: 0; } }

/* Full-bleed variants of the band: the dark passage runs seam to seam, one
   scene per section, stacked with no light between. The foil edge marks the
   passage's entry only, so it reads as one door rather than three. */
.band--bleed { border-radius: 0; }
.band-air { padding-top: clamp(100px, 11vw, 190px); padding-bottom: clamp(100px, 11vw, 190px); }
/* The passage's own seams, as opposed to its two ends. The full 190px is the
   air that gets a reader into the dark and back out of it, and it is right
   where the passage meets the light page above and below. Between two scenes
   inside it that figure lands twice — one band's bottom against the next
   band's top — and 380px of empty ink read as the page having stopped rather
   than as a beat between scenes. So an interior seam takes the sheet's other
   vertical rhythm, the one .band-pad already uses, on both sides of the join.
   Written against the neighbours rather than as a class, because it is the
   stacking that creates the doubling, not the scene. */
.band--bleed + .band--bleed > .band-air { padding-top: clamp(56px, 6vw, 104px); }
.band--bleed:has(+ .band--bleed) > .band-air { padding-bottom: clamp(56px, 6vw, 104px); }
.band .marks { max-width: min(100%, 980px); }


/* ------------------------------------------------------------ announcement */
.announce { background: var(--void); color: var(--on-void); position: relative; }
/* the brand foil, closing the announce ribbon. The ramp is written twice,
   so panning exactly one copy loops with no seam. */
.announce::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  background-position: 0% 50%;
  animation: foilRibbon 9s linear infinite;
}
@keyframes foilRibbon { to { background-position: 100% 50%; } }
.announce-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
  min-height: 44px;
  padding-top: 8px;
  padding-bottom: 8px;
}
.announce-in {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 500;
}
.announce-nav {
  flex: none;
  display: flex;
  align-items: center;
  gap: 24px;
  margin-left: auto;
}
.announce-nav a {
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: .01em;
  color: rgba(255,255,255,.7);
  transition: color .18s ease;
}
.announce-nav a:hover { color: var(--on-void); }
.announce b { font-weight: 700; }
.announce .dot { color: rgba(255,255,255,.45); }
.announce .ar { width: 14px; height: 14px; transition: transform .25s var(--ease); }
.announce-in:hover .ar { transform: translateX(4px); }
@media (max-width: 900px) {
  .announce-row { gap: 16px; }
  .announce-nav { gap: 16px; }
}
@media (max-width: 640px) {
  .announce b { display: none; }
  .announce .dot { display: none; }
  .announce-nav { display: none; }
}

/* ------------------------------------------------------------ skip link */
/* The first thing a keyboard reaches: one link past the announcement, the
   nav and the mega menu to the page itself. Off screen until it takes focus,
   then it drops in as the house pill, above the sticky bar. */
.skip {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 80;
  padding: 12px 20px;
  border-radius: var(--r-pill);
  background: var(--void);
  color: var(--on-void);
  font-size: 14px;
  font-weight: 600;
  transform: translateY(-200%);
  opacity: 0;
  transition: transform .25s var(--ease), opacity .25s var(--ease);
}
.skip:focus-visible {
  transform: none;
  opacity: 1;
  border-radius: var(--r-pill);
  outline-color: var(--bubblegum);
}

/* ------------------------------------------------------------ nav */
/* At rest the topbar is the page: the same off-white ground, no edge. Once
   the page scrolls under it (site.js sets .is-stuck past 4px) it turns white
   and takes a hairline, so the bar becomes a surface over the document
   exactly when there is a document under it to be over. The colour change is
   the same idea the box-shadow already carried and does most of the work now
   that the ground is not white: on white the two states were one tone and
   only the line said anything. It used to be liquid glass, blurred and translucent;
   the mega sheet dropping out of it is a white surface too, and two
   different whites, one of them tinted by whatever happened to be scrolling
   behind it, never agreed with each other.

   The hairline is the whole separation now. There was a soft drop shadow
   under it as well, which on a flat, hairline-ruled page was the one piece
   of depth on the screen — a bar floating over the document rather than
   part of it. A single line says the same thing without the lift. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--ground);
  transition: background .35s ease, box-shadow .35s ease;
}
.topbar.is-stuck { background: var(--canvas); box-shadow: 0 1px 0 var(--seam); }
.topbar-in {
  display: flex;
  align-items: center;
  height: 84px;
  gap: 48px;
}
/* `.holo-mark` is the reusable wordmark component; `.brand` is the site
   header's instance of it. The portal rails and auth screens mount their own
   (assets/js/portal/holo.js) — which is why every rule below is written as a
   selector list rather than being scoped to .brand.
   The sheen masks itself with the SVG file, so whatever sits underneath can
   stay an <img>; only the site header needs the mark inlined, and that is for
   currentColor, not for this. --holo-pool / --holo-bloom scale the spotlight
   to the mark: 72/44 is tuned for 138px, so a smaller mark overrides them. */
.brand, .holo-mark {
  flex: none;
  display: block;
  position: relative;
  --holo-x: 50%;
  --holo-y: 50%;
  --holo-bg: 50%;
  --holo-o: 0;
  --holo-rx: 0deg;
  --holo-ry: 0deg;
  --holo-pool: 72px;
  --holo-bloom: 44px;
  transform: perspective(760px) rotateX(var(--holo-rx)) rotateY(var(--holo-ry));
  transition: transform .38s var(--ease);
}
.brand svg { width: 138px; height: auto; color: var(--ink); }
.holo-mark > img, .holo-mark > svg { display: block; }

/* holographic sheen — masked to the wordmark, lit by the pointer */
.brand-holo, .holo-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--holo-o);
  transition: opacity .38s ease;
  -webkit-mask-image: url("../../ULTRABASIC.svg");
          mask-image: url("../../ULTRABASIC.svg");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
/* Oil slick, not a rainbow. Two soft layers — no hard stops anywhere:
   at 138px a 1px stripe aliases into a white slash, so every edge here
   is a feathered stop. The near-black bands are what read as metal;
   an even hue ramp reads as a flag. Spectrum listed twice at 200%
   so one pointer sweep loops seamlessly. */
.brand-holo::before, .holo-sheen::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    /* broad sheen — travels faster than the colour, so light appears
       to move across a surface rather than the surface itself moving */
    linear-gradient(100deg,
      rgba(255,255,255,0) 38%,
      rgba(255,255,255,.14) 46%,
      rgba(255,255,255,.52) 50%,
      rgba(255,255,255,.14) 54%,
      rgba(255,255,255,0) 62%),
    var(--brand-foil-linear);
  background-size: 260% 100%, 200% 100%;
  background-position: calc(var(--holo-bg) * 1.4) 50%, var(--holo-bg) 50%;
  /* Spotlight: only the letterforms under the cursor take the finish, the
     rest of the mark stays black. Two nested masks — the wordmark shape on
     the parent, this pool of light on the child — so no mask compositing.
     Nine stops on a gaussian-ish curve; three stops leave a visible rim. */
  -webkit-mask-image: radial-gradient(circle var(--holo-pool) at var(--holo-x) var(--holo-y),
    rgba(0,0,0,1) 0%,   rgba(0,0,0,.97) 17%, rgba(0,0,0,.89) 30%,
    rgba(0,0,0,.75) 42%, rgba(0,0,0,.56) 54%, rgba(0,0,0,.37) 65%,
    rgba(0,0,0,.20) 76%, rgba(0,0,0,.08) 87%, rgba(0,0,0,0) 100%);
          mask-image: radial-gradient(circle var(--holo-pool) at var(--holo-x) var(--holo-y),
    rgba(0,0,0,1) 0%,   rgba(0,0,0,.97) 17%, rgba(0,0,0,.89) 30%,
    rgba(0,0,0,.75) 42%, rgba(0,0,0,.56) 54%, rgba(0,0,0,.37) 65%,
    rgba(0,0,0,.20) 76%, rgba(0,0,0,.08) 87%, rgba(0,0,0,0) 100%);
}
/* soft bloom in the middle of the pool */
.brand-holo::after, .holo-sheen::after {
  content: "";
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  background: radial-gradient(circle var(--holo-bloom) at var(--holo-x) var(--holo-y),
    rgba(255,255,255,.22) 0%,  rgba(255,255,255,.16) 26%,
    rgba(255,255,255,.09) 46%, rgba(255,255,255,.04) 66%,
    rgba(255,255,255,.01) 84%, rgba(255,255,255,0) 100%);
}
.brand.is-holo, .holo-mark.is-holo { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .brand, .holo-mark { transform: none; }
}
.mainnav { display: flex; align-items: center; gap: 36px; margin-left: auto; }
.mainnav a {
  font-size: 15px;
  font-weight: 500;
  position: relative;
  padding: 6px 0;
  color: var(--fog);
  transition: color .18s ease;
}
.mainnav a:hover, .mainnav a.is-active { color: var(--ink); }
.mainnav a::after {
  content: "";
  position: absolute;
  left: 0; right: 100%; bottom: 0;
  height: 2px;
  background: var(--void);
  transition: right .28s var(--ease);
}
.mainnav a:hover::after, .mainnav a.is-active::after { right: 0; }
.topbar-cta { display: flex; align-items: center; gap: 24px; }

/* ---- the mega menu ---- */
/* Three of the four primary links open a sheet under the bar. The link
   itself still goes to its page, the sheet is only the shortcut, so nothing
   here is load bearing for navigation and every link inside it is real
   markup rather than something JavaScript builds.
   One element carries the background and the height; the panels cross fade
   inside it and it takes the height of whichever is showing. That way moving
   from Solutions to Products reads as the sheet changing shape, not as one
   sheet closing and another opening. Below 900px the whole thing is off and
   the mobile sheet carries the same links as accordions. */
.mainnav a.has-mega { display: inline-flex; align-items: center; gap: 6px; }
.navchev {
  width: 9px; height: 6px;
  flex: none;
  opacity: .45;
  transition: transform .3s var(--ease), opacity .2s ease;
}
.mainnav a.has-mega[aria-expanded="true"] { color: var(--ink); }
.mainnav a.has-mega[aria-expanded="true"] .navchev { transform: rotate(180deg); opacity: 1; }
.mainnav a.has-mega[aria-expanded="true"]::after { right: 0; }

.megawrap {
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  opacity: 0;
  background: var(--canvas);
  border-bottom: 1px solid var(--hairline);
  box-shadow: 0 26px 50px -30px rgba(0, 0, 0, .3);
  transition: height .42s var(--ease), opacity .2s ease, visibility .42s;
}
.megawrap.is-open { visibility: visible; opacity: 1; }

/* Panels stack on top of each other, so the sheet only ever has to size to
   one of them. Non active panels go visibility:hidden rather than opacity:0
   alone, or their links stay in the tab order behind the visible panel. */
.mega {
  position: absolute;
  left: 0; right: 0; top: 0;
  visibility: hidden;
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity .24s ease, transform .36s var(--ease), visibility .24s;
}
.mega.is-active { visibility: visible; opacity: 1; transform: none; }
.mega-in {
  display: grid;
  grid-template-columns: minmax(190px, 240px) minmax(0, 1fr) 300px;
  gap: 36px clamp(32px, 4vw, 64px);
  align-items: start;
  /* .shell owns the side gutter, so it has to be restated here rather than
     collapsed into a shorthand that drops it. */
  padding: 36px var(--gutter) 40px;
}
.mega-lede { max-width: 28ch; }
.mega-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 14px;
}
.mega-line {
  font-family: var(--font-title);
  font-size: 18px;
  line-height: 1.4;
  letter-spacing: -.008em;
  color: var(--ink);
  margin-bottom: 20px;
}

.mega-list {
  display: grid;
  grid-template-columns: repeat(var(--mcols, 2), minmax(0, 1fr));
  gap: 2px 14px;
}
.megaitem {
  display: block;
  position: relative;
  padding: 12px 14px;
  border-radius: var(--r-card);
  transition: background .22s ease;
}
/* The tone rule draws itself down the left edge on hover. It is the only
   colour in the sheet, one hue per service, so the list stays a list. */
.megaitem::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 2px; height: 0;
  border-radius: var(--r-pill);
  background: var(--tone, var(--ink));
  transition: height .3s var(--ease);
}
.megaitem:hover, .megaitem:focus-visible { background: var(--cream); }
.megaitem:hover::before, .megaitem:focus-visible::before { height: 62%; }
.megaitem-t {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.012em;
  color: var(--ink);
}
.megaitem-d {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--fog);
}

/* The photograph. Its src is held in data-src and swapped in on the first
   open (site.js), so a menu nobody touches costs nothing on page load. */
.megacard {
  display: block;
  position: relative;
  border-radius: var(--r-panel);
  overflow: hidden;
  background: var(--blush);
  isolation: isolate;
}
.megacard img {
  display: block;
  width: 100%;
  height: 212px;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.megacard:hover img { transform: scale(1.03); }
.megacard-body {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 16px 16px 15px;
  color: var(--on-void);
  background: linear-gradient(to top, rgba(0,0,0,.74), rgba(0,0,0,.3) 56%, rgba(0,0,0,0));
}
.megacard-tag {
  display: block;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
  margin-bottom: 6px;
}
.megacard-t {
  display: block;
  font-family: var(--font-title);
  font-size: 16px;
  line-height: 1.3;
}
.megacard-s {
  display: block;
  margin-top: 5px;
  font-size: 12px;
  color: rgba(255, 255, 255, .72);
}

/* Between 900 and 1120 the three columns stop fitting. The photograph is the
   part that can go: the links are the point of the sheet. */
@media (max-width: 1100px) {
  .mega-in { grid-template-columns: minmax(180px, 220px) minmax(0, 1fr); padding: 30px var(--gutter) 34px; }
  .megacard { display: none; }
}
@media (max-width: 900px) {
  .megawrap { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .megawrap, .mega, .megacard img, .megaitem::before { transition-duration: .01ms; }
}

/* ---- the toggle ---- */
/* A ring, so the control has an edge to press rather than three floating
   lines, and the bars cross into an X in place. */
.navtoggle {
  display: none;
  width: 42px; height: 42px;
  margin-left: auto;
  position: relative;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--canvas);
  transition: background .24s var(--ease), border-color .24s var(--ease);
}
.navtoggle span {
  position: absolute;
  left: 13px; right: 13px;
  height: 1.5px;
  background: var(--void);
  border-radius: 2px;
  transition: transform .38s var(--ease), opacity .18s ease, top .38s var(--ease);
}
.navtoggle span:nth-child(1) { top: 16px; }
.navtoggle span:nth-child(2) { top: 20px; }
.navtoggle span:nth-child(3) { top: 24px; }
.navtoggle[aria-expanded="true"] { border-color: var(--ink); }
.navtoggle[aria-expanded="true"] span:nth-child(1) { top: 20px; transform: rotate(45deg); }
.navtoggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] span:nth-child(3) { top: 20px; transform: rotate(-45deg); }

/* ---- the sheet ---- */
/* It used to be a short list pushed under the bar, which shoved the page down
   and read like a dropdown. It is a sheet now: it rises from the bottom edge,
   dark against the dimmed page, with the links where a thumb already is. */
.mobilenav {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 55;
  max-height: min(82vh, 720px);
  flex-direction: column;
  padding: 26px 22px calc(22px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--void);
  color: var(--on-void);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  box-shadow: 0 -20px 60px rgba(0, 0, 0, .4);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform .46s var(--ease), visibility .46s;
}
.mobilenav.is-open { transform: none; visibility: visible; }
/* The grab handle. Decoration, so it is not in the accessibility tree. */
.mobilenav::before {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  width: 38px;
  height: 4px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .22);
}
/* Direct children only: the address line at the bottom carries a mail link
   that has no business being a nav row. */
.mobilenav > a:not(.btn) {
  font-family: var(--font-head);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -.016em;
  color: var(--on-void);
  padding: 15px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  transition: color .2s ease, padding-left .3s var(--ease);
}
.mobilenav > a:not(.btn):active { color: var(--bubblegum); padding-left: 8px; }
.mobilenav > a:not(.btn):first-of-type { border-top: 1px solid rgba(255, 255, 255, .1); }
.mobilenav .btn {
  align-self: stretch;
  justify-content: center;
  margin-top: 22px;
  background: var(--canvas);
  border-color: var(--on-void);
  color: var(--ink);
}
/* ---- the mobile groups ---- */
/* The same three sets of links the mega sheet holds, as accordions. <details>
   rather than JavaScript: it opens with the keyboard, it opens with the script
   blocked, and the mobile sheet already closes itself on any link tap, which a
   summary is not. */
.mnav-group { border-bottom: 1px solid rgba(255, 255, 255, .1); }
.mnav-group > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  list-style: none;
  cursor: pointer;
  font-family: var(--font-head);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -.016em;
  color: var(--on-void);
  padding: 15px 2px;
}
.mnav-group > summary::-webkit-details-marker { display: none; }
.mnav-group > summary::after {
  content: "";
  flex: none;
  width: 8px; height: 8px;
  margin-right: 4px;
  border-right: 2px solid rgba(255, 255, 255, .5);
  border-bottom: 2px solid rgba(255, 255, 255, .5);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .28s var(--ease);
}
.mnav-group[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
.mnav-sub {
  display: flex;
  flex-direction: column;
  margin: 0 0 14px;
  padding-left: 14px;
  border-left: 1px solid rgba(255, 255, 255, .16);
}
.mnav-sub a {
  font-size: 15px;
  font-weight: 500;
  color: rgba(255, 255, 255, .68);
  padding: 9px 0;
  transition: color .2s ease, padding-left .3s var(--ease);
}
.mnav-sub a:active { color: var(--on-void); padding-left: 6px; }
@media (prefers-reduced-motion: reduce) {
  .mnav-group > summary::after, .mnav-sub a { transition-duration: .01ms; }
}

.mobilenav-foot {
  margin-top: 18px;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .55);
}
.mobilenav-foot a { color: var(--on-void); text-decoration: underline; text-underline-offset: 3px; }
/* The page behind it goes down, not away: the bar with the close control has
   to stay legible above the dim. */
html.is-navopen::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 54;
  background: rgba(0, 0, 0, .5);
  animation: navdim .3s var(--ease);
}
@keyframes navdim { from { opacity: 0; } to { opacity: 1; } }

@media (max-width: 900px) {
  .topbar-in { height: 64px; gap: 20px; }
  .brand svg { width: 116px; }
  .mainnav, .topbar-cta { display: none; }
  .navtoggle { display: block; }
  .mobilenav { display: flex; }
}
@media (prefers-reduced-motion: reduce) {
  .mobilenav { transition-duration: .01ms; }
}
/* The ask bar floats above everything, including this. It steps out while the
   sheet is open rather than hovering over the menu. */
html.is-navopen .ask { opacity: 0; pointer-events: none; }

/* ------------------------------------------------------------ hero · type monolith */
.hero { padding: 96px 0 0; }
/* The cap used to be 77px, reached at a 1426 viewport and held there forever.
   Against a page that now tracks the screen that left the headline shrinking
   inside its own column — which is the thing that actually makes a wide screen
   look thin, more than the margins do. 5.4vw runs all the way to 132px, so the
   headline holds the same share of the measure at 2560 that it holds at 1440.
   Nothing below 1426 moves: 5.4vw is still the operative term there.

   The aside is a share of the page rather than a flat 440px, for the same
   reason — pinned at 440 it became a strip against a 2288 page, with the lede
   wrapping every four words. At 30% it holds its 40ch lede on one measure at
   every width, and the lede's own size rises with it. */
.hero-mono .display {
  font-size: clamp(40px, 5.4vw, 138px);
  line-height: .96;
  letter-spacing: -.026em;
}
.hero-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 30%);
  gap: 40px clamp(40px, 6vw, 96px);
  align-items: end;
}
.hero-aside { padding-bottom: 10px; }
.hero-aside .lead { max-width: 40ch; }
.hero-aside .eyebrow { color: var(--fog); margin-bottom: 16px; }
.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 30px; }
@media (max-width: 900px) {
  .hero-top { grid-template-columns: 1fr; gap: 28px; }
  .hero-aside { padding-bottom: 0; }
}


/* ------------------------------------------------------------ hero plate · pills · joinline */
@keyframes logoscroll {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}
.hero-plate {
  position: relative;
  border-radius: var(--r-panel);
  overflow: hidden;
  height: clamp(432px, 58vh, 576px);
  background: var(--blush);
  margin-top: clamp(64px, 7.3vw, 105px);
}
/* site.js lays a canvas over the hero photograph and draws a lens under the
   pointer on it — see "Hero photograph · the pointer's lens" there. It is
   transparent everywhere the lens is not, so what shows through the rest of
   the plate is the photograph itself and nothing is swapped or faded. */
/* No z-index: the canvas is put straight after the <img> it stands in for, so
   what is stacked on the photograph — a caption chip, the contact card's shade
   and address — comes after it in the markup and stays over it. Giving this a
   layer of its own would lift it over all of them. */

/* One photograph. The plate used to cross-fade four of them behind a row of
   pills; it is a single image now, so nothing here is stacked or timed. */
.hero-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
}
/* `.hpill` is a general filter pill, documented in design-system.html. It
   kept the hero's rotation state until 20 Aug 2026 and no longer does. */
.hpill {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  border-radius: var(--r-pill);
  padding: 10px 18px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--on-void);
  background: rgba(20, 20, 20, .38);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: background .22s ease;
}
.hpill > span { position: relative; z-index: 1; }
/* The active pill used to carry a sweeping fill that timed the hero's
   rotation. With one photograph there is nothing to time, so an active
   pill is simply ink. */
.hpill.is-active { background: var(--void); }
button.hpill:hover { background: rgba(20, 20, 20, .58); }
button.hpill.is-active:hover { background: var(--void); }
@media (max-width: 900px) {
  /* 48 rather than the 44 this used to be, so the number is the eyebrow's own
     height + gap and the inner-page heroes below can do the same subtraction
     they do on the desktop. The 4px is not visible; sharing one number is. */
  .hero { padding-top: 48px; }
  .hero-plate { height: 460px; margin-top: 44px; }
  .pagehero-plate, .case-plate, .carephoto { height: 460px; }
}
@media (max-width: 640px) {
  .hero-mono .display { font-size: clamp(38px, 11.8vw, 56px); }
  .hero-plate { height: 403px; }
  .pagehero-plate, .case-plate, .carephoto { height: 403px; }
}

/* join line */

/* ---- platform partners ---------------------------------------------------- */
/* The trust band, directly under the line about ten thousand daily users —
   the one place on the page a reader is already asking who vouches for this.
   It shares that band rather than opening a section of its own, which is why
   the join line gives back most of its bottom padding when this follows it.

   Grey is done here, not in the files. Every vendor ships its mark in colour
   and under its own brand rules, so greyscale + a held-back opacity means an
   official SVG can be dropped straight in and still land in the house palette
   — no editing the asset, nothing to redo when a logo is refreshed.

   Heights are set on the img and widths left auto: these marks are different
   proportions and matching their widths would make the narrow ones huge. */
/* The partner marks live inside the measurement scene now (see .scene-row):
   label and logos on the left floor, the reading on the right. The old
   stand-alone centred strip is gone with its wrapper. */
.platforms-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(26px, 3.4vw, 56px);
}
/* One height for all four would be wrong. These marks are not the same shape:
   the AWS lockup stacks a wordmark over the smile and is 1.6 wide to 1 tall,
   Databricks is a single line at 6.9 to 1. Set to the same height the first
   renders 48px across and the second 206px, and the row reads as one logo
   shouting and one whispering.

   So the height is a base times a per mark factor, set on the <li> as --k and
   tuned by eye rather than by arithmetic — optical balance is what matters and
   no formula gets it right. The base still scales with the page. */
.platforms-list img {
  height: calc(clamp(24px, 1.9vw, 38px) * var(--k, 1));
  width: auto;
  filter: grayscale(1);
  opacity: .52;
  transition: opacity .28s var(--ease);
}
.platforms-list li:hover img { opacity: .78; }
@media (prefers-reduced-motion: reduce) {
  .platforms-list img { transition: none; }
}
/* one band, not two: the line above keeps its air, this takes the rest */
/* Four marks of four different widths wrap 3 + 1 on a phone, which reads as
   one of them having been left behind. Two even rows instead. */
@media (max-width: 640px) {
  .platforms { padding-bottom: 72px; }
  .platforms-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px 20px;
    justify-items: center;
  }
}

/* ------------------------------------------------------------ sections */
.section { padding: calc(var(--gap-section) / 2) 0; }
.section--flush-top { padding-top: 0; }
.section--flush-bot { padding-bottom: 0; }
.section--tint {
  background: var(--band);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  padding: var(--gap-section) 0;
}
.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  column-gap: clamp(40px, 6vw, 96px);
  align-items: end;
  margin: 0 0 80px;
}
.sec-head .eyebrow { grid-column: 1 / -1; color: var(--fog); margin-bottom: 18px; }
.sec-head h2 { grid-column: 1; }
.sec-head .lead {
  grid-column: 2;
  margin: 0 0 6px;
  max-width: 44ch;
  justify-self: end;
}
.faq-wrap .sec-head { display: block; }
.faq-wrap .sec-head .lead { margin-top: 18px; }
@media (max-width: 860px) {
  .sec-head { display: block; }
  .sec-head .lead { margin-top: 18px; }
}
@media (max-width: 760px) {
  :root { --gap-section: 96px; }
  .sec-head { margin-bottom: 40px; }
}

/* ------------------------------------------------------------ services index */
.svcindex { border-top: 1px solid var(--mist); }
.svcrow {
  position: relative;
  display: grid;
  grid-template-columns: 88px minmax(0, 1.1fr) minmax(0, 1fr) 64px;
  align-items: center;
  gap: 24px;
  padding: 44px 24px;
  margin: 0 -24px;
  border-radius: var(--r-card);
  transition: background .3s var(--ease);
}
.svcrow::after {
  content: "";
  position: absolute;
  left: 24px; right: 24px; bottom: 0;
  height: 1px;
  background: var(--mist);
}
.svcrow-num {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--fog);
  font-variant-numeric: tabular-nums;
  transition: color .3s ease;
}
.svcrow-title {
  font-size: clamp(22px, 2.2vw, 56px);
  line-height: 1;
  letter-spacing: -.016em;
  font-weight: 600;
  transition: transform .35s var(--ease);
}
.svcrow-desc {
  font-size: clamp(15px, .85vw, 19px);
  line-height: 1.5;
  color: var(--charcoal);
  max-width: 44ch;
}
.svcrow-go {
  justify-self: end;
  width: 44px; height: 44px;
  border-radius: var(--r-pill);
  border: 1px solid var(--mist);
  display: grid;
  place-items: center;
  background: transparent;
  transition: background .3s ease, border-color .3s ease, transform .35s var(--ease);
}
.svcrow-go svg { width: 16px; height: 16px; color: var(--ink); transition: color .3s ease, transform .35s var(--ease); }
.svcrow:hover { background: var(--wash, var(--blush)); }
.svcrow:hover::after { opacity: 0; }
.svcrow:hover .svcrow-num { color: var(--ink); }
.svcrow:hover .svcrow-title { transform: translateX(10px); }
.svcrow:hover .svcrow-go { background: var(--void); border-color: var(--void); transform: rotate(-45deg); }
.svcrow:hover .svcrow-go svg { color: var(--on-void); }
@media (max-width: 860px) {
  .svcrow {
    grid-template-columns: 34px minmax(0, 1fr) 40px;
    gap: 10px 14px;
    padding: 24px 16px;
    margin: 0 -16px;
  }
  .svcrow::after { left: 16px; right: 16px; }
  .svcrow-num { grid-column: 1; grid-row: 1; }
  .svcrow-title { grid-column: 2; grid-row: 1; }
  .svcrow-desc { grid-column: 2; grid-row: 2; max-width: none; }
  .svcrow-go { grid-column: 3; grid-row: 1 / 3; width: 40px; height: 40px; align-self: center; }
  .svcrow:hover .svcrow-title { transform: none; }
}

/* ------------------------------------------------------------ work posters */
.wstack { display: flex; flex-direction: column; gap: 48px; }
.wpanel {
  position: sticky;
  top: 108px;
  display: grid;
  grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
  min-height: min(600px, calc(100vh - 140px));
  border-radius: var(--r-panel);
  overflow: hidden;
}
.wpanel:nth-child(2) { top: 116px; }
.wpanel:nth-child(3) { top: 124px; }
.wpanel:nth-child(4) { top: 132px; }
.wpanel-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(44px, 5vw, 88px);
}
.wpanel-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 20px;
}
.wpanel-title {
  font-family: var(--font-title);
  font-size: clamp(26px, 2.9vw, 74px);
  line-height: 1.16;
  letter-spacing: -.008em;
  font-weight: 300;
  max-width: 16ch;
}
.wpanel-desc {
  font-size: 16px;
  line-height: 1.55;
  color: var(--charcoal);
  margin-top: 18px;
  max-width: 42ch;
}
.wpanel-stat {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid rgba(0, 0, 0, .14);
}
.wpanel-stat .num {
  font-size: clamp(48px, 5vw, 128px);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1;
  white-space: nowrap;
}
.wpanel-stat .cap {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--charcoal);
  max-width: 14ch;
  line-height: 1.35;
}
.wpanel .alink { margin-top: 34px; }
.wpanel-media { position: relative; overflow: hidden; }
.wpanel-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.wpanel:hover .wpanel-media img { transform: scale(1.03); }
.wpanel-pill {
  position: absolute;
  top: 20px; left: 20px;
  background: var(--canvas);
  border-radius: var(--r-pill);
  padding: 8px 16px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink);
}

/* surfaces */
.wpanel--deep { background: var(--void); }
.wpanel--deep .wpanel-title { color: var(--on-void); }
.wpanel--deep .wpanel-desc { color: var(--mist); }
.wpanel--deep .wpanel-tag { color: rgba(255, 255, 255, .55); }
.wpanel--deep .wpanel-stat { border-top-color: rgba(255, 255, 255, .18); }
.wpanel--deep .wpanel-stat .num { color: var(--on-void); }
.wpanel--deep .wpanel-stat .cap { color: var(--mist); }
.wpanel--deep .alink { color: var(--on-void); }
.wpanel--blush { background: var(--blush); }
.wpanel--cream { background: var(--cream); border: 1px solid var(--lilac); }
.wpanel--ice { background: var(--ice); }
.wpanel--blush .wpanel-tag, .wpanel--ice .wpanel-tag { color: rgba(0, 0, 0, .5); }

.work-more { margin-top: 48px; display: flex; justify-content: center; }

/* The phone stacks the posters the way the desktop does, pinned under the
   64px bar in 8px steps. A pinned panel taller than the room under the bar
   can never show its bottom edge, so the poster is kept short here: a 16/9
   picture, tighter copy, a smaller figure — about 580px, which clears the
   room a phone browser leaves once its own toolbars are counted. (A
   min-height guard was tried and read as the stack not being there at all,
   because phones report a viewport well short of their screen.) */
@media (max-width: 860px) {
  .wstack { gap: 20px; }
  .wpanel, .wpanel:nth-child(n) {
    position: sticky;
    top: 72px;
    grid-template-columns: 1fr;
    min-height: 0;
  }
  .wpanel:nth-child(2) { top: 80px; }
  .wpanel:nth-child(3) { top: 88px; }
  .wpanel:nth-child(4) { top: 96px; }
  .wpanel-media { aspect-ratio: 16 / 9; order: -1; }
  .wpanel-copy { padding: 24px 22px 30px; }
  .wpanel-stat .num { font-size: 40px; }
}

/* ------------------------------------------------------------ quote */
/* This used to stop at a flat 1160px, which was 90% of a 1280 page and read as
   a deliberate indent. Against a measure that goes to 1600 it stopped being an
   indent and became a hole: the quote ended a third of the way short of the
   rows above and below it, on the same left-aligned grid. It fills the measure
   now, and the side column is the narrower share so an eyebrow and a quote
   glyph are not sitting in 440px of column. */
.quote {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 3.1fr);
  gap: clamp(32px, 5vw, 88px);
  align-items: start;
}
.quote-side { display: flex; flex-direction: column; gap: 18px; }
.quote-side .eyebrow { color: var(--fog); }
.quote-glyph {
  font-family: var(--font-head);
  font-weight: 800;
  font-size: clamp(72px, 7vw, 178px);
  line-height: .6;
  color: var(--bubblegum);
  user-select: none;
}
.quote blockquote {
  font-size: clamp(23px, 2.4vw, 60px);
  line-height: 1.35;
  letter-spacing: -.014em;
  font-weight: 400;
}
.quote blockquote b { font-weight: 700; }
.quote-who {
  margin-top: 48px;
  padding-top: 32px;
  border-top: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  gap: 16px;
}
.quote-avatar {
  flex: none;
  width: 48px; height: 48px;
  border-radius: var(--r-pill);
  background: var(--cotton);
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .02em;
}
.quote-id .name { font-size: 15px; font-weight: 700; }
.quote-id .role { font-size: 13px; color: var(--fog); margin-top: 2px; }
.quote-who .alink { margin-left: auto; }
@media (max-width: 860px) {
  .quote { grid-template-columns: 1fr; gap: 20px; }
  .quote-glyph { font-size: 64px; }
  .quote-who { flex-wrap: wrap; }
  .quote-who .alink { margin-left: 64px; }
}

/* ------------------------------------------------------------ stats */
/* The results block, built on the products ledger's skeleton so the two read
   as one family: one figure leads with the foil inside its numerals, the rest
   fall in beside it as a ruled column, caption left and number right. It used
   to be four equal cells in a hairline grid, which gave "4.9 average rating"
   exactly the weight of the number the section was written to deliver.

   Where it differs from the ledger, and why. A ledger row carries three lines
   — caption, number, and a sentence of detail — so its rows have the height to
   stand beside a 176px lead. These rows carry two, and the first attempt made
   the grid rows equal fractions of the lead's height: three single lines each
   floating in 200px of nothing. So the rows size to their own content here and
   the lead is scaled to sit inside them, rather than the other way round. The
   figures are the same size relative to each other; the section is half the
   height.

   Rows are explicit because `grid-row: 1 / -1` counts explicit lines only —
   against an implicit grid the lead silently spans one row and the whole thing
   collapses back into cells. A page carries three figures or four. */
.stats {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 clamp(32px, 4.5vw, 80px);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.stats:has(.stat:nth-child(4)) { grid-template-rows: repeat(3, auto); }
.stats:has(.stat:nth-child(3)):not(:has(.stat:nth-child(4))) { grid-template-rows: repeat(2, auto); }

.stat { min-width: 0; }

/* ---- the lead figure --- */
.stat:first-child {
  grid-column: 1;
  grid-row: 1 / -1;
  /* column-reverse puts the caption over the number it names. The markup has
     the number first, and reversing here is cheaper than reordering six pages. */
  display: flex;
  flex-direction: column-reverse;
  justify-content: center;
  padding: 34px 0;
}
.stat:first-child .stat-cap { margin: 0 0 12px; }
.stat:first-child .stat-num {
  font-size: clamp(56px, 6.4vw, 164px);
  line-height: .95;
  letter-spacing: -.04em;
  /* The foil, clipped inside the numerals and panned slowly: the wordmark's
     own signature on a closed shape, which is the only place it is allowed.
     Same ink wash as the ledger, and for the same reason — the palest stops
     read as pastel and cost the figure its weight. */
  background:
    linear-gradient(100deg, rgba(0,0,0,.18), rgba(0,0,0,.18)),
    linear-gradient(100deg, var(--brand-foil));
  background-size: 100% 100%, 200% 100%;
  background-blend-mode: multiply, normal;
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: ledgerPan 14s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .stat:first-child .stat-num { animation: none; } }

/* ---- the supporting column --- */
.stat:not(:first-child) {
  grid-column: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: 20px;
  padding: 24px 0;
  padding-left: clamp(24px, 3vw, 52px);
  border-top: 1px solid var(--hairline);
  border-left: 1px solid var(--hairline);
}
.stat:nth-child(2) { border-top: 0; }
.stat:not(:first-child) .stat-cap { grid-column: 1; margin: 0; }
.stat:not(:first-child) .stat-num {
  grid-column: 2;
  grid-row: 1;
  font-size: clamp(30px, 3vw, 76px);
  letter-spacing: -.03em;
  line-height: 1;
  text-align: right;
}

.stat-num {
  font-size: clamp(52px, 6vw, 154px);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.stat-cap {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--charcoal);
  line-height: 1.35;
}

/* On cream the ink wash comes down: what gives the figure its weight on white
   starts to grey it on a warm ground. */
.section--tint .stat:first-child .stat-num {
  background:
    linear-gradient(100deg, rgba(0,0,0,.13), rgba(0,0,0,.13)),
    linear-gradient(100deg, var(--brand-foil));
  background-size: 100% 100%, 200% 100%;
  background-blend-mode: multiply, normal;
  -webkit-background-clip: text;
          background-clip: text;
}

@media (max-width: 860px) {
  /* One column. The lead stays first and stays large, because the hierarchy is
     the point; turning these back into a 2x2 grid at the width where the
     section is hardest to read would throw it away. */
  .stats,
  .stats:has(.stat:nth-child(3)),
  .stats:has(.stat:nth-child(4)) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    gap: 0;
  }
  .stat:first-child { grid-row: auto; padding: 28px 0 26px; }
  .stat:first-child .stat-num { font-size: clamp(52px, 15vw, 88px); }
  .stat:not(:first-child) {
    grid-column: 1;
    padding: 18px 0;
    border-left: 0;
    border-top: 1px solid var(--hairline);
  }
  .stat:nth-child(2) { border-top: 1px solid var(--hairline); }
  .stat:not(:first-child) .stat-num { font-size: clamp(28px, 7.5vw, 38px); }
}

/* ------------------------------------------------------------ products · the ledger */
/* The numbers behind the products, set so they read rather than tabulate.
   Four equal cells gave "2 products" the same weight as "10k+ people" and
   left every figure without its story. Here one number leads, large, with
   the foil inside its numerals (the wordmark's own signature, on a closed
   shape, which is where the foil is allowed to live), and three supporting
   figures stack beside it as a ruled column, each one a short sentence:
   the caption is the verb, the number is the object, the line under it is
   the detail a reader would ask for next. Still hairlines and the cream
   ground: it is the same page, it just reads as one thought. */
.ledger, .ledger dd, .ledger dt { margin: 0; }
.ledger {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 0 clamp(40px, 6vw, 104px);
  align-items: stretch;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.ledger-lead {
  padding: clamp(44px, 5vw, 72px) 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.ledger-cap {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--charcoal);
}
.ledger-num {
  margin-top: 14px;
  font-family: var(--font-head);
  font-size: clamp(72px, 11vw, 280px);
  font-weight: 700;
  letter-spacing: -.045em;
  line-height: .92;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* the lead figure carries the foil inside the glyphs: clipped, so nothing
   spills, and panned slowly so it has the life of the wordmark without the
   pointer. Reduced motion holds it still; it is still foil, just at rest. */
.ledger-num--foil {
  /* A light ink wash over the foil: on cream, the foil's palest stops (ice,
     lilac) read as pastel and the figure loses its weight. Multiplying a
     faint dark layer on top gives the numerals a touch more weight without
     taking the colour out of them. It is a light touch on purpose: at .5
     the whole figure went near black and stopped being foil at all. */
  background:
    linear-gradient(100deg, rgba(0,0,0,.18), rgba(0,0,0,.18)),
    linear-gradient(100deg, var(--brand-foil));
  background-size: 100% 100%, 200% 100%;
  background-blend-mode: multiply, normal;
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: ledgerPan 14s linear infinite;
}
@keyframes ledgerPan { to { background-position: 0 0, -200% 50%; } }
@media (prefers-reduced-motion: reduce) { .ledger-num--foil { animation: none; } }
.ledger-sub {
  margin-top: 18px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--charcoal);
  max-width: 34ch;
}
.ledger-lead .ledger-sub { margin-top: 26px; font-size: 16px; max-width: 40ch; }

/* the supporting column: three rows under one another, ruled between,
   number and caption on one line so each row reads left to right */
.ledger-rows {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-left: 1px solid var(--hairline);
  padding-left: clamp(32px, 4vw, 64px);
}
.ledger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "cap num" "sub num";
  column-gap: 24px;
  align-items: center;
  padding: 26px 0;
  border-top: 1px solid var(--hairline);
}
.ledger-row:first-child { border-top: 0; }
.ledger-row .ledger-cap { grid-area: cap; }
.ledger-row .ledger-sub { grid-area: sub; margin-top: 6px; font-size: 14px; }
.ledger-row .ledger-num {
  grid-area: num;
  margin-top: 0;
  font-size: clamp(40px, 4.2vw, 108px);
  letter-spacing: -.035em;
  line-height: 1;
  text-align: right;
}

@media (max-width: 960px) {
  .ledger { grid-template-columns: 1fr; }
  .ledger-lead { padding: 40px 0 36px; }
  .ledger-num { font-size: clamp(72px, 18vw, 132px); }
  .ledger-rows { border-left: 0; padding-left: 0; border-top: 1px solid var(--hairline); }
  .ledger-row { padding: 22px 0; }
  .ledger-row .ledger-num { font-size: clamp(36px, 9vw, 52px); }
}

/* ------------------------------------------------------------ products · list + preview */
.pswitch {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(40px, 5vw, 88px);
  align-items: center;
}
.plist { border-top: 1px solid var(--mist); }
.pitem { border-bottom: 1px solid var(--mist); }
.pitem-btn {
  width: 100%;
  text-align: left;
  padding: 36px 0;
  display: block;
}
.pitem-name {
  font-family: var(--font-head);
  font-weight: 600;
  letter-spacing: -.016em;
  line-height: 1;
  font-size: clamp(32px, 3.4vw, 86px);
  color: var(--mist);
  transition: color .25s ease, transform .35s var(--ease);
  display: inline-block;
}
.pitem-btn:hover .pitem-name { color: var(--charcoal); }
.pitem.is-on .pitem-name { color: var(--ink); }
.pitem-sub {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .35s var(--ease);
}
.pitem-sub > div { overflow: hidden; min-height: 0; }
.pitem.is-on .pitem-sub { grid-template-rows: 1fr; }
.pitem-subrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 18px;
  padding-bottom: 36px;
}
.pitem-tag { font-size: 15px; color: var(--charcoal); }
.pitem-stat {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 20px;
  letter-spacing: -.02em;
  white-space: nowrap;
}
.pitem-cap {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--charcoal);
}
.pitem-subrow .alink { margin-left: auto; }
/* The open row now: a short account of the product and the way in, no
   figure. The description holds a readable measure; the link sits on the
   same line where there is room and drops under it where there is not. */
.pitem-desc { margin: 0; font-size: 16px; line-height: 1.55; color: var(--charcoal); max-width: 52ch; text-wrap: pretty; flex: 1 1 32ch; }
.pitem-subrow { align-items: flex-end; gap: 14px 32px; }
/* Placeholder logos until each product's own arrives: a glyph and a wordmark
   in the heading face, each with its own habit of case and spacing. They
   take the row's colour: mist at rest, ink when open. */
.pitem-logo { display: inline-flex; align-items: center; gap: 12px; line-height: 1; color: var(--mist); transition: color .25s ease; }
.pitem-btn:hover .pitem-logo { color: var(--charcoal); }
.pitem.is-on .pitem-logo { color: var(--ink); }
.pitem-logo svg { width: clamp(24px, 2.2vw, 34px); height: clamp(24px, 2.2vw, 34px); flex: none; }
.pitem-logo span { font-family: var(--font-head); }
.pitem-logo--daypost span { font-size: clamp(24px, 2.4vw, 36px); font-weight: 800; letter-spacing: -.04em; }
.pitem-logo--vaaho span { font-size: clamp(23px, 2.3vw, 34px); font-weight: 800; letter-spacing: -.02em; }
.pitem-logo--vaaho i { font-style: normal; font-weight: 500; opacity: .6; }
.pitem-logo--dusk span { font-size: clamp(18px, 1.8vw, 26px); font-weight: 700; letter-spacing: .22em; text-transform: uppercase; }

.ppreview {
  position: relative;
  aspect-ratio: 4 / 3.3;
  border-radius: var(--r-panel);
  overflow: hidden;
  background: var(--cream);
}
.player {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .55s var(--ease);
}
.player.is-show { opacity: 1; }
.player img { width: 100%; height: 100%; object-fit: cover; }
.player-pill {
  position: absolute;
  top: 18px; left: 18px;
  background: var(--canvas);
  border-radius: var(--r-pill);
  padding: 8px 16px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.player--slot {
  border: 1.5px dashed var(--mist);
  border-radius: var(--r-panel);
  display: grid;
  place-items: center;
  background: var(--canvas);
}
.player--slot .pslot-plus {
  width: 60px; height: 60px;
  border-radius: var(--r-pill);
  background: var(--cotton);
  position: relative;
}
.player--slot .pslot-plus::before, .player--slot .pslot-plus::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  translate: -50% -50%;
  background: var(--void);
  border-radius: 1px;
}
.player--slot .pslot-plus::before { width: 20px; height: 2px; }
.player--slot .pslot-plus::after { width: 2px; height: 20px; }

/* On a phone the preview that stands beside the list on the desktop would
   sit above it, a screen away from the row it belongs to; so each row
   carries its own shot instead, which opens with the row. */
.pitem-shot { display: none; }
@media (max-width: 900px) {
  .pswitch { grid-template-columns: 1fr; gap: 0; }
  .ppreview { display: none; }
  .pitem-shot { display: block; position: relative; aspect-ratio: 16 / 10; border-radius: var(--r-panel); overflow: hidden; background: var(--cream); margin: 4px 0 18px; }
  .pitem-shot img { width: 100%; height: 100%; object-fit: cover; }
  .pitem-name { font-size: clamp(32px, 8vw, 44px); }
}


/* stacked players sit on top of each other, so only the shown one may take
   the pointer — without this a link inside a hidden player eats every click */
.player { pointer-events: none; }
.player.is-show { pointer-events: auto; }

/* ------------------------------------------------------------ solutions · the catalogue */
/* The ruled sheet: hairline-celled cells on white, sibling of the ink
   capgrid. Nothing sets it today — what a service covers moved onto the
   offer stack below — but the linked-cell affordances (hover wash, the go
   arrow) stay for whichever page next needs cells that are links. */
.svccat {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  border: 1px solid var(--mist);
  border-radius: var(--r-panel);
  overflow: clip;
}
.svccat-cell {
  --wash: var(--blush);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: clamp(300px, 26vw, 420px);
  padding: clamp(28px, 3vw, 52px);
  border-right: 1px solid var(--mist);
  border-bottom: 1px solid var(--mist);
  transition: background .35s var(--ease);
}
.svccat-cell:nth-child(2n) { border-right: 0; }
.svccat-cell:nth-child(n+5) { border-bottom: 0; }
.svccat-top { display: flex; align-items: center; justify-content: space-between; }
.svccat-num { font-size: 13px; font-weight: 700; color: var(--fog); }
.svccat-go {
  width: 44px;
  height: 44px;
  border: 1px solid var(--mist);
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .3s ease, border-color .3s ease, transform .35s var(--ease);
}
.svccat-go svg { width: 15px; height: 15px; color: var(--ink); transition: color .3s ease; }
.svccat-title {
  font-family: var(--font-head);
  font-weight: 600;
  letter-spacing: -.016em;
  line-height: 1.1;
  font-size: clamp(24px, 2.1vw, 48px);
}
.svccat-desc { font-size: 15px; line-height: 1.6; color: var(--charcoal); max-width: 46ch; }
.svccat-cell .dossier-list { margin-top: auto; padding-top: 10px; }
.svccat-cell:hover { background: var(--wash); }
.svccat-cell:hover .svccat-go { background: var(--void); border-color: var(--void); transform: rotate(-45deg); }
.svccat-cell:hover .svccat-go svg { color: var(--on-void); }
/* on the wash the tinted squares would vanish into their own colour */
.svccat-cell:hover .dossier-list { border-top-color: rgba(0, 0, 0, .12); }
.svccat-cell:hover .dossier-list li { border-bottom-color: rgba(0, 0, 0, .12); }
.svccat-cell:hover .dossier-list li::before { background: var(--canvas); }
@media (max-width: 860px) {
  .svccat { grid-template-columns: 1fr; }
  .svccat-cell, .svccat-cell:nth-child(2n) { border-right: 0; }
  .svccat-cell:nth-child(n+5) { border-bottom: 1px solid var(--mist); }
  .svccat-cell:last-child { border-bottom: 0; }
  .svccat-cell { min-height: 0; }
}


/* The offer stack: what a service covers, as a pile of panels rather than a
   ruled sheet. One offering per panel — its name, why it exists, the work
   people actually ask for under it — and a way in at the bottom that opens
   the contact form with that offering already named. The panels are the home
   page's work posters (.wpanel) in the service's own wash: same sticky
   pile-up, same 8px steps, so a detail page stacks the way the home page
   does, on the phone as well as the desktop. */
.offer-stack {
  display: flex;
  flex-direction: column;
  gap: 40px;
  margin-top: clamp(40px, 4.2vw, 72px);
}
.offer {
  /* what the mark's faces take at rest: the panel's own white */
  --iso-fill: #fff;
  position: sticky;
  top: 108px;
  display: flex;
  flex-direction: column;
  gap: clamp(32px, 3.6vw, 60px);
  min-height: min(600px, calc(100vh - 140px));
  padding: clamp(28px, 3.2vw, 56px);
  /* White on a hairline, the surface the values panels and the eligibility
     cards stand on. The four pastel washes went: beside the foil-coloured
     drawings they read as a second palette arguing with the first. The
     drawing carries the colour now, the section behind the pile takes the
     cream tint the values panels stand on, and a soft shadow under each
     panel keeps the pile reading as a deck. */
  background: var(--canvas);
  border: 1px solid var(--hairline);
  box-shadow: 0 -8px 40px rgba(0, 0, 0, .06);
  border-radius: var(--r-panel);
  /* The pile settles: as the next panel slides over this one, site.js writes
     how many panels now sit on top of it into --depth, and the panel eases
     back a few percent from its top edge, so the stack reads as a deck rather
     than a set of edges. `scale` rather than `transform`, because the reveal
     owns transform on the same element. Nothing moves under reduced motion:
     the script never runs and --depth stays unset. */
  transform-origin: 50% 0;
  scale: calc(1 - var(--depth, 0) * .035);
}
.offer:nth-child(2) { top: 116px; }
.offer:nth-child(3) { top: 124px; }
.offer:nth-child(4) { top: 132px; }
.offer-top { display: flex; align-items: flex-start; justify-content: space-between; }
/* The mark is one of the studio's own isometric objects (below), standing
   free on the wash at a size a line drawing needs. */
.offer-mark { width: clamp(84px, 9vw, 136px); aspect-ratio: 1; --iso-size: 110; }
.offer-num {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  color: rgba(0, 0, 0, .62);
  font-variant-numeric: tabular-nums;
}
.offer-body {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 54fr) minmax(0, 46fr);
  gap: 32px clamp(40px, 6vw, 128px);
}
.offer-copy { display: flex; flex-direction: column; min-width: 0; }
/* the titles are broken by hand, like every headline on the site */
.offer-desc {
  margin-top: auto;
  padding-top: 32px;
  font-size: clamp(16px, 1.05vw, 19px);
  line-height: 1.55;
  color: var(--charcoal);
  max-width: 40ch;
  text-wrap: pretty;
}
.offer-side { display: flex; flex-direction: column; min-width: 0; }
.offer-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, .62);
  margin-bottom: 12px;
}
.offer-list { border-top: 1px solid rgba(0, 0, 0, .16); }
.offer-list li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 2px;
  border-bottom: 1px solid rgba(0, 0, 0, .1);
  font-family: var(--font-head);
  font-size: clamp(15px, 1.15vw, 19px);
  font-weight: 600;
  letter-spacing: -.012em;
}
/* the tinted square the item-by-item list carried, gone white on the wash */
.offer-list li::before {
  content: "";
  width: 9px; height: 9px;
  border-radius: 3px;
  background: var(--canvas);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .14);
  flex: none;
}
.offer-cta {
  margin-top: auto;
  padding-top: 30px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 22px;
}
.offer-cta-note { font-size: 13px; line-height: 1.5; color: var(--charcoal); max-width: 26ch; }
/* Same pile on the phone as the posters above: pinned under the bar in 8px
   steps, the card kept short enough to show its foot. */
@media (max-width: 860px) {
  .offer-stack { gap: 20px; }
  .offer, .offer:nth-child(n) { position: sticky; top: 72px; min-height: 0; padding: 24px 22px 28px; gap: 24px; }
  .offer:nth-child(2) { top: 80px; }
  .offer:nth-child(3) { top: 88px; }
  .offer:nth-child(4) { top: 96px; }
  .offer-body { grid-template-columns: 1fr; gap: 26px; }
  .offer-desc { padding-top: 16px; }
  .offer-cta { padding-top: 22px; }
}

/* ---- the studio's own icons: isometric line objects --------------------- */
/* Drawn by scripts/isoicons.py: solids in true isometric projection, one
   stroke, two states. At rest the faces take the surface they stand on
   (--iso-fill), which is what hides the edges behind them. On hover the
   object lights up in the card's tone: three tints on the three faces, top
   lightest, as if lit from above, every one paler than the stroke so no
   edge is lost, and the stroke and the title take the tone. (A pixel
   checker was tried first and read as clutter at this size.) The stroke is
   a weight in drawing units worked out from the mark's rendered size
   (--iso-size, in px, unitless), so it lands near 1.3px wherever it stands.
   It used to be vector-effect: non-scaling-stroke, but WebKit paints a
   userSpaceOnUse gradient stroke in the wrong space under that effect, and
   every line on iPhone came out the ramp's first blue. */
.iso {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  color: var(--ink);
  transition: color .25s var(--ease);
}
.iso path {
  fill: none;
  stroke: currentColor;
  stroke-width: calc(1.3px * 64 / var(--iso-size, 80));
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* one class deeper than `.iso path`, which would otherwise win with fill: none */
/* The drawings arrive in the brand foil: the line on the bright ramp, the
   three faces on ramps a step deeper each, all opaque so a face still hides
   what stands behind it. Each drawing carries its own four ramps, fitted to
   its own extent (--foil-*, set on the svg by scripts/isoicons.py), so the
   whole sweep lands on every object; the page-level ramps are the fallback
   for a drawing stamped before that. --iso-fill stays the fallback for
   anywhere no gradients are stamped in (the portal sets its own). */
.iso path { stroke: var(--foil-line, url(#isofoil)); }
.iso .iso-face { fill: var(--iso-fill, #fff); transition: fill .3s var(--ease); }
.iso .iso-top   { fill: var(--foil-top, url(#isofoiltop)); }
.iso .iso-left  { fill: var(--foil-left, url(#isofoilleft)); }
.iso .iso-right { fill: var(--foil-right, url(#isofoilright)); }
.iso .iso-dash { stroke-dasharray: 3 3; }
/* Hovering a card grows its drawing a little. That is the whole hover now:
   the foil is already there, so there is nothing left to reveal. */
.iso { transform-origin: 50% 50%; transition: transform .3s var(--ease); }
@media (hover: hover) {
  :is(.offer, .tenet, .hline, .capcell, .route, .perk, .term, .assure-item, .qual-item):hover .iso { transform: scale(1.12); }
}
@media (prefers-reduced-motion: reduce) {
  .iso { transition: none; }
  :is(.offer, .tenet, .hline, .capcell, .route, .perk, .term, .assure-item, .qual-item):hover .iso { transform: none; }
}
.offer-title { transition: color .25s var(--ease); }
/* On the ink band the marks stand in white and their faces take the ink.
   No hover there: the band's cells are facts, not controls. */
.capcell-mark { width: clamp(60px, 6vw, 84px); aspect-ratio: 1; margin-bottom: 20px; --iso-size: 72; }
.capgrid--facts .capcell { --iso-fill: var(--ink); }
.capgrid--facts .capcell .iso { color: var(--on-void); }
@media (hover: hover) {
  }


/* ------------------------------------------------------------ faq */
.faq-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}
@media (max-width: 860px) { .faq-wrap { grid-template-columns: 1fr; } }
.faq-item { border-bottom: 1px solid var(--mist); }
.faq-item:first-child { border-top: 1px solid var(--mist); }
.faq-q {
  width: 100%;
  padding: 32px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  text-align: left;
  /* a question is reading type, not a heading, so it rides the lede's curve
     rather than a display ceiling — otherwise it is an 18px line crossing a
     2288px row under a 102px h2 */
  font-size: clamp(18px, 1.05vw, 24px);
  font-weight: 500;
  letter-spacing: -.012em;
  line-height: 1.35;
}
.faq-mark {
  flex: none;
  width: 28px; height: 28px;
  border-radius: var(--r-pill);
  background: var(--cotton);
  position: relative;
  transition: background .2s ease;
}
.faq-q:hover .faq-mark { background: var(--bubblegum); }
.faq-mark::before, .faq-mark::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  background: var(--void);
  border-radius: 1px;
  translate: -50% -50%;
}
.faq-mark::before { width: 12px; height: 2px; }
.faq-mark::after { width: 2px; height: 12px; transition: transform .25s var(--ease); }
.faq-item.is-open .faq-mark::after { transform: scaleY(0); }
.faq-a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .32s var(--ease);
}
.faq-a > div { overflow: hidden; min-height: 0; }
.faq-item.is-open .faq-a { grid-template-rows: 1fr; }
.faq-a p {
  font-size: 16px;
  line-height: 1.55;
  color: var(--charcoal);
  padding-bottom: 28px;
  max-width: 60ch;
}

/* ------------------------------------------------------------ closing cta */
.cta-panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--void);
  color: var(--on-void);
  border-radius: var(--r-panel);
  padding: clamp(48px, 5.2vw, 84px) clamp(28px, 5vw, 88px);
  text-align: center;
}
.cta-shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 1.2s ease;
  pointer-events: none;
}
.cta-shader.is-live { opacity: 1; }
.cta-panel > *:not(.cta-shader) { position: relative; z-index: 1; }
/* the sentence owns the open dropdown, so it has to sit above its later siblings */
.cta-panel > .madlib { z-index: 5; }
.cta-panel .display { color: var(--on-void); margin: 0 auto; max-width: 14ch; }
.cta-panel .lead { color: var(--mist); max-width: 46ch; margin: 24px auto 0;
  /* two even lines: on a laptop and at 1024 the closing line left "for it."
     stranded by itself */
  text-wrap: balance;
}
.cta-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 14px; margin-top: 40px; }
.cta-meta {
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, .16);
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px 48px;
  font-size: 14px;
  color: var(--mist);
}
.cta-meta a { color: var(--on-void); font-weight: 500; }
.cta-meta a:hover { color: var(--cotton); }

/* ------------------------------------------------------------ solutions · dossiers */
/* Six hairline-separated editorial spreads — an open ledger, not cards, so a
   solution cannot be mistaken for a case study (.wpanel), the home index
   (.svcrow) or the retired bento. Each spread owns one wash colour: it tints
   the fit-note chip and the checklist squares — never the whole band. */
/* the first hairline doubles as the hero's closing rule, so it needs the
   breathing room the section's own padding-top no longer provides */
.dossiers { margin-top: clamp(56px, 6vw, 88px); }
.dossier {
  --wash: var(--blush);
  border-top: 1px solid var(--mist);
  padding: clamp(52px, 6vw, 92px) 0;
}
.dossier-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: 32px clamp(40px, 6vw, 104px);
  align-items: stretch;
}
.dossier-title {
  font-family: var(--font-title);
  font-size: clamp(30px, 3.2vw, 82px);
  font-weight: 300;
  letter-spacing: -.008em;
  line-height: 1.14;
  max-width: 15ch;
}
.dossier-desc {
  font-size: 16px;
  line-height: 1.6;
  color: var(--charcoal);
  max-width: 52ch;
  margin-top: 18px;
}
.dossier-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 32px;
  margin-top: 30px;
  border-top: 1px solid var(--hairline);
}
.dossier-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 14px;
  font-weight: 500;
}
.dossier-list li::before {
  content: "";
  flex: none;
  width: 9px; height: 9px;
  border-radius: 2px;
  background: var(--wash);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .14);
}
/* The qualifier is the "is this me?" line, so it gets sentence-level presence
   rather than a container: the house marker highlight (.mark-pink) carrying
   the solution's own wash, set larger than the body copy around it. */
.dossier-fit {
  margin-top: 34px;
  font-family: var(--font-head);
  font-size: clamp(18px, 1.7vw, 21px);
  line-height: 1.45;
  letter-spacing: -.018em;
  font-weight: 500;
  max-width: 34ch;
  text-wrap: pretty;
}
.dossier-fit b {
  font-weight: 700;
  background: var(--wash);
  border-radius: var(--r-card);
  padding: .08em .28em;
  margin-left: -.28em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.dossier-copy > .btn { margin-top: 30px; }
/* the photograph always leads and the copy always follows — the markup keeps
   the copy first for reading order, so the image is pulled left with order */
.dossier-media {
  order: -1;
  position: relative;
  border-radius: var(--r-panel);
  overflow: hidden;
  min-height: 440px;
}
.dossier-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.dossier:hover .dossier-media img { transform: scale(1.03); }
.dossier-pill {
  position: absolute;
  top: 18px; left: 18px;
  background: var(--canvas);
  border-radius: var(--r-pill);
  padding: 8px 16px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
@media (max-width: 900px) {
  .dossier-grid { grid-template-columns: 1fr; }
  .dossier-media { min-height: 0; aspect-ratio: 16 / 10; }
  .dossier-list { grid-template-columns: 1fr; }
}

/* ---- solutions · the overview under the hero photograph ------------------ */
/* No headline. The page already has one four lines above it, and a second one
   this close was answering a question nobody had asked yet — the reader has
   just been told what the service is called and is looking for what it means,
   not for another title.

   The claim and its explanation, side by side rather than stacked. Two sizes
   do the work a heading would have done: the left column is the thing being
   said, the right is the detail behind it, and because they sit on one line
   the block stays short and fills the measure without a panel or a rule
   around it. Ranged left like everything else on the page — a centred version
   of this read as a mission statement, which is not what it is.

   No cream ground either. The section above it is a photograph and the one
   below opens with a 100px serif heading; a box between them was a third
   object competing in a space that only needed two. */
.ovw {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 0 clamp(40px, 5vw, 96px);
  align-items: start;
}
/* block rather than the shared inline-flex, so the margin under it counts */
.ovw .eyebrow {
  grid-column: 1 / -1;
  display: flex;
  width: fit-content;
  color: var(--fog);
  margin-bottom: 28px;
}
/* The working face, not the serif: this is a sentence about handoffs and
   loading spinners, and Merriweather at this size made it read like a pull
   quote. 500 because a light sans this large looks under-inked, and Jakarta
   opens up as it grows, hence the tighter tracking. */
.ovw-lede {
  font-family: var(--font-head);
  font-weight: 500;
  font-size: clamp(21px, 1.85vw, 44px);
  line-height: 1.3;
  letter-spacing: -.024em;
  text-wrap: balance;
}
/* the half em holds it on the big line's baseline rather than its box top */
.ovw-note {
  padding-top: .5em;
  max-width: 44ch;
  font-size: clamp(15px, 1vw, 18px);
  line-height: 1.7;
  color: var(--charcoal);
}
@media (max-width: 900px) {
  .ovw { grid-template-columns: minmax(0, 1fr); }
  .ovw-note { margin-top: 24px; padding-top: 0; }
}

/* ------------------------------------------------------------ solutions · guarantees */
.assure {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 40px clamp(28px, 3vw, 48px);
}
.assure-item { border-top: 1px solid var(--mist); padding-top: 24px; }
.assure-mark {
  display: block;
  width: 11px; height: 11px;
  border-radius: 3px;
  background: var(--bubblegum);
  margin-bottom: 20px;
}
/* Four points that each name a different piece of the work, all pointed at by
   the same 11px square, said "here is a thing" four times and nothing more. A
   drawn glyph says which thing. The square is still the fallback — a mark with
   no svg in it is untouched — so a page whose glyphs are not written yet keeps
   the dot rather than an empty box.

   54px and .95 are not new numbers: they are exactly .icotile's, which is the
   site's icon size. Same grid, same weight, so the two components draw the
   same hand. .95 rather than the 1.5 in the markup because the stroke scales
   with the box — 1.5 at 54px renders over three pixels and stops reading as a
   drawn line. The attribute stays 1.5 so a glyph lifted out of here into a
   smaller context still looks right.

   Colour comes from the accent inks, not the pastels: at stroke weight a
   pastel disappears, which is the whole reason the -ink set exists. The pink
   one is the default, so a converted mark lands on the colour the square
   already was; a page with its own tone sets --tone on .assure and the row
   follows. */
.assure-mark:has(svg) {
  width: 54px; height: 54px;
  border-radius: 0;
  background: none;
  margin-bottom: 18px;
  color: var(--tone, var(--cotton-ink));
}
.assure-mark svg { width: 100%; height: 100%; stroke-width: .95; }
.assure-item h3 {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.012em;
  line-height: 1.3;
}
.assure-item p {
  font-size: 14px;
  line-height: 1.55;
  color: var(--charcoal);
  margin-top: 10px;
}
@media (max-width: 900px) { .assure { grid-template-columns: repeat(2, 1fr); gap: 32px 24px; } }
@media (max-width: 540px) { .assure { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ solutions · engagement models */
.models {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  align-items: stretch;
}
.model {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  background: var(--canvas);
  padding: clamp(28px, 2.8vw, 40px);
}
/* The recommended tier is ringed in the Ultrabasic foil over a white fill —
   the same padding-box / border-box composite the ask bar uses, so every foil
   surface on the site still composes from the one --brand-foil token. Slower
   than the ask bar's rotation: this is a much larger ring to travel. */
.model--hot {
  border: 2px solid transparent;
  background:
    linear-gradient(#fff, #fff) padding-box,
    conic-gradient(from var(--holo-angle), var(--brand-foil)) border-box;
  animation: askHolo 9s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .model--hot { animation: none; }
}
.model-flag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 20px;
}
.model h3 {
  font-family: var(--font-head);
  font-size: clamp(22px, 2vw, 50px);
  font-weight: 600;
  letter-spacing: -.016em;
  line-height: 1.1;
}
.model-sub {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--charcoal);
  margin-top: 12px;
  max-width: 38ch;
}
.model-facts { margin-top: 28px; }
.model-facts > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-top: 1px solid rgba(0, 0, 0, .1);
  font-size: 13.5px;
}
.model-facts dt { color: var(--fog); font-weight: 600; flex: none; }
.model-facts dd { font-weight: 600; text-align: right; }
.model-cta { margin-top: auto; padding-top: 32px; }
@media (max-width: 900px) { .models { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ contact */
/* The desk. A white writing panel carries the form; a rail beside it
   carries the real ways to reach a person. Below that, the same grammar
   the rest of the site uses — wash cards, hairline steps, the FAQ list.
   No new colours, no new radii. */

/* ---- the fold ----
   Three bands stack between the header and the desk: the hero's own 96px of
   clear air, the gap above the fact strip, and the first section's padding.
   Each was sized against viewport *width*, which is the wrong axis for
   vertical air: a wide, short laptop screen scores maximum on all three and
   pays for it in the one dimension it has least of. The result was a screen
   of hero with the desk pushed entirely below the fold, under visibly empty
   space.

   So each band now takes the smaller of its width-based and height-based
   measure. A tall screen keeps exactly the spacing this was drawn with; a
   short one gives the room back to the content. Nothing is capped lower than
   before, so nothing gets tighter than the design allows.

   The hero is identified by its fact strip rather than a page class, the same
   way the eyebrow variant identifies itself further up. */
.pagehero:has(.cmeta) { padding-top: clamp(40px, min(96px, 9vh), 96px); }
.pagehero:has(.cmeta) + .section { padding-top: clamp(28px, min(72px, 6vh), 72px); }

/* ---- hero fact strip ---- */
.cmeta {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(28px, 3vw, 48px);
  margin-top: clamp(48px, min(5vw, 6vh), 72px);
}
.cmeta > div { border-top: 1px solid var(--mist); padding-top: 20px; }
.cmeta dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--fog);
}
.cmeta dd {
  font-family: var(--font-head);
  font-size: clamp(15px, 1.4vw, 17px);
  font-weight: 600;
  letter-spacing: -.012em;
  margin-top: 8px;
}
@media (max-width: 760px) {
  .cmeta { grid-template-columns: 1fr; }
  .cmeta > div { padding: 16px 0; }
}

/* ---- the desk · form + rail ---- */
.cdesk {
  display: grid;
  grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
  gap: clamp(28px, 3vw, 48px);
  /* stretch, not start: the rail used to stop 120px above the panel's bottom
     edge, which read as a column that had run out rather than as a decision */
  align-items: stretch;
}

/* ---- left · the writing panel ---- */
.cpanel {
  /* White, not cream: the panel is the one thing on this page a reader is
     asked to write into, and --canvas is the surface that lifts off the
     ground rather than sitting in it. */
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  /* Same reasoning as the bands above, applied to the panel's own top and
     bottom: the sides keep the width measure, because width is what they are
     actually spanning. */
  padding: clamp(24px, min(3.6vw, 4.5vh), 56px) clamp(28px, 3.6vw, 56px);
}
.cpanel .eyebrow { color: var(--fog); margin-bottom: 16px; }
.cpanel-sub {
  font-size: 15px;
  line-height: 1.6;
  color: var(--charcoal);
  max-width: 46ch;
  margin-top: 14px;
}

/* topic chips — a radio group wearing pills */
.ctopic {
  border: 0;
  margin: clamp(28px, 3vw, 40px) 0 0;
  padding: 0;
  min-width: 0;
}
/* Inside a numbered ask the group's own label already set the space above, and
   the chip set's and field grid's top margins would double it. */
.fgroup > .ctopic:first-of-type,
.fgroup > .cform:first-of-type { margin-top: 0; }
/* the writing panel's form is three asks now, so it opens on the same gap the
   groups keep between one another */
.cpanel form { margin-top: clamp(30px, 3.2vw, 42px); }
/* The message is the only thing on this page that matters, and as a single
   underline it carried the same weight as "Phone (optional)". A box, with room
   to write in. Scoped to the panel: in the application dialogs the textarea
   sits in a column of underlines and has to keep matching them. */
.cpanel .cf-textarea {
  border: 1px solid var(--mist);
  border-radius: var(--r-panel);
  /* The panel went white, so the box can no longer be white too — it takes
     the ground, one step down, and still reads as somewhere to write. */
  background: var(--ground);
  padding: 14px 16px;
  min-height: 128px;
}
.cpanel .cf-textarea:focus { border-color: var(--ink); }
/* Same move for the chips: they were white on cream and are now scoped down a
   step so an unchecked chip still lifts off the panel it sits on. Scoped,
   because on the other pages the group still sits on cream or on the band. */
.cpanel .ctopic-set label { background: var(--ground); }
.cpanel .ctopic-set input:checked + label { background: var(--void); }
.ctopic-label {
  display: block;
  padding: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 14px;
}
.ctopic-set { display: flex; flex-wrap: wrap; gap: 8px; }
.ctopic-set input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}
.ctopic-set label {
  display: inline-block;
  padding: 9px 17px;
  border: 1px solid var(--mist);
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -.005em;
  cursor: pointer;
  background: var(--canvas);
  transition: border-color .2s var(--ease), background .2s var(--ease), color .2s var(--ease);
}
.ctopic-set label:hover { border-color: var(--ink); }
.ctopic-set input:checked + label {
  background: var(--void);
  border-color: var(--ink);
  color: var(--on-void);
}
.ctopic-set input:focus-visible + label { outline: 2px solid var(--ink); outline-offset: 3px; }

/* fields */
.cform {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(22px, 2.2vw, 28px) clamp(20px, 2.2vw, 32px);
  margin-top: clamp(28px, 3vw, 40px);
}
.cf-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.cf-field--wide { grid-column: 1 / -1; }
.cf-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
  transition: color .2s var(--ease);
}
.cf-label .opt { text-transform: none; letter-spacing: 0; font-weight: 400; }
.cf-field:focus-within .cf-label { color: var(--ink); }
.cf-input {
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--mist);
  border-radius: 0;
  padding: 6px 0 10px;
  transition: border-color .22s var(--ease);
}
.cf-input::placeholder { color: var(--mist); }
.cf-input:focus { outline: none; border-bottom-color: var(--ink); }
/* The ring comes back for the keyboard. `outline: none` on :focus alone also
   takes it from :focus-visible, which is where the one rule at the top of this
   file was putting it, so a tab into any of these three landed on nothing
   visible. Mouse focus still gets the quiet treatment. */
.cf-input:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.cf-textarea { resize: none; line-height: 1.55; }

.cform-send {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 24px;
  margin-top: 4px;
}
.cform-note { font-size: 12.5px; line-height: 1.5; color: var(--fog); max-width: 40ch; }
.cform-note.is-warn { color: var(--ink); font-weight: 500; }
.cform-note a { text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 640px) { .cform { grid-template-columns: 1fr; } }

/* ---- application panels · one layout for both apply forms ---------------- */
/* Both applications were a single narrow column of fields, which read as a
   queue of work and left half the screen empty. They now sit beside a tinted
   rail that keeps the promise in view while you fill the form in: what it
   costs you in time, when you hear back, and who to write to instead. */
.fpanel {
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.5fr);
  min-height: 0;
}
.fpanel-aside {
  padding: clamp(28px, 3vw, 44px);
  /* The brand foil, washed out. The foil itself is far too loud to read type
     on, so it sits under a heavy white veil: the hue run and the dark anchors
     still show as a shift down the panel, at a strength text survives. */
  background:
    linear-gradient(rgba(255, 255, 255, .84), rgba(255, 255, 255, .84)),
    linear-gradient(168deg, var(--brand-foil));
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}
/* Fog grey is for white. On the pink wash it goes to mud, so the small type
   here steps up to charcoal and the rules go to a tint of the ink. */
.fpanel-aside .eyebrow { color: var(--charcoal); }
.fpanel-title {
  font-family: var(--font-title);
  font-size: clamp(26px, 2.8vw, 72px);
  font-weight: 300;
  letter-spacing: -.008em;
  line-height: 1.16;
}
.fpanel-lead {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--charcoal);
  max-width: 34ch;
}
.fpanel-facts { margin: auto 0 0; display: grid; gap: 14px; }
.fpanel-facts > div { border-top: 1px solid rgba(0, 0, 0, .14); padding-top: 12px; }
.fpanel-facts dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, .55);
}
.fpanel-facts dd {
  margin: 5px 0 0;
  font-family: var(--font-head);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.fpanel-facts a { text-decoration: underline; text-underline-offset: 3px; }
.fpanel-main { padding: clamp(26px, 3vw, 44px); min-width: 0; background: var(--canvas); }

/* The fields are grouped so the form reads as three short asks rather than
   nine fields in a row. */
.fgroup { min-width: 0; }
.fgroup + .fgroup {
  margin-top: clamp(28px, 3vw, 38px);
  padding-top: clamp(24px, 2.6vw, 32px);
  border-top: 1px solid var(--hairline);
}
.fgroup-label { display: flex; align-items: baseline; gap: 10px; margin-bottom: 18px; }
.fgroup-num {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  color: var(--bubblegum-deep);
}
.fgroup-name {
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.012em;
}
/* Inside a group the chip set and the field grid already have the label above
   them, so they lose the standalone top margin. */
.fgroup .ctopic, .fgroup .cform { margin-top: 0; }
.fgroup .ctopic + .ctopic { margin-top: clamp(22px, 2.4vw, 30px); }
@media (max-width: 900px) {
  .fpanel { grid-template-columns: 1fr; }
  .fpanel-facts { margin-top: 4px; }
}
/* Stacked on a phone the rail is the first screenful and the form is below the
   fold, which is the wrong way round for something somebody came to fill in.
   The facts fold into a line and the type steps down, so the first field is in
   view without a scroll. */
@media (max-width: 640px) {
  .fpanel-aside { padding: 20px 22px 18px; gap: 12px; }
  .fpanel-title { font-size: clamp(22px, 6.4vw, 26px); }
  .fpanel-lead { font-size: 13.5px; max-width: none; }
  .fpanel-facts { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 4px; }
  .fpanel-facts > div {
    display: flex;
    align-items: baseline;
    gap: 6px;
    border-top: 0;
    padding-top: 0;
  }
  .fpanel-facts dt { font-size: 10px; }
  .fpanel-facts dd { margin: 0; font-size: 13px; }
}

/* ---- right · the rail ---- */
/* One card, not two stacked boxes. A photograph in its own frame above a
   bordered list of details read as two unrelated objects sharing a column,
   and the pair stopped short of the panel beside them. Joined, the rail is a
   single thing: the details sit under the photograph on one border, and the
   photograph takes whatever height the form leaves over, so both columns of
   the desk finish on the same line.

   Sticky is gone with them. It could not survive a stretched grid item, and
   at this section's height it was never doing much: the desk is barely taller
   than the viewport it is read in. */
.crail {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  overflow: hidden;
  background: var(--canvas);
}
.crail-card {
  border-top: 1px solid var(--hairline);
  padding: 6px clamp(22px, 2.2vw, 30px);
}
.crail-row {
  display: block;
  padding: 20px 0;
  border-top: 1px solid var(--hairline);
}
.crail-row:first-child { border-top: 0; }
.crail-row dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--fog);
}
.crail-row dd {
  font-family: var(--font-head);
  font-size: clamp(15px, 1.35vw, 17px);
  font-weight: 600;
  letter-spacing: -.014em;
  line-height: 1.4;
  margin-top: 7px;
}
.crail-row dd a { border-bottom: 1px solid var(--mist); transition: color .2s var(--ease), border-color .2s var(--ease); }
.crail-row dd a:hover { color: var(--bubblegum-deep); border-color: var(--bubblegum); }
.crail-row dd .sub {
  display: block;
  font-family: var(--font);
  font-size: 12.5px;
  font-weight: 400;
  color: var(--fog);
  margin-top: 5px;
}

.crail-media {
  position: relative;
  flex: 1;
  /* a floor, for when the form beside it is short */
  min-height: 300px;
  overflow: hidden;
}
/* absolute, so the photograph contributes no intrinsic height of its own and
   the form is what sets the row */
.crail-media img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}
.crail-shade {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, .66) 100%);
}
.crail-place {
  position: absolute;
  left: clamp(20px, 2vw, 28px);
  right: clamp(20px, 2vw, 28px);
  bottom: clamp(18px, 1.8vw, 26px);
  color: var(--on-void);
}
.crail-place .where {
  font-family: var(--font-head);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.014em;
}
.crail-place .addr {
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .72);
  margin-top: 5px;
}

@media (max-width: 900px) {
  .cdesk { grid-template-columns: 1fr; }
  /* stacked, there is no form beside it to take a height from */
  .crail-media { flex: none; min-height: 0; aspect-ratio: 16 / 9; }
}

/* ---- the hero plate, and its caption ---- */
.pplate { position: relative; margin: 0; }
.pplate-cap {
  position: absolute;
  left: clamp(20px, 2.4vw, 32px);
  right: clamp(20px, 2.4vw, 32px);
  bottom: clamp(18px, 2.2vw, 28px);
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.pplate-pill {
  background: var(--canvas);
  border-radius: var(--r-pill);
  padding: 7px 15px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  flex: none;
}
.pplate-line {
  color: var(--on-void);
  font-family: var(--font-head);
  font-size: clamp(14px, 1.5vw, 18px);
  font-weight: 600;
  letter-spacing: -.014em;
  text-shadow: 0 1px 18px rgba(0, 0, 0, .55);
}
@media (max-width: 640px) {
  .pplate-line { display: none; }
}

/* ---- the tiers band ---- */
/* The tint band already carries a full section gap inside it, so the perks
   section above only needs a seam, not another half gap. The band keeps its
   own padding even, top and bottom. */
#membership { padding-bottom: calc(var(--gap-section) / 4); }

/* ---- the application dialog ---- */
/* The form is deliberately not on the page. It is nine fields and two chip
   groups, and open by default it read as work to do before you had decided
   to do it. Behind a button, the page sells and the form only appears to
   someone who already said yes. */
.pdialog {
  width: min(1060px, calc(100vw - 32px));
  max-height: min(90vh, 920px);
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  background: var(--cream);
  color: var(--ink);
  overflow: hidden;
}
/* Not position:relative. A modal dialog is position:fixed in the UA sheet, and
   overriding that drops it back into flow, which makes the page scroll to it
   on open. It is already a containing block for the floating close button. */
.pdialog::backdrop { background: rgba(0, 0, 0, .55); }
.pdialog[open] { display: flex; flex-direction: column; }
/* The panel fills the dialog and only the form column scrolls, so the rail on
   the left stays put while the fields move. */
.pdialog .fpanel { flex: 1; min-height: 0; }
.pdialog .fpanel-main { overflow-y: auto; -webkit-overflow-scrolling: touch; }
.pdialog .fpanel-aside { overflow-y: auto; }
@media (max-width: 900px) {
  .pdialog { overflow-y: auto; }
  .pdialog .fpanel-main, .pdialog .fpanel-aside { overflow: visible; }
}
.pdialog-x {
  flex: none;
  width: 36px; height: 36px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  display: grid;
  place-items: center;
  background: var(--canvas);
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.pdialog-x svg { width: 16px; height: 16px; }
.pdialog-x:hover { background: var(--bubblegum); border-color: var(--bubblegum); }
/* With the head folded into the rail, the close control floats over the form
   column instead of sitting in a bar of its own. */
.pdialog-x--float {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
}

/* ---- the facts under the apply CTA ---- */
.cta-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(20px, 3vw, 48px);
  margin-top: clamp(40px, 4vw, 56px);
  text-align: left;
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
}
.cta-facts > div { border-top: 1px solid rgba(255, 255, 255, .22); padding-top: 16px; }
.cta-facts dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
}
.cta-facts dd {
  font-family: var(--font-head);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.012em;
  color: var(--on-void);
  margin-top: 7px;
}
@media (max-width: 640px) {
  .cta-facts { grid-template-columns: 1fr; text-align: center; }
  .cta-facts > div { padding: 14px 0; }
}

/* ---- routes · who to write to ---- */
/* Plain cards on the cream band. The wash only survives at icon-tile size —
   four full-bleed pastels shouted louder than anything else on the page. */
.routes {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(16px, 1.6vw, 20px);
}
.route {
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: clamp(24px, 2.4vw, 32px) clamp(22px, 2.2vw, 28px);
  min-height: 250px;
  transition: transform .3s var(--ease), border-color .3s var(--ease);
}
.route:hover { transform: translateY(-4px); border-color: var(--ink); }
.icotile {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px; height: 54px;
  margin-bottom: 24px;
  color: var(--tone, var(--ink));
}
/* Nothing behind it: the colour is in the line, and at this size the line is
   enough. The glyphs are drawn on a 24 unit grid, so the stroke scales with
   the box, and 1.5 at 54px would render over three pixels and lose the
   drawn-line quality. This holds it near two. */
.icotile svg { width: 54px; height: 54px; stroke-width: .95; }
/* The tiles carry the studio's isometric objects now (scripts/isoicons.py),
   a size up from the 24-grid glyphs they replaced, in the tile's own tone,
   their faces taking the surface they sit on. */
.icotile, .assure-mark:has(svg) { width: 80px; height: 80px; }
.icotile svg, .assure-mark svg { width: 80px; height: 80px; }
/* Ink at rest; the tile's tone only when its card is hovered, the same
   lighting the offer cards and the values panels use. */
.icotile .iso, .assure-mark .iso { color: var(--ink); }
.section--tint { --iso-fill: var(--band); }
.route, .perk { --iso-fill: #fff; }
@media (hover: hover) {
  }
.route h3 {
  font-family: var(--font-head);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -.016em;
}
.route p {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--charcoal);
  margin-top: 10px;
  flex: 1;
}
.route .alink { margin-top: 22px; font-size: 13px; }
@media (max-width: 900px) { .routes { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .routes { grid-template-columns: 1fr; } .route { min-height: 0; } }

/* ------------------------------------------------------------ partners */
/* The programme page. Reuses the contact panel, the solutions tier cards and
   the assure grid wholesale; only the three things below are new. */

/* ---- perks · what membership gives you ---- */
.perks {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 1.6vw, 20px);
}
.perk {
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: clamp(26px, 2.6vw, 34px) clamp(24px, 2.4vw, 30px);
}
.perk h3 {
  font-family: var(--font-head);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -.016em;
}
.perk p {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--charcoal);
  margin-top: 10px;
  /* a third of a page that now tracks the screen: without this the card is
     700px wide on a large monitor and the sentence inside it runs to 115
     characters. 48ch measures ~84 in this face at this size. */
  max-width: 48ch;
}
@media (max-width: 960px) { .perks { grid-template-columns: repeat(2, 1fr); } }


@media (max-width: 640px) { .perks { grid-template-columns: 1fr; } }

/* ---- terms · a ruled list of what comes with something ------------------
   The card grid this replaces gave every item a box, a border and a corner
   radius, which is a lot of furniture for eight short facts, and eight boxes
   of equal weight read as eight things to get through. Rules do the same job
   of separating with none of the drawing: two columns, a heavy rule to open
   the list and a hairline under each entry, and the icon carrying the colour.
   House rule, and the reason this exists: a rule, not a box. */
.terms {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(40px, 6vw, 96px);
  border-top: 1.5px solid var(--ink);
}
.term {
  padding: clamp(26px, 2.6vw, 34px) 0;
  border-bottom: 1px solid var(--hairline);
}
.term .icotile { margin-bottom: 18px; }

/* ---- who qualifies, and who does not ------------------------------------ */
/* Six kinds on white cards, then the two exclusions in a dashed panel below.
   The dashed edge is the site's "nothing here" line (the empty product slot
   uses it), which is exactly what the second half says. */
.qual {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}
.qual-item {
  --iso-fill: #fff;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(24px, 2.4vw, 36px);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
}
.qual-item .icotile { margin-bottom: 4px; }
/* The titles start at the same height across a row: the object above them is
   one size for the whole set, so nothing has to be pushed anywhere. */
.qual-item h3 {
  font-family: var(--font-head);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.016em;
}
.qual-item p { font-size: 14px; line-height: 1.6; color: var(--charcoal); text-wrap: pretty; }
/* Three across needs the width; two on a tablet, one on a phone, where three
   columns had every card down to a word a line. */
@media (max-width: 900px) { .qual { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .qual { grid-template-columns: 1fr; gap: 12px; } }
.notfor {
  margin-top: 16px;
  padding: clamp(24px, 2.6vw, 36px);
  border: 1.5px dashed var(--mist);
  border-radius: var(--r-panel);
}
.notfor-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 22px;
}
.notfor-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px clamp(40px, 6vw, 96px); }
.notfor-item { display: flex; gap: 20px; align-items: flex-start; }
.notfor-item .icotile { flex: none; margin-bottom: 0; width: 56px; height: 56px; }
.notfor-item .icotile svg { width: 56px; height: 56px; --iso-size: 56; }
.notfor-item h3 {
  font-family: var(--font-head);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.016em;
}
.notfor-item p { margin-top: 8px; font-size: 14px; line-height: 1.6; color: var(--charcoal); text-wrap: pretty; }
@media (max-width: 640px) { .notfor-grid { grid-template-columns: 1fr; gap: 28px; } }

/* ---- the handover, as an itemised note ---------------------------------- */
/* Every line that would be on an invoice, with what it costs you written
   beside it — and every line saying the same word. The page is about work
   given away, so a bill is the honest shape for what is in it, and the total
   at the foot is the joke the whole programme rests on. Cards and rules were
   both tried here and read as the two sections above it.

   The cost column is the point of the layout: it is the one thing the eye
   runs down, so it holds its own track at every width and only folds under
   the title on a phone. */
.hnote {
  border: 1px solid var(--mist);
  border-radius: var(--r-panel);
  background: var(--cream);
  overflow: clip;
}
.hnote-head,
.hline,
.hnote-total {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 13ch);
  align-items: center;
  gap: clamp(18px, 2.4vw, 36px);
  padding: clamp(16px, 1.8vw, 24px) clamp(22px, 2.6vw, 40px);
}
.hnote-head {
  padding-top: 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--mist);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
}
.hnote-head span:first-child { grid-column: 1 / 3; }
.hnote-head span:last-child { text-align: right; }
.hline {
  --iso-fill: var(--cream);
  border-bottom: 1px solid var(--hairline);
  transition: background .3s var(--ease);
}
.hline-mark { width: clamp(68px, 7vw, 96px); aspect-ratio: 1; color: var(--ink); transition: color .25s var(--ease); --iso-size: 82; }
.hline-mark .iso { width: 100%; height: 100%; }
.hline-copy h3 {
  font-family: var(--font-head);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.016em;
}
.hline-copy p { margin-top: 5px; font-size: 14px; line-height: 1.6; color: var(--charcoal); max-width: 62ch; text-wrap: pretty; }
.hline-cost {
  justify-self: end;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--charcoal);
  transition: color .25s var(--ease);
}
/* the foot: the same three tracks, the sum set in the display face */
.hnote-total {
  border-top: 1.5px solid var(--ink);
  padding-top: clamp(22px, 2.4vw, 32px);
  padding-bottom: clamp(22px, 2.4vw, 32px);
  background: var(--canvas);
}
.hnote-total-label {
  grid-column: 1 / 3;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fog);
}
.hnote-total-sum {
  justify-self: end;
  font-family: var(--font-title);
  font-weight: 300;
  font-size: clamp(28px, 3vw, 46px);
  letter-spacing: -.01em;
  line-height: 1;
}
@media (hover: hover) {
  .hline:hover { background: var(--canvas); --iso-fill: var(--canvas); }
  }
@media (max-width: 760px) {
  .hnote-head { display: none; }
  .hline, .hnote-total { grid-template-columns: auto minmax(0, 1fr); }
  .hline-cost, .hnote-total-sum { grid-column: 2; justify-self: start; margin-top: 6px; }
  .hnote-total-label { grid-column: 1 / -1; }
}


/* ---- admission · four steps down one line ---- */
/* Four equal columns read as four separate facts. Admission is an order, so
   the steps run down a single rule with the numerals sitting on it. The head
   is smaller than the section heads above: a sequence, not another pitch. */
.admitwrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: clamp(40px, 6vw, 96px);
  align-items: start;
}
.sec-head--sm { display: block; margin-bottom: 0; }
/* The FAQ head sits directly on its list, so it drops the 80px it would
   otherwise leave. Thirteen pages carried this as a style attribute. */
.sec-head--flush { margin-bottom: 0; }
/* In the block variant the lead sits under the heading rather than beside
   it, and wants a breath above. */
.sec-head--sm .lead { margin-top: 18px; }
.admit {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}
/* The rule stops short at both ends so it reads as a span between the first
   and last step, not an arrow running off the section. */
.admit::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 12px;
  bottom: 12px;
  width: 1px;
  background: var(--mist);
}
.admit-item { position: relative; padding-left: 56px; }
.admit-item + .admit-item { margin-top: clamp(30px, 3.2vw, 44px); }
.admit-num {
  position: absolute;
  left: 0;
  top: 0;
  width: 23px;
  text-align: center;
  /* The numeral sits on the rule, so it carries the page colour to break it. */
  background: var(--canvas);
  padding: 4px 0;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  color: var(--bubblegum-deep);
}
.admit-item h3 {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.014em;
}
.admit-item p {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--charcoal);
  margin-top: 9px;
  max-width: 52ch;
}
@media (max-width: 900px) {
  .admitwrap { grid-template-columns: 1fr; gap: 36px; }
}

/* ---- the steps' own wrapper, when they are the whole section --------------
   .admitwrap is a two column construction: something on the left, the steps
   on the right. Products fills that left column with an eyebrow, a display
   heading and a lead; GoodWork fills it with a photograph. Partners had only
   a six word heading in it, so the column stood five hundred pixels empty and
   the section read as a layout with a piece missing. This is that page's
   wrapper instead: the heading takes the top, and the steps take the whole
   width under it. What fills it there is .sreel, below. ------------------ */
.admitwrap--ledger { grid-template-columns: 1fr; gap: clamp(28px, 3.2vw, 48px); }

/* ---- the same steps again, as a reel ------------------------------------
   The ledger below was four rows down the page, each one carrying its whole
   sentence. It read correctly and it read all at once: four paragraphs of
   equal weight, which is a list of facts rather than a sequence someone is
   being walked through.

   This is the other way to say it. The four numerals sit across the page as
   one line, and only the open step spends any width — its column takes what
   the other three give up, and the sentence appears in the room that opens.

   HOW IT MOVES. The first build of this widened a grid column, by putting a
   transition on grid-template-columns. It is the obvious way to write it and
   it is not smooth: track sizes are layout, so every frame of that animation
   re-runs layout on the whole grid and re-rasterises four numerals set at a
   hundred and fifty pixels. It stutters, and no amount of easing fixes it,
   because nothing about it is on the compositor.

   So nothing here resizes. Every step is the full open width all the time and
   sits on top of the one before it, and what animates is each step's
   translateX — a transform, which the compositor can run on its own. Opening
   a step means the three to its right slide right by the width it gains; the
   step's own sentence is not faded in, it is uncovered, because the neighbour
   that was parked on top of it moves off. The wipe is the reveal.

   That also draws the rules for free. Each step's rule runs its whole width,
   and the next step's cover — which reaches one gap further left than the
   step does — cuts it off exactly where that column ends. So the rules widen
   and narrow with the columns without anyone measuring anything.

   Single digits, not the 01–04 the ledger used: at this size a second digit
   is half again as wide as a closed column has to spend. ------------------ */
.sreel {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  /* the last step is parked a full open width to the right of where it shows,
     so the reel's own edge is what trims it back to a numeral */
  overflow: hidden;
  /* Held in from the shell's full measure. Across the whole width the open
     column ends in a stretch of nothing after the sentence — the reel is a
     line of four marks, and it wants to be about as long as the line is. */
  max-width: min(100%, 1040px);
  /* the closed column, the air between columns, and the colour a step paints
     over its neighbour with — the page's own ground, so the cover is
     invisible as a shape and only reads as the edge of a column */
  --shut: clamp(92px, 8.2vw, 118px);
  --sgap: clamp(14px, 1.8vw, 30px);
  --sreel-ground: var(--ground);
}

.sreel-step {
  position: absolute;
  top: 0;
  left: 0;
  /* everything the reel is not spending on the three closed columns */
  width: calc(100% - 3 * (var(--shut) + var(--sgap)));
  padding-top: clamp(16px, 1.9vw, 28px);
  --push: 0px;
  /* Each step is one sealed layer, so a step paints over the whole of the one
     before it — cover, rule, numeral and sentence together. The transform
     below would do this on its own, since a transform makes a stacking
     context, but then the painting order would be a side effect of the
     animation rather than something the step was asked for. */
  isolation: isolate;
  transform: translateX(calc(var(--i) * (var(--shut) + var(--sgap)) + var(--push)));
  transition: transform .58s var(--ease);
  will-change: transform;
}
/* the first step stays in the flow, and is what gives the reel its height */
.sreel-step:first-child { position: relative; }
.sreel-step:nth-child(1) { --i: 0; }
.sreel-step:nth-child(2) { --i: 1; }
.sreel-step:nth-child(3) { --i: 2; }
.sreel-step:nth-child(4) { --i: 3; }
/* everything to the right of the open step stands off by what it gained.
   The 100% is the step's own width, which is the open width — the one place
   in this file where a percentage inside a transform is the shorter way to
   say a thing. */
.sreel-step.is-open ~ .sreel-step { --push: calc(100% - var(--shut)); }

/* the cover. One gap wider on the left than the step itself, so it lands on
   the previous step exactly where that column's width runs out and leaves the
   air between columns showing. */
.sreel-step::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: calc(-1 * var(--sgap));
  background: var(--sreel-ground);
}
/* the rule, over the cover of the step before it and under this step's own */
.sreel-step::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 1;
  background: var(--hairline);
  transition: background-color .35s var(--ease);
}
.sreel-step.is-open::before { background: var(--ink); }
.sreel-step > * { position: relative; z-index: 1; }
/* the closed columns are a target end to end, so the pointer does not have to
   find the numeral. Closed only: on the open one this would sit over the
   sentence and take the text away from anyone trying to select it. */
.sreel-step:not(.is-open) .sreel-hit::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--shut);
}

.sreel-hit {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: block;
}
.sreel-hit:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.sreel-num {
  display: block;
  font-size: clamp(58px, 10.5vw, 158px);
  line-height: .92;
  font-weight: 600;
  letter-spacing: -.045em;
  color: var(--mist);
  transition: color .35s var(--ease);
}
.sreel-step.is-open .sreel-num { color: var(--ink); }
/* A pointer opens a step outright, so this is what a keyboard gets instead of
   the hover it cannot have: the numeral it is about to open, in the accent. */
.sreel-step:not(.is-open) .sreel-hit:focus-visible .sreel-num { color: var(--bubblegum-deep); }
/* the step's name is the button's accessible name and nothing else up here —
   the numeral is what a reader is looking at, and the name is already the
   panel's heading. It comes back as visible text on a narrow screen, where
   four bare numerals in a column would be a list of nothing. */
.sreel-name {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.sreel-panel {
  position: absolute;
  top: clamp(20px, 2.6vw, 40px);
  left: calc(var(--shut) + clamp(12px, 1.4vw, 26px));
  right: 0;
  /* Out of the reader's way the moment it is covered, and not a frame before:
     a closing step is still in plain sight for the length of the slide, so the
     delay on the way out is the slide's own duration. */
  visibility: hidden;
  transition: visibility 0s linear .58s;
}
.sreel-step.is-open .sreel-panel { visibility: visible; transition-delay: 0s; }
.sreel-t {
  margin: 0;
  font-size: clamp(18px, 1.35vw, 25px);
  font-weight: 600;
  letter-spacing: -.016em;
  line-height: 1.2;
}
.sreel-s {
  margin: 12px 0 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--charcoal);
  max-width: 46ch;
}

@media (prefers-reduced-motion: reduce) {
  .sreel-step, .sreel-num, .sreel-step::before { transition: none; }
  .sreel-panel { transition-delay: 0s; }
}

/* Under the fold of a phone the reel has no width to spend, so it turns back
   into a column — and into an ordinary accordion, which is what a stack of
   these is. Nothing is stacked or slid here: the steps go back into the flow,
   the covers come off, and the name comes out of hiding to label a closed row
   while the panel's heading stands down so the two do not say it twice. */
@media (max-width: 860px) {
  .sreel { overflow: visible; max-width: none; }
  .sreel-step,
  .sreel-step:first-child {
    position: relative;
    width: auto;
    transform: none;
    transition: none;
    padding: clamp(16px, 3.6vw, 24px) 0;
    border-top: 1px solid var(--hairline);
  }
  .sreel-step.is-open ~ .sreel-step { --push: 0px; }
  .sreel-step:last-child { border-bottom: 1px solid var(--hairline); }
  .sreel-step::after { content: none; }
  .sreel-step::before { content: none; }
  .sreel-step:not(.is-open) .sreel-hit::after { width: 100%; right: 0; }
  .sreel-hit { display: flex; align-items: baseline; gap: clamp(14px, 3.5vw, 22px); width: 100%; }
  .sreel-num { font-size: clamp(30px, 8.5vw, 44px); line-height: 1; }
  .sreel-name {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: normal;
    font-size: clamp(17px, 4.4vw, 21px);
    font-weight: 600;
    letter-spacing: -.016em;
  }
  .sreel-step.is-open .sreel-name { color: var(--ink); }
  .sreel-panel {
    position: static;
    display: none;
    visibility: visible;
    transition: none;
  }
  .sreel-step.is-open .sreel-panel { display: block; margin-top: 12px; }
  .sreel-t { display: none; }
  .sreel-s { margin-top: 0; max-width: none; }
}

/* ---- the application · aside beside the same panel contact uses ---- */

/* ------------------------------------------------------------ good work */
/* GoodWork is a programme of the studio, not a second company, so it gets a
   lockup rather than a logo: the name in the working face with a slice of the
   brand foil under it, then who it belongs to. The foil is an underline and
   not a fill on purpose — the house pattern is ink at rest and foil as an
   accent, and clipped into 19px glyphs the pale stops go illegible on white.

   The height is pinned to the 24px the page hero's eyebrow arithmetic assumes
   (see .pagehero .eyebrow above): the headline lands on the same line whether
   a hero carries an eyebrow or not, and a taller lockup would push it off. */
.gwmark {
  gap: 14px;
  align-items: center;
  text-transform: none;
  letter-spacing: normal;
}
.gwmark-name {
  position: relative;
  font-family: var(--font-head);
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -.028em;
  line-height: 24px;
  color: var(--ink);
}
.gwmark-name::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: 1px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  animation: gwPan 14s linear infinite;
}
@keyframes gwPan { to { background-position: -200% 50%; } }
@media (prefers-reduced-motion: reduce) { .gwmark-name::after { animation: none; } }
/* the divider is drawn on the label so the lockup stays two elements */
.gwmark-by {
  position: relative;
  padding-left: 15px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 24px;
  color: var(--fog);
}
.gwmark-by::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px; bottom: 7px;
  width: 1px;
  background: var(--mist);
}

/* ---- the mark, in the footer ------------------------------------------
   A column of plain 15px links, and one of them is a brand. "by Ultrabasic"
   is dropped here: it earns its place in the page hero where GoodWork is
   introducing itself, but this is already Ultrabasic's own footer, and the
   full lockup is 226px wide against a footer column of roughly 200.

   It separates from its neighbours by weight, tracking, colour and the foil
   rule rather than by scale, so the column keeps its rhythm. Specificity
   note: `.foot-col a` is (0,1,1) and would otherwise win on size and colour,
   so this is scoped through .foot-col rather than left as a bare class. */
.foot-col .gwmark--foot {
  position: relative;
  display: inline-block;
  padding-bottom: 6px;
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -.028em;
  color: var(--ink);
}
.foot-col .gwmark--foot::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  animation: gwPan 14s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .foot-col .gwmark--foot::after { animation: none; }
}
/* ---- the two ways in · a docket ---------------------------------------
   Four goes at this section, and the first three were the same drawing: the
   solutions tier cards at two across, then a figure at display scale in a
   panel with the free one inverted to ink, then the same thing with the
   panels taken off. All three were a pricing widget — two columns of feature
   list with a button under each, inviting a choice.
   The copy says there is no choice. "One application covers both. Tell us
   what you need and we will tell you which one you are getting." Both buttons
   pointed at the same #apply anchor, which is the tell.
   So it is a docket. One narrow measure on the page's own ground, two entries
   ruled with leaders, values right aligned on tabular figures, and one button
   at the foot. It reads as something the studio issues rather than something
   it advertises, which is what the copy has been saying all along. */
.gwledger {
  max-width: 620px;
  margin: clamp(40px, 5vw, 76px) auto 0;
  padding: clamp(26px, 3vw, 44px);
  background: var(--canvas);
  border: 1px solid var(--ink);
  border-radius: var(--r-panel);
}
.gwl-item + .gwl-item {
  margin-top: clamp(30px, 3.2vw, 44px);
  padding-top: clamp(26px, 2.8vw, 38px);
  /* dashed, not solid: the rule between two entries on a docket is a tear,
     and a solid one here read as the edge of a second card */
  border-top: 1px dashed var(--mist);
}
.gwl-flag {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--fog);
}
.gwl-fig {
  display: flex;
  align-items: flex-end;
  gap: 11px;
  margin-top: 8px;
  font-family: var(--font-head);
  font-size: clamp(46px, 5.4vw, 74px);
  font-weight: 800;
  letter-spacing: -.045em;
  line-height: .9;
}
/* The unit resets its tracking rather than inheriting it. letter-spacing in em
   computes against the element it is written on and then inherits as an
   absolute length, so the figure's -.045em arrives here as about three
   negative pixels a character: "in full" came out ten pixels wide, every
   letter on top of the last. */
.gwl-unit {
  flex: none;
  white-space: nowrap;
  padding-bottom: 6px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: normal;
  color: var(--fog);
}
.gwl-name { font-weight: 700; font-size: 16.5px; margin-top: 15px; }
.gwl-note {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--fog);
  margin-top: 6px;
  max-width: 52ch;
}
.gwl-rows { margin-top: 16px; }
/* label · leader · value, in that visual order out of source order, so the
   dotted rule can stretch to whatever the two ends leave it */
.gwl-rows > div {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 0;
  font-size: 14px;
}
.gwl-rows dt { color: var(--fog); flex: none; }
.gwl-rows > div::after {
  content: "";
  flex: 1;
  order: 1;
  border-bottom: 1px dotted var(--mist);
  transform: translateY(-4px);
}
.gwl-rows dd {
  order: 2;
  flex: none;
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.gwl-foot {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: clamp(28px, 3vw, 40px);
  padding-top: clamp(22px, 2.4vw, 30px);
  border-top: 1px solid var(--ink);
}
.gwl-foot p { font-size: 13.5px; color: var(--fog); }
/* The values are the long half of these rows, so on a narrow screen the pair
   stacks and the leader goes with it: a dotted rule between a label and a
   value on the line below is a rule to nowhere. */
@media (max-width: 540px) {
  .gwl-rows > div { display: grid; gap: 2px; padding: 10px 0; border-top: 1px solid var(--hairline); }
  .gwl-rows > div::after { display: none; }
  .gwl-rows dd { text-align: left; }
  .gwl-rows > div:first-child { border-top: 0; }
}

/* ---- the steps, with the empty half filled -----------------------------
   .admitwrap is a two column grid whose left column holds a short heading and
   then nothing for the height of four steps. On the partners page that
   emptiness reads as air; at this page's length it read as an unfinished
   section, so here the heading moves out to a full width section head and the
   column carries a photograph instead.

   The photograph takes its height from the steps beside it rather than from
   its own crop: align-self on this one item, never align-items on the wrap,
   because stretching the row stretches the step list too and .admit draws its
   connector rule down its own full height. */



/* ---- the band · one sentence, over a photograph ------------------------
   A pause between two long reading sections. It carries no link and no list
   on purpose: the only job is to stop the page being column after column of
   type, and to say the one thing the page would be dishonest without. */
/* ---- the mission mosaic · about ----------------------------------------
   A six across, two deep grid of boxes, and only four photographs in it. Each
   run of boxes is one picture seen through that many windows: the gutters cut
   across it and the image carries on behind them, so the row reads as one
   photograph broken up rather than a set of unrelated crops. Which is the
   whole reason it works here — the site has eight photographs and they are
   not a set, so a true mosaic of them looks like a contact sheet from four
   different shoots.

       row 1   [ one photo, 2 wide ] [ solid ] [ one photo, 3 wide ]
       row 2   [ one photo, 3 wide ] [ solid ] [ one photo, 2 wide ]

   The masking is arithmetic rather than four cropped files. Every box clips,
   and the <img> inside it is stretched to the width of its whole run and slid
   left by the boxes that came before it:

       width : n * 100% + (n - 1) * gap
       left  : -k * (100% + gap)

   where n is how many boxes the picture spans and k is which one this is,
   both stated on the element. The percentages resolve against the box, so it
   holds at every width with no media query doing sums. One file per run, one
   request, and the gutters land on the photograph instead of between copies
   of it. ------------------------------------------------------------------ */
.mosaic-grid {
  --g: clamp(6px, .7vw, 10px);
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--g);
}
.mos-box, .mos-solid {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--r-panel);
  overflow: hidden;
}
.mos-box img {
  position: absolute;
  top: 0;
  left: calc(-1 * var(--k) * (100% + var(--g)));
  width: calc(var(--n) * 100% + (var(--n) - 1) * var(--g));
  height: 100%;
  /* img carries max-width:100% site wide, which would undo the stretch */
  max-width: none;
  object-fit: cover;
}
.mos-solid {
  background: var(--void);
  color: var(--on-void);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 5px;
  padding: clamp(12px, 1.15vw, 20px);
}
.mos-solid b { font-size: clamp(12px, .95vw, 15px); font-weight: 700; letter-spacing: -.01em; }
.mos-solid span { font-size: clamp(10px, .78vw, 12px); line-height: 1.35; color: rgba(255, 255, 255, .66); }

.mosaic-foot {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: end;
  margin-top: clamp(26px, 2.8vw, 44px);
}
.mosaic-foot .eyebrow { color: var(--fog); margin-bottom: 16px; }
.mosaic-line {
  font-family: var(--font-title);
  font-size: clamp(23px, 2.4vw, 52px);
  font-weight: 300;
  line-height: 1.22;
  letter-spacing: -.008em;
  color: var(--ink);
}
.mosaic-line b { font-weight: inherit; font-style: italic; }
.mosaic-checks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.mosaic-checks li { font-size: 15px; color: var(--charcoal); display: flex; gap: 10px; }
.mosaic-checks li::before { content: "\2713"; color: var(--mint-ink); font-weight: 700; flex: none; }

@media (max-width: 760px) {
  /* Six boxes across is a row of postage stamps on a phone. Three across, and
     each run collapses to its first box showing the whole picture — the slices
     are what the extra boxes were for, and there is no room for them. */
  .mosaic-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .mos-slice { display: none; }
  .mosaic-grid .mos-box img { left: 0; width: 100%; }
  .mosaic-foot { grid-template-columns: 1fr; align-items: start; gap: clamp(22px, 5vw, 32px); }
}

/* The mission, as a split panel: the sentence on the lavender with the
   photograph standing beside it as a tall column, both inside one rounded
   frame. Chosen from five treatments; the four that lost are in the history
   if anyone wants them back. */
.gwband {
  display: grid;
  grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
  min-height: clamp(340px, 38vw, 500px);
  border-radius: var(--r-panel);
  overflow: hidden;
}
/* the caption is written after the image, and reads before it */
.gwband img {
  grid-column: 2; grid-row: 1;
  display: block;
  width: 100%; height: 100%;
  /* the crop is stated per page, on the element */
  object-fit: cover;
}
.gwband-copy {
  grid-column: 1; grid-row: 1;
  background: var(--peri);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(34px, 4.4vw, 76px);
}
/* --fog is 2.9:1 on this ground and the eyebrow is 13px uppercase, which is
   its worst case. This is --void's hue lifted to clear 5:1 on the lavender. */
.gwband-copy .eyebrow { color: #5f5880; margin-bottom: 22px; }
/* ch on the line itself resolves against the size it is actually set at. The
   same cap written on .gwband-copy would resolve against the caption's
   inherited 16px and come out a third of the intended measure. */
.gwband-line {
  font-family: var(--font-title);
  font-size: clamp(24px, 2.5vw, 58px);
  font-weight: 300;
  line-height: 1.22;
  letter-spacing: -.008em;
  color: var(--ink);
  max-width: 20ch;
}
.gwband-line b { font-weight: inherit; font-style: italic; }

@media (max-width: 860px) {
  /* one column, sentence first: the claim is what the section is for, and a
     reader arriving on a phone should not have to scroll a photograph to
     reach it */
  .gwband { grid-template-columns: 1fr; min-height: 0; }
  .gwband-copy { grid-column: 1; grid-row: 1;
                 padding: clamp(30px, 6vw, 48px) var(--gutter) clamp(34px, 7vw, 52px); }
  .gwband img { grid-column: 1; grid-row: 2; height: clamp(200px, 42vw, 300px); }
  .gwband-line { max-width: 24ch; }
}


/* ------------------------------------------------------------ sitemap page */
/* The long form of the footer index. Same shape as .foot-col on purpose: a
   reader who came here because a footer link was not where they looked should
   recognise the lists when they arrive. */
.sm-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 44px 32px;
}
.sm-col { display: flex; flex-direction: column; gap: 12px; }
.sm-col h2 {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  margin: 0 0 6px;
}
.sm-col a { font-size: 15px; color: var(--charcoal); width: fit-content; transition: color .18s ease; }
.sm-col a:hover { color: var(--ink); }
.sm-head { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 28px; }
/* The posts, two abreast on a wide page: thirteen of them in one column is a
   scroll, and in five is a wall of truncated titles. */
.sm-posts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 40px; }
.sm-post {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--charcoal);
  transition: color .18s ease;
}
.sm-post:hover { color: var(--ink); }
.sm-post time { font-size: 13px; color: var(--fog); white-space: nowrap; font-variant-numeric: tabular-nums; }
@media (max-width: 900px) {
  .sm-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 36px 24px; }
  .sm-posts { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 540px) {
  .sm-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px 20px; }
}

/* ------------------------------------------------------------ footer */
/* White, like the topbar once it sticks: the page's off-white ground runs
   from the header to the last section, and the footer closes it as its own
   surface rather than more of the same. The hairline is what makes the change
   read as an edge instead of a printing fault. */
.foot {
  padding: 128px 0 48px;
  background: var(--canvas);
  border-top: 1px solid var(--seam);
}
/* Four lists again, and the brand column keeps its width. The footer carries
   the sections a reader navigates by; the whole index, case studies and every
   Insights post included, is one link away on /sitemap. */
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(4, minmax(0, 1fr));
  gap: 48px 40px;
}
.foot-mail {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 20px;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -.012em;
}
.foot-mail svg { width: 15px; height: 15px; transition: transform .25s var(--ease); }
.foot-mail:hover svg { transform: translate(3px, -3px); }
.foot-col { display: flex; flex-direction: column; gap: 12px; }
.foot-col h4 {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  margin-bottom: 6px;
}
.foot-col a, .foot-col span { font-size: 15px; color: var(--charcoal); width: fit-content; }
.foot-col a { transition: color .18s ease; }
.foot-col a:hover { color: var(--ink); }

/* --mark-w is the share of the column the mark takes. Both the flat SVG and
   the canvas that replaces it read it, so the two can never disagree. */
.foot-mark { margin: 104px 0 40px; color: var(--ink); --mark-w: .84; }
.foot-mark svg { width: calc(var(--mark-w) * 100%); height: auto; opacity: .11; margin: 0 auto; }
/* site.js redraws the mark as a dot matrix on a canvas (the LED field); the
   flat SVG stays in the markup for no-JS and steps aside once the canvas is
   in. The canvas is sized in script from the same --mark-w measure. */
.foot-led { display: block; margin: 0 auto; }
/* The phone gives the mark the whole column. The dot grid carries the
   letterforms on rows it can only win by being wide, and at 84 per cent of a
   360 point screen there were too few of them to hold the word. */
@media (max-width: 640px) {
  .foot-mark { --mark-w: 1; }
}
html.js .foot-mark.is-led svg { display: none; }

.foot-bot {
  border-top: 1px solid var(--hairline);
  padding-top: 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--fog);
}
.foot-legal { display: flex; gap: 24px; }
.foot-legal a:hover, .foot-up:hover { color: var(--ink); }
.foot-up { display: inline-flex; align-items: center; gap: 7px; }
.foot-up svg { width: 13px; height: 13px; }
@media (max-width: 900px) {
  .foot { padding-top: 64px; }
  .foot-grid { grid-template-columns: repeat(2, 1fr); gap: 36px; }
  .foot-brand { grid-column: 1 / -1; }
}
/* Two columns all the way down rather than one. These are four short lists of
   short labels — the longest, "Northwind case study", still fits a half column
   at 320 — and stacking them made the footer a 15-link scroll twice the height
   of the page's last section. */
@media (max-width: 540px) {
  .foot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px 20px; }
  /* everything else in the footer is a list of short labels; this column is a
     postal address and an email, neither of which has a break opportunity —
     hello@ultrabasiclabs.com is 185px against a 158px half column and pushed
     the page 7px wider than the phone. It takes the whole row. */
}

/* ------------------------------------------------------------ hero entrance */
html.js .hero .ln > span,
html.js .pagehero .ln > span {
  transform: translateY(112%);
  transition: transform 1s var(--ease);
}
html.js .hero-e {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
html.js .hero-plate {
  opacity: 0;
  transition: opacity .9s var(--ease);
}
html.is-ready .hero .ln > span,
html.is-ready .pagehero .ln > span { transform: none; }
html.is-ready .hero .ln:nth-child(1) > span,
html.is-ready .pagehero .ln:nth-child(1) > span { transition-delay: .1s; }
html.is-ready .hero .ln:nth-child(2) > span,
html.is-ready .pagehero .ln:nth-child(2) > span { transition-delay: .22s; }
html.is-ready .hero .ln:nth-child(3) > span,
html.is-ready .pagehero .ln:nth-child(3) > span { transition-delay: .34s; }
html.is-ready .hero-e { opacity: 1; transform: none; }
/* The aside holds the lead paragraph, and the lead paragraph is what Largest
   Contentful Paint measures on this page. Fading it in put the metric at
   2.63s: the entrance waits on document.fonts.ready, which on a cold load
   loses to its own 800ms safety net, and then the fade runs for another 800ms
   before the text counts as painted at all.
   So this one element arrives at full opacity and only travels. The headline,
   the plate and the actions still fade as they did — what changes is that the
   sentence a reader is there to read is on screen at first paint. */
html.js .hero-aside.hero-e { opacity: 1; }
html.is-ready .hero-lead.hero-e { transition-delay: .5s; }
html.is-ready .hero-actions.hero-e { transition-delay: .62s; }
html.is-ready .hero-plate { opacity: 1; transition-delay: .25s; }
@media (prefers-reduced-motion: reduce) {
  html.js .hero .ln > span,
  html.js .pagehero .ln > span { transform: none; }
  html.js .hero-e, html.js .hero-plate { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ anchor offset */
[id] { scroll-margin-top: 104px; }

/* ------------------------------------------------------------ footer wordmark wipe */
html.js .foot-mark svg {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1.4s var(--ease) .15s;
}
html.js .foot-mark.in svg { clip-path: inset(0 0 0 0); }
@media (prefers-reduced-motion: reduce) {
  html.js .foot-mark svg { clip-path: none; }
}

/* ------------------------------------------------------------ static/dev mode: kill entrances */
html.no-anim [data-rv],
html.no-anim .hero-e,
html.no-anim .hero-plate { opacity: 1 !important; transform: none !important; transition: none !important; }
html.no-anim .hero .ln > span,
html.no-anim .pagehero .ln > span { transform: none !important; transition: none !important; }
html.no-anim .foot-mark svg { clip-path: none !important; }

/* ------------------------------------------------------------ reveals */
[data-rv] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: calc(var(--d, 0) * 80ms);
}
[data-rv].in { opacity: 1; transform: none; }
html:not(.js) [data-rv] { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-rv] { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ case study */
/* Inner-page hero template — shared by every screen below the home page.
   .pagehero is the canonical name; .case-hero is an alias, so the case
   study and any new inner page render from one rule set. */
/* The home hero drops its H1 96px below the header and has no eyebrow, so it
   is the benchmark: a hero with no eyebrow gets the same 96px of clear air.
   The eyebrow's line-height is pinned below for the arithmetic that follows —
   otherwise its height comes from the parent strut and drifts with the body
   font. */
.pagehero, .case-hero { padding: 96px 0 0; }
/* ...and where a hero does carry one, the padding gives back exactly the
   eyebrow's height + gap, so the headline lands on that same line either way */
.pagehero:has(> .shell > .eyebrow),
.case-hero:has(> .shell > .eyebrow) { padding-top: calc(96px - 48px); }
.pagehero .eyebrow, .case-hero .eyebrow {
  color: var(--fog);
  /* block-level rather than the shared inline-flex: as an inline box it would
     sit on the parent's baseline and the strut's descender would add ~2px the
     arithmetic above cannot see */
  display: flex;
  width: fit-content;
  line-height: 24px;
  margin-bottom: 24px;
}
/* 1.12 matches the home hero's *effective* leading, not its declared .96: the
   reveal wrappers there carry padding .16em with a cancelling -.16em margin,
   and between adjacent lines those margins collapse into one while both
   paddings survive — so every line gap silently gains .16em. Inner-page
   headlines use plain <br>, so the leading has to be stated outright. */
.pagehero .display, .case-hero .display {
  font-size: clamp(40px, 5vw, 128px);
  line-height: 1.12;
  max-width: 22ch;
}
/* A headline built from .ln reveal wrappers gains .16em of leading on its own —
   their cancelling margins collapse between adjacent lines while both paddings
   survive — so it declares .96 to land on the same 1.12em as a plain one. */
.pagehero .display:has(.ln), .case-hero .display:has(.ln) { line-height: .96; }
.pagehero .lead, .case-hero .lead { max-width: 56ch; margin-top: 26px; }
/* The home hero drops to 48px of clear air on a phone and these did not, so
   every screen below the home page started 96px down while the home page
   started at 48 — the one place on the site where the same element sat in two
   different places. Same arithmetic as the desktop rule above: a hero with an
   eyebrow gives back exactly the eyebrow's height + gap, which is the 48. */
@media (max-width: 900px) {
  .pagehero, .case-hero { padding-top: 48px; }
  .pagehero:has(> .shell > .eyebrow),
  .case-hero:has(> .shell > .eyebrow) { padding-top: 0; }
  /* The GoodWork lockup is a 19px wordmark, not a small-caps eyebrow, and at
     zero it sat on the bar. It keeps a little air; its headline lands lower
     than the home page's by the same amount, which is the lesser evil. */
  .pagehero:has(> .shell > .gwmark) { padding-top: 24px; }
}

/* Inner-page hero, two-column variant — headline left, the lead and a way in
   on the right so the wide half is not sitting empty.
   Centred, not bottom-aligned. The home hero bottom-aligns because its headline
   is three lines and its aside is nearly as tall, so the two columns finish
   together. Every inner page has a two-line headline and an aside of whatever
   height its content happens to be, and bottom-aligning those leaves the right
   column floating near the top of a much taller left one. */
.pagehero-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 30%);
  gap: 40px clamp(40px, 6vw, 96px);
  align-items: center;
}
.pagehero-top .display { max-width: none; }
/* no padding-bottom here: it existed to nudge a bottom-aligned column, and
   against a centred one it is just 10px off centre */
.pagehero-top .lead { max-width: 40ch; margin-top: 0; }
.pagehero-aside .alink { margin-top: 18px; }
/* The button row under the lead. It was the same style attribute on all six
   service pages; a class is what six copies of anything should be. */
.pagehero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 26px;
  margin-top: 24px;
}
@media (max-width: 900px) {
  .pagehero-top { grid-template-columns: 1fr; gap: 28px; }
  .pagehero-aside { padding-bottom: 0; }
}
/* ---------------------------------------------------------- concept note --
   The four case studies are concept work: the client, the people and the
   numbers in them are invented, and only the thinking and the craft are ours.
   That has to be legible before a reader carries a figure away with them, so
   this sits between the lead and the meta block rather than in a footnote —
   high enough to be read, quiet enough not to become the page.

   The foil edge is the same device as .gwway--free: a rule that reads as the
   studio speaking about itself. An amber alert box was tried first and was
   wrong — it reads as an error state, and there is nothing wrong with the
   page; it is simply telling you what it is.

   Marked up as <aside> so a screen reader announces it in its own right
   rather than folding it into the lead above. */
.concept-note {
  position: relative;
  overflow: hidden;
  max-width: 78ch;
  margin-top: clamp(30px, 3vw, 46px);
  padding: clamp(18px, 1.8vw, 24px) clamp(20px, 2vw, 28px);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-left: 0;
  border-radius: var(--r-panel);
}
.concept-note::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--brand-foil-linear);
}
.concept-note b {
  display: block;
  margin-bottom: 7px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
}
.concept-note p {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.58;
  color: var(--charcoal);
}
.concept-note a { color: inherit; text-underline-offset: 3px; }

/* The terms. Timeline, cadence, pricing and ownership on a service page;
   client, sector, timeline and scope on a case study. Either way it is the
   first hard information on the screen and the thing most readers came for,
   and it used to be the quietest thing on the page — 16px, ruled on all four
   sides with a line between every cell, which drew a spreadsheet row and set
   the answers smaller than the body copy underneath them.

   It is a dark band now, on the same ink the closing panel uses, so the two
   bookend the page. Against a white page and a photograph directly above it,
   the terms stop being a caption and read as something committed to. Labels
   in cotton so they sit back without going grey on ink; answers in white on
   the working face, because these are figures and dates rather than prose and
   the serif made a delivery date look like a pull quote.

   The dividers are white at .13 rather than a hairline: on ink, a --hairline
   is invisible and a solid white line is a fence. The negative margin pulls
   each divider back into the column gap so the rule sits between the columns
   rather than inside one. */
.case-meta {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 clamp(28px, 3vw, 48px);
  margin-top: 80px;
  padding: clamp(28px, 2.6vw, 46px) clamp(24px, 2.4vw, 42px);
  background: var(--void);
  border-radius: var(--r-panel);
  color: var(--on-void);
}
.case-meta > div + div {
  border-left: 1px solid rgba(255, 255, 255, .13);
  padding-left: clamp(28px, 3vw, 48px);
  margin-left: calc(clamp(28px, 3vw, 48px) * -1);
}
.case-meta dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cotton);
}
.case-meta dd {
  margin: 12px 0 0;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: clamp(18px, 1.4vw, 32px);
  line-height: 1.2;
  letter-spacing: -.018em;
  color: var(--on-void);
  text-wrap: balance;
}
/* One hero band across the site. Aspect ratios made every page a different
   height, so these carry the same clamped height as the home hero plate. */
.pagehero-plate, .case-plate {
  position: relative;   /* the glass chip stands on the photograph */
  margin-left: 0;
  margin-right: 0;
  border-radius: var(--r-panel);
  overflow: hidden;
  margin-top: 72px;
  height: clamp(432px, 58vh, 576px);
}
.pagehero-plate img, .case-plate img { width: 100%; height: 100%; object-fit: cover; }
.case-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr);
  gap: clamp(32px, 5vw, 88px);
}
.case-block + .case-block { margin-top: 112px; }
.case-block h2 { max-width: 14ch; }
/* The copy column is a fraction of the page, and once the page stopped being a
   flat 1280 that fraction went past the point where a sentence can be read:
   126 characters to the line on a 1920 screen, against the 84 the site's own
   .lead settles at. The measure is capped on the paragraphs rather than on the
   column so the rules and hairlines of .case-steps still run out to the column
   edge — the block keeps its right edge, the prose just wraps sooner. 56ch is
   ~85 characters in this face, and it reads the same at 17px and at 15px
   because ch scales with whatever the paragraph is set in. */
.case-copy p, .case-copy li, .case-step p { max-width: 56ch; }
.case-copy p { font-size: 17px; line-height: 1.65; color: var(--charcoal); }
.case-copy p + p { margin-top: 20px; }
.case-steps { border-top: 1px solid var(--mist); margin-top: 8px; }
.case-step {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 20px;
  padding: 22px 0;
  border-bottom: 1px solid var(--hairline);
}
.case-step .num { font-size: 13px; font-weight: 700; color: var(--fog); padding-top: 3px; }
.case-step h3 { font-size: 18px; font-weight: 700; letter-spacing: -.01em; }
.case-step p { font-size: 15px; line-height: 1.55; color: var(--charcoal); margin-top: 6px; }
.case-next {
  display: grid;
  grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
  background: var(--blush);
  border-radius: var(--r-panel);
  overflow: hidden;
  min-height: 340px;
}
.case-next-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(32px, 4vw, 56px);
}
/* the one-line summary under the case title, a size down from the lead */
.case-next-copy .lead { margin-top: 14px; font-size: 16px; }
.case-next .eyebrow { color: rgba(0, 0, 0, .5); margin-bottom: 16px; }
.case-next h2 {
  font-family: var(--font-title);
  font-size: clamp(26px, 2.8vw, 72px);
  letter-spacing: -.008em;
  font-weight: 300;
  line-height: 1.18;
}
.case-next h2 b { font-weight: inherit; }
.case-next-stat {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 22px;
}
.case-next-stat .num {
  font-family: var(--font-head);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1;
}
.case-next-stat .cap {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--charcoal);
}
.case-next .alink { margin-top: 28px; }
.case-next-media { position: relative; overflow: hidden; }
.case-next-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.case-next:hover .case-next-media img { transform: scale(1.03); }
.case-next-pill {
  position: absolute;
  top: 16px; left: 16px;
  background: var(--canvas);
  border-radius: var(--r-pill);
  padding: 7px 15px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
@media (max-width: 860px) {
  /* two up, then the divider only belongs between the pair on each row, and
     the second row needs air above it rather than a rule it cannot align to */
  .case-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 28px; }
  .case-meta > div:nth-child(odd) { border-left: 0; padding-left: 0; margin-left: 0; }
  .case-meta > div:nth-child(n + 3) { margin-top: 30px; }
  .case-block { grid-template-columns: 1fr; gap: 24px; }

/* One column on a phone. Two 130px cells cannot hold "Product design, iOS
   engineering" without breaking it over three lines, and a rule between two
   cells of unequal height reads as ragged rather than ruled. Below the 900px
   block on purpose: same specificity, so whichever comes last wins. */
@media (max-width: 640px) {
  .case-meta { grid-template-columns: minmax(0, 1fr); }
  .case-meta > div + div {
    border-left: 0;
    padding-left: 0;
    margin-left: 0;
    margin-top: 26px;
  }
  .case-meta > div:nth-child(n + 3) { margin-top: 26px; }
}

  .case-next { grid-template-columns: 1fr; }
  .case-next-media { order: -1; aspect-ratio: 16 / 9; }
}

/* ------------------------------------------------------------ cookie notice */
.cookie {
  position: fixed;
  left: max(var(--gutter), calc((100vw - var(--page)) / 2));
  bottom: 24px;
  z-index: 90;
  max-width: 380px;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: 20px 22px;
  box-shadow: 0 16px 44px rgba(0, 0, 0, .10);
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.cookie.is-in { opacity: 1; transform: none; pointer-events: auto; }
/* the tally the notice is describing, drawn */
.cookie p { font-size: 14px; line-height: 1.55; color: var(--charcoal); }
.cookie-actions { display: flex; align-items: center; gap: 14px; margin-top: 14px; }
.cookie-actions .btn { padding: 10px 20px; font-size: 13px; }
.cookie-no { font-size: 13px; font-weight: 500; color: var(--fog); transition: color .18s ease; }
.cookie-no:hover { color: var(--ink); }
@media (max-width: 640px) {
  .cookie { left: 16px; right: 16px; max-width: none; bottom: 88px; }
}

/* ------------------------------------------------------------ madlib brief (closing CTA) */
.madlib-kicker { font-size: 14px; font-weight: 600; color: var(--mist); }
/* the brief sentence runs on the serif with the rest of the headline tier.
   The slots inside it inherit the face, so the sentence stays one voice;
   the menu that drops out of a slot declares the sans for itself, because
   a list of options you pick from is interface, not the headline. */
.madlib {
  font-family: var(--font-title);
  font-size: clamp(26px, 3.2vw, 82px);
  font-weight: 300;
  letter-spacing: -.008em;
  line-height: 1.55;
  max-width: 22ch;
  margin: 26px auto 0;
  color: var(--on-void);
}
.slotwrap { position: relative; display: inline-block; }
.slot {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: .22em;
  padding: 0 .16em;
  border-bottom: 3px solid transparent;
  border-radius: var(--r-card) var(--r-card) 0 0;
  color: var(--on-void);
  cursor: pointer;
  user-select: none;
  transition: background .2s ease, color .2s ease;
}
/* the underline is a slice of the brand foil, not a flat colour.
   200% wide = exactly one repeat of the token shows per slot. */
.slot::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 3px;
  border-radius: 1px;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  background-position: 0% 50%;
  transition: background-position .55s var(--ease), opacity .2s ease;
}
/* empty slots get the same ribbon, punched into dashes by a mask */
.slot.is-empty { color: rgba(255, 255, 255, .38); }
.slot.is-empty::after {
  -webkit-mask-image: repeating-linear-gradient(90deg, #000 0 5px, transparent 5px 9px);
  mask-image: repeating-linear-gradient(90deg, #000 0 5px, transparent 5px 9px);
}
.slot-car {
  width: .42em;
  height: .42em;
  flex: none;
  opacity: .55;
  transition: transform .22s var(--ease), opacity .2s ease;
}
.slot:hover { background: rgba(255, 255, 255, .10); }
.slot:hover::after { background-position: 100% 50%; }
.slot:hover .slot-car { opacity: 1; }
.slot[aria-expanded="true"] { background: rgba(255, 255, 255, .14); }
.slot[aria-expanded="true"]::after {
  -webkit-mask-image: none;
  mask-image: none;
}
.slot[aria-expanded="true"] .slot-car { transform: rotate(180deg); opacity: 1; }

.slotmenu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 12px);
  left: 0;
  min-width: 100%;
  width: max-content;
  max-width: min(340px, 78vw);
  display: flex;
  flex-direction: column;
  padding: 6px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-panel);
  background: #141414;
  box-shadow: 0 20px 48px rgba(0, 0, 0, .55);
  text-align: left;
  animation: slotmenu-in .18s var(--ease) both;
}
.slotmenu[hidden] { display: none; }
.slotmenu--end { left: auto; right: 0; }
@keyframes slotmenu-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}
.slotmenu button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  padding: 11px 12px;
  border-radius: var(--r-card);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.3;
  text-align: left;
  color: rgba(255, 255, 255, .72);
  transition: background .16s ease, color .16s ease;
}
.slotmenu button:hover, .slotmenu button:focus-visible { background: rgba(255, 255, 255, .08); color: var(--on-void); }
.slotmenu button[aria-selected="true"] { color: var(--on-void); }
.slotmenu button[aria-selected="true"]::after {
  content: "";
  width: 6px; height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--bubblegum);
}
@media (prefers-reduced-motion: reduce) {
  .slotmenu { animation: none; }
  .slot-car, .slot::after { transition: none; }
}
@media (max-width: 640px) {
  .slotmenu { max-width: 82vw; }
  .slotmenu button { font-size: 14px; padding: 10px; }
}
.madlib-send { margin-top: 32px; }
.madlib-send[aria-disabled="true"] { opacity: .35; pointer-events: none; }

/* ------------------------------------------------------------ ask bar
   A command bar, not a button: the pill holds a real input, the panel above
   it holds whatever that input turned up. Markup is built by ask.js, so this
   is the only place the look of it is described. */
.ask {
  position: fixed;
  z-index: 95;
  right: max(var(--gutter), calc((100vw - var(--page)) / 2));
  bottom: 24px;
  /* The clip is what the dissolve animates; at rest it has to be wide enough
     to let everything this element owns show through. The panel opens some
     290px above the bar, so a 40px lip cropped it to its own foot — the top
     inset is the panel's whole height and then some. Left and right stay
     tight to the bar; -40px is the ring and its shadow. */
  clip-path: inset(-720px -40px -40px -40px);
  transition: clip-path .5s cubic-bezier(.55, 0, .8, .45), filter .45s ease, opacity .18s ease .34s;
}
/* when the reader reaches the footer the bar disintegrates — the clip
   erodes it bottom to top, blurring as it goes, while ask.js throws real
   dust off the rising edge (.ask-dust). Coming back, the same clip reveals
   it top to bottom and the blur sharpens away. The negative insets leave
   room for the ring and its shadow at rest. */
.ask.is-dust {
  clip-path: inset(-720px -40px calc(100% + 40px) -40px);
  filter: blur(6px);
  opacity: 0;
  pointer-events: none;
}
.ask-dust {
  position: fixed;
  z-index: 94;
  border-radius: 1px;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .ask { transition: opacity .3s ease; }
  .ask.is-dust { clip-path: none; filter: none; }
}
/* With the panel open nothing should be clipped or blurred at all: the
   dissolve is for the resting bar. ask.js already holds the dissolve off
   while the panel is open; this makes it true in the stylesheet too. */
.ask:has(.ask-panel.is-open) { clip-path: none; filter: none; }

.ask-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  width: 420px;
  max-width: calc(100vw - 32px);
  padding: 13px 14px 13px 19px;
  color: var(--ink);
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  /* solid white fill (padding-box) + rotating holographic ring in the border (border-box) */
  background:
    linear-gradient(#fff, #fff) padding-box,
    conic-gradient(from var(--holo-angle), var(--brand-foil)) border-box;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .10), 0 2px 8px rgba(0, 0, 0, .05);
  transition: box-shadow .25s ease, transform .3s var(--ease);
  animation: askHolo 4.5s linear infinite;
  cursor: text;
}
@keyframes askHolo { to { --holo-angle: 360deg; } }
/* Open, the ring quickens — the one bit of motion that says it is listening. */
.ask-bar.is-open { animation-duration: 2.2s; box-shadow: 0 18px 44px rgba(0, 0, 0, .14), 0 3px 10px rgba(0, 0, 0, .06); }
@media (prefers-reduced-motion: reduce) {
  .ask-bar, .ask-bar.is-open { animation: none; }
}
.ask-bar:hover { box-shadow: 0 18px 44px rgba(0, 0, 0, .14), 0 3px 10px rgba(0, 0, 0, .06); }
.ask-glyph { flex: none; width: 16px; height: 16px; color: var(--fog); transition: color .2s ease; }
.ask-bar.is-open .ask-glyph { color: var(--ink); }

.ask-q {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 2px 0;
  transition: opacity .22s ease, transform .22s var(--ease);
}
.ask-q:focus { outline: none; }
.ask-q:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.ask-q::placeholder { color: var(--fog); font-weight: 500; }
.ask-q.is-swap::placeholder { color: transparent; }

.ask-key {
  flex: none;
  font: inherit;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--fog);
  background: var(--cream);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 3px 6px;
}
.ask-bar.is-open .ask-key { display: none; }
.ask-x { display: none; flex: none; color: var(--fog); transition: color .2s ease; }
.ask-x svg { width: 15px; height: 15px; }
.ask-x:hover { color: var(--ink); }
.ask-bar.is-open .ask-x { display: block; }

/* ---- panel ---- */
.ask-panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 12px);
  width: 420px;
  max-width: calc(100vw - 32px);
  border: 2px solid transparent;
  background:
    linear-gradient(#fff, #fff) padding-box,
    conic-gradient(from var(--holo-angle), var(--brand-foil)) border-box;
  border-radius: var(--r-panel);
  overflow: hidden;
  box-shadow: 0 16px 44px rgba(0, 0, 0, .12);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.ask-panel.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  animation: askHolo 2.2s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ask-panel.is-open { animation: none; }
}
.ask-scroll { max-height: min(62vh, 460px); overflow-y: auto; overscroll-behavior: contain; padding: 8px; }
.ask-view { display: none; }
.ask-view.is-on { display: block; }

/* ---- what it read out of the sentence ---- */
.ask-read {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--hairline);
  background: var(--cream);
}
/* display:flex above outranks the UA sheet's [hidden] rule, so say it here. */
.ask-read[hidden] { display: none; }
.ask-read-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  margin-right: 2px;
}
.ask-readchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 8px 4px 10px;
  border: 1px solid var(--mist);
  border-radius: var(--r-pill);
  background: var(--canvas);
  transition: border-color .18s ease, color .18s ease;
}
.ask-readchip svg { width: 11px; height: 11px; color: var(--fog); }
.ask-readchip:hover { border-color: var(--ink); }
.ask-readchip:hover svg { color: var(--ink); }

/* ---- results ---- */
.ask-group {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  padding: 12px 10px 6px;
}
.ask-res {
  display: block;
  width: 100%;
  text-align: left;
  padding: 11px 12px;
  border-radius: var(--r-card);
  transition: background .16s ease;
  animation: askIn .28s var(--ease) both;
  animation-delay: var(--d, 0ms);
}
@keyframes askIn { from { opacity: 0; transform: translateY(5px); } }
@media (prefers-reduced-motion: reduce) { .ask-res { animation: none; } }
.ask-res-t { display: block; font-size: 14px; font-weight: 600; letter-spacing: -.008em; }
.ask-res-b {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--fog);
}
.ask-res-go {
  display: none;
  align-items: center;
  gap: 5px;
  margin-top: 7px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.ask-res-go .ar { width: 13px; height: 13px; }
.ask-res:hover, .ask-res.is-cur { background: var(--cream); }
.ask-res.is-cur { box-shadow: inset 0 0 0 1px var(--hairline); }
.ask-res.is-cur .ask-res-go { display: inline-flex; }

/* ---- nothing matched: say so, and mean it ---- */
.ask-none { padding: 18px 12px 8px; }
.ask-none-t { font-size: 15px; font-weight: 700; letter-spacing: -.012em; }
.ask-none-b { margin-top: 7px; font-size: 13px; line-height: 1.55; color: var(--charcoal); }

/* ---- empty state: recents, prompts, the way out ---- */
.ask-sug {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border-radius: var(--r-card);
  font-size: 13.5px;
  font-weight: 500;
  transition: background .16s ease;
}
.ask-sug svg { flex: none; width: 14px; height: 14px; color: var(--fog); }
.ask-sug:hover { background: var(--cream); }
.ask-sug--go { font-weight: 600; }
.ask-sug--go .ar { width: 14px; height: 14px; margin-left: auto; color: var(--fog); transition: transform .25s var(--ease), color .2s ease; }
.ask-sug--go:hover .ar { transform: translateX(3px); color: var(--ink); }

/* ---- one answer, opened out ---- */
.ask-crumb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 10px;
  padding: 6px 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--fog);
  border-radius: var(--r-card);
  transition: color .18s ease, background .18s ease;
}
.ask-crumb svg { width: 13px; height: 13px; }
.ask-crumb:hover { color: var(--ink); background: var(--cream); }
.ask-answer { margin-top: 9px; padding: 0 4px; font-size: 14px; line-height: 1.6; color: var(--charcoal); }
.ask-links { display: flex; flex-direction: column; margin-top: 12px; }
.ask-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-card);
  font-size: 13.5px;
  font-weight: 600;
  transition: background .16s ease;
}
.ask-link .ar { width: 14px; height: 14px; margin-left: auto; color: var(--fog); transition: transform .25s var(--ease), color .2s ease; }
.ask-link:hover { background: var(--cream); }
.ask-link:hover .ar { transform: translateX(3px); color: var(--ink); }

/* ---- the brief ---- */
.ask-step-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  padding: 0 4px;
}
.ask-title { font-size: 18px; font-weight: 700; letter-spacing: -.012em; margin-top: 8px; padding: 0 4px; }
.ask-step { display: none; }
.ask-step.is-on { display: block; padding: 12px 4px 8px; }
.ask-opts { margin-top: 14px; }
.ask-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 12px;
  border-radius: var(--r-card);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  text-align: left;
  transition: background .18s ease;
}
.ask-item:hover { background: var(--cream); }
.ask-item .ar { width: 14px; height: 14px; margin-left: auto; color: var(--fog); transition: transform .25s var(--ease), color .2s ease; }
.ask-item:hover .ar { transform: translateX(3px); color: var(--ink); }
.ask-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; padding: 0 4px; }
.ask-chip {
  border: 1px solid var(--mist);
  border-radius: var(--r-pill);
  padding: 11px 20px;
  font-size: 14px;
  font-weight: 500;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.ask-chip:hover { background: var(--void); border-color: var(--void); color: var(--on-void); }
.ask-brief {
  margin: 16px 4px 0;
  background: var(--cream);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 14px 16px;
}
.ask-brief .row { display: flex; gap: 10px; font-size: 13.5px; padding: 4px 0; }
.ask-brief .k { color: var(--fog); width: 92px; flex: none; }
.ask-brief .v { font-weight: 600; }
.ask-send { width: 100%; margin-top: 14px; }
.ask-send[disabled] { opacity: .55; pointer-events: none; }

.ask-form { display: flex; flex-direction: column; gap: 10px; margin: 14px 4px 0; }
/* The brief fields' labels. Clipped rather than display:none — the panel is
   three short asks under its own heading and a visible label on each would
   make it a form, which is the thing the ask bar exists not to be. Clipped
   text is still in the accessibility tree, so the name survives the
   placeholder disappearing on the first keystroke. */
.ask-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ask-input {
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 11px 12px;
  transition: border-color .2s var(--ease);
}
.ask-notes { resize: vertical; min-height: 62px; line-height: 1.5; }
.ask-input::placeholder { color: var(--mist); }
.ask-input:focus { outline: none; border-color: var(--ink); }
.ask-input:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
/* Honeypot: off-screen rather than display:none, so a bot filling every field
   still trips it. aria-hidden + tabindex keep it away from real people. */
.ask-hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.ask-note { font-size: 12.5px; color: var(--fog); min-height: 1em; }
.ask-note.is-warn { color: var(--ink); font-weight: 500; }
.ask-done { font-size: 14px; line-height: 1.55; color: var(--charcoal); margin-top: 12px; padding: 0 4px; }
.ask-back {
  display: block;
  margin: 12px auto 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--fog);
  transition: color .18s ease;
}
.ask-back:hover { color: var(--ink); }

/* ---- footer: either a line of reassurance, or the way forward ---- */
.ask-foot {
  padding: 12px 14px;
  text-align: center;
  font-size: 12px;
  color: var(--fog);
  border-top: 1px solid var(--hairline);
  background: var(--cream);
}
.ask-foot a { font-weight: 600; color: var(--charcoal); }
.ask-foot a:hover { color: var(--ink); }
.ask-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  padding: 2px;
}
.ask-cta .ar { width: 14px; height: 14px; transition: transform .25s var(--ease); }
.ask-cta:hover .ar { transform: translateX(3px); }
.ask-cta kbd {
  font: inherit;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--fog);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: 2px 6px;
}

@media (max-width: 640px) {
  .ask { left: 16px; right: 16px; bottom: 16px; }
  .ask-bar { width: auto; padding: 12px 12px 12px 16px; }
  /* 16px stops iOS zooming the whole page on focus */
  .ask-q { font-size: 16px; }
  .ask-panel { width: auto; left: 0; right: 0; }
  .ask-scroll { max-height: min(58vh, 420px); }
  .cookie { bottom: 84px; }
}

/* ------------------------------------------------------------ experiences index */
/* The work index. Deliberately NOT .wpanel — those are full-bleed sticky
   posters for the one or two headline projects; this is the catalogue, so it
   reads as an editorial ledger: hairline-separated rows, numbered by a CSS
   counter, with the artwork held back until a row is hovered. It shares the
   .svcrow hover language (wash fill, title nudge, arrow rotates to 45°) so
   the home index and this one feel like the same instrument. */
.expfilter {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 40px;
  padding-bottom: 32px;
  border-bottom: 1px solid var(--hairline);
}
.expchip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  background: transparent;
  padding: 11px 18px;
  font-size: 14px;
  font-weight: 500;
  color: var(--charcoal);
  transition: background .22s var(--ease), color .22s var(--ease), border-color .22s var(--ease);
}
.expchip-n {
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--fog);
  transition: color .22s var(--ease);
}
.expchip:hover { border-color: var(--ink); color: var(--ink); }
.expchip.is-on { background: var(--void); border-color: var(--void); color: var(--on-void); }
.expchip.is-on .expchip-n { color: rgba(255, 255, 255, .6); }


/* the rows. A CSS counter rather than typed-in numbers: hidden rows don't
   generate boxes, so filtering to three projects renumbers them 01–03 instead
   of leaving 01, 03, 07 behind */
.expindex { counter-reset: exp; border-top: 1px solid var(--mist); }
.exprow {
  position: relative;
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr) 200px 72px 44px;
  align-items: center;
  gap: 24px;
  padding: 34px 24px;
  margin: 0 -24px;
  border-radius: var(--r-card);
  transition: background .3s var(--ease);
}
.exprow::before {
  counter-increment: exp;
  content: counter(exp, decimal-leading-zero);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--fog);
  font-variant-numeric: tabular-nums;
  transition: color .3s ease;
}
/* the hairline belongs to the row, not the container, so a filtered-out row
   takes its rule with it and the list never ends on a doubled line */
.exprow::after {
  content: "";
  position: absolute;
  left: 24px; right: 24px; bottom: 0;
  height: 1px;
  background: var(--mist);
}
.exprow[hidden] { display: none; }
.exprow-main { display: block; transition: transform .35s var(--ease); }
.exprow-title {
  display: block;
  font-family: var(--font-head);
  font-size: clamp(20px, 2vw, 51px);
  line-height: 1.08;
  letter-spacing: -.016em;
  font-weight: 600;
}
.exprow-sub {
  display: block;
  margin-top: 9px;
  font-size: 14px;
  color: var(--charcoal);
}
.exprow-sub b { font-weight: 700; color: var(--ink); }
.exprow-tags {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
  transition: color .3s ease;
}
.exprow-year {
  justify-self: end;
  font-size: 13px;
  font-weight: 500;
  color: var(--fog);
  font-variant-numeric: tabular-nums;
  transition: color .3s ease;
}
.exprow-go {
  justify-self: end;
  width: 44px; height: 44px;
  border-radius: var(--r-pill);
  border: 1px solid var(--mist);
  display: grid;
  place-items: center;
  transition: background .3s ease, border-color .3s ease, transform .35s var(--ease);
}
.exprow-go svg { width: 16px; height: 16px; transition: color .3s ease; }
.exprow:hover { background: var(--wash, var(--blush)); }
.exprow:hover::after { opacity: 0; }
.exprow:hover::before { color: var(--ink); }
.exprow:hover .exprow-main { transform: translateX(10px); }
.exprow:hover .exprow-tags, .exprow:hover .exprow-year { color: var(--charcoal); }
.exprow:hover .exprow-go { background: var(--void); border-color: var(--void); color: var(--on-void); transform: rotate(-45deg); }

/* the artwork, flown to the cursor. Fixed rather than absolute so it is
   positioned from the pointer's viewport coordinates with no scroll maths,
   and pointer-events:none so it can never eat the row's own hover. */
.expthumb {
  position: fixed;
  top: 0; left: 0;
  z-index: 40;
  width: 260px;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-panel);
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(var(--tx, 0), var(--ty, 0), 0) scale(.94);
  transition: opacity .3s var(--ease), transform .45s var(--ease);
  will-change: transform;
}
.expthumb img { width: 100%; height: 100%; object-fit: cover; }
.expthumb.is-on { opacity: 1; transform: translate3d(var(--tx, 0), var(--ty, 0), 0) scale(1); }

.expempty {
  margin-top: 48px;
  text-align: center;
  font-size: 16px;
  color: var(--charcoal);
}
.expempty button {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.expempty[hidden] { display: none; }

@media (max-width: 1100px) {
  .exprow { grid-template-columns: 60px minmax(0, 1fr) 72px 44px; }
  .exprow-tags { display: none; }
}
/* the follower is a pointer affordance — on a touch screen it would either
   never fire or stick to the last tap, so it is switched off outright */
@media (max-width: 860px), (hover: none) {
  .expthumb { display: none; }
}
@media (max-width: 640px) {
  .exprow {
    grid-template-columns: 34px minmax(0, 1fr) 40px;
    /* the number belongs beside the title's first line, not floating in the
       middle of a three-line row */
    align-items: start;
    gap: 8px 14px;
    padding: 24px 16px;
    margin: 0 -16px;
  }
  .exprow::before { padding-top: 4px; }
  .exprow::after { left: 16px; right: 16px; }
  .exprow::before { grid-column: 1; grid-row: 1; }
  .exprow-main { grid-column: 2; grid-row: 1; }
  .exprow-year { grid-column: 2; grid-row: 2; justify-self: start; }
  .exprow-go { grid-column: 3; grid-row: 1 / 3; width: 40px; height: 40px; align-self: center; }
  .exprow:hover .exprow-main { transform: none; }
  /* six chips wrap to four rows on a phone, which pushes the first row off
     screen — so on small viewports the filter becomes one swipeable strip
     that bleeds into the gutters instead */
  .expfilter {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 8px;
    margin: 0 calc(var(--gutter) * -1) 28px;
    padding: 0 var(--gutter) 20px;
  }
  .expfilter::-webkit-scrollbar { display: none; }
  .expchip { flex: none; padding: 9px 15px; font-size: 13px; }
}

/* ------------------------------------------------------------ careers */
/* The openings list reuses .expindex/.exprow wholesale — a list of open roles
   and a list of shipped work are the same kind of object to a reader, and two
   different treatments would be decoration rather than information. Only the
   states around it, the role detail and the application form live here. */
.roles-loading, .roles-empty {
  padding: 40px 0;
  font-size: 16px;
  color: var(--charcoal);
  border-top: 1px solid var(--mist);
}
.roles-empty { max-width: 56ch; }
.roles-empty a { font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
.roles-dot {
  display: inline-block;
  width: 7px; height: 7px;
  margin-right: 8px;
  border-radius: var(--r-pill);
  background: var(--bubblegum);
  animation: rolespulse 1.1s var(--ease) infinite;
}
@keyframes rolespulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* ---- one role, opened ---- */
/* The requirement lists reuse .dossier-list, whose bullet squares are painted
   with var(--wash). That custom property is declared on .dossier, and nothing
   here is inside one — so the squares computed to transparent and the lists
   rendered with invisible bullets. Declare it on the scope that borrows the
   component. */
/* ---- the role, as a modal ----------------------------------------------- */
/* A role used to replace the list, which meant reading one cost you your place
   in the others. It opens over them now, and the bar along the bottom carries
   the way in through both views so the apply button is never scrolled off. */
/* The dialog itself is only a stage: no background, no border. The two cards
   share one grid cell and are told apart by a transform, so applying moves the
   role aside rather than throwing it away. */
.rdlg {
  width: min(1320px, calc(100vw - 24px));
  max-width: none;
  max-height: 92vh;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  overflow: visible;
}
.rdlg::backdrop { background: rgba(0, 0, 0, .55); }
.rdlg[open] { display: grid; }
.rdlg-card {
  grid-area: 1 / 1;
  /* The close button anchors to the card, not to the wider stage behind it. */
  position: relative;
  width: min(1020px, 100%);
  justify-self: center;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  overflow: hidden;
  /* The shadow is on both cards at all times: animating it alongside the move
     is what made the slide judder. One duration, one curve, two properties. */
  box-shadow: 0 30px 80px rgba(0, 0, 0, .26);
  will-change: transform, opacity;
  transition:
    transform .5s var(--ease),
    opacity .5s var(--ease);
}
.rdlg-card--apply { z-index: 2; }
/* The role shrinks toward the edge it leaves behind, so the peek stays put
   while the rest of the card pulls away. */
.rdlg-card--role { transform-origin: right center; }
/* Reading the role: the form is parked off to the right, out of the way. */
.rdlg[data-view="detail"] .rdlg-card--apply {
  opacity: 0;
  transform: translateX(6%) scale(.97);
  pointer-events: none;
}
/* Filling the form: the form does not move or resize, it just becomes the one
   in front. The role slides its whole width to the left and parks 12px clear
   of the form, so all that is left of it is a peek down the side of the
   screen. That peek stays clickable — clicking it is the way back. */
.rdlg[data-view="apply"] .rdlg-card--role {
  transform: translateX(calc(-100% - 12px)) scale(.92);
  opacity: .8;
  cursor: pointer;
}
.rdlg[data-view="apply"] .rdlg-card--role:hover { opacity: 1; }
/* All that shows of the role is a sliver of its right edge, and a stray Apply
   now button in it would read as a live control rather than a card behind. */
.rdlg[data-view="apply"] .rdlg-card--role .rdlg-foot { opacity: 0; }
.rdlg[data-view="apply"] .rdlg-card--apply { transform: none; }
.rdlg[data-view="apply"] .rdlg-card--role .pdialog-x { opacity: 0; pointer-events: none; }
.rdlg-scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* Under a certain width there is no room beside the form, so it simply covers
   the role the way any modal would. */
@media (max-width: 1100px) {
  .rdlg { width: min(1020px, calc(100vw - 24px)); }
  .rdlg[data-view="apply"] .rdlg-card--role { transform: none; opacity: 0; pointer-events: none; }
}
/* The role dialog is only a stage, so on a phone it is the card that has to
   become the sheet. The stage itself keeps no edge of its own. */
@media (max-width: 640px) {
  .rdlg[open] { width: 100%; border-radius: 0; background: none; }
  .rdlg-card {
    width: 100%;
    max-height: 92dvh;
    border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    border-bottom: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .rdlg-card { transition: none; }
}

/* A role opens on a washed foil band carrying the title and the four facts
   people actually scan for. The prose below it is held to a reading measure:
   a modal this wide would otherwise run body copy to 120 characters a line. */
.rdhead {
  padding: clamp(30px, 3.4vw, 52px) clamp(22px, 3vw, 56px) clamp(24px, 2.6vw, 34px);
  background:
    linear-gradient(rgba(255, 255, 255, .86), rgba(255, 255, 255, .86)),
    linear-gradient(120deg, var(--brand-foil));
  border-bottom: 1px solid var(--hairline);
}
.rdhead .eyebrow { color: var(--charcoal); margin-bottom: 14px; }
.rdhead-title {
  font-family: var(--font-title);
  font-size: clamp(28px, 3.4vw, 86px);
  font-weight: 300;
  letter-spacing: -.008em;
  line-height: 1.14;
  max-width: 20ch;
}
.rdhead-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 14px clamp(28px, 3.4vw, 52px);
  margin: clamp(22px, 2.4vw, 30px) 0 0;
}
.rdhead-facts dt {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, .55);
}
.rdhead-facts dd {
  margin: 5px 0 0;
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.012em;
}
.rdhead .rd-paused { margin-top: 22px; }
.rdlg .roledetail { padding: clamp(28px, 3vw, 44px) clamp(22px, 3vw, 56px) clamp(32px, 3vw, 48px); }
.rdlg .rd-lead { margin-top: 0; max-width: 60ch; }
.rdlg .rd-body p { max-width: 68ch; }
.rdlg .rd-h:first-child { margin-top: 0; }
.rdlg-foot {
  flex: none;
  transition: opacity .3s var(--ease);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  padding: 16px clamp(20px, 2.6vw, 28px);
  border-top: 1px solid var(--hairline);
  background: var(--cream);
}
.rdlg-foot-role { margin-right: auto; min-width: 0; }
.rdlg-foot-role b {
  display: block;
  font-family: var(--font-head);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.014em;
}
.rdlg-foot-role span { display: block; font-size: 12.5px; color: var(--fog); margin-top: 2px; }
.rdlg-foot .rd-paused { margin: 0; }
.rdlg .fpanel { border: 0; border-radius: 0; }
.rdlg .rd-apply { margin: 0; padding: 0; border-top: 0; }
@media (max-width: 640px) {
  .rdlg-foot-role { width: 100%; margin-right: 0; }
  .rdlg-foot .btn { width: 100%; justify-content: center; }
}

.roledetail { max-width: 820px; --wash: var(--cotton); }
.rd-paused { font-size: 14px; color: var(--charcoal); max-width: 34ch; }
.rd-lead { margin-top: 32px; }
.rd-body { margin-top: 24px; }
.rd-body p { font-size: 16px; line-height: 1.6; color: var(--charcoal); margin-bottom: 18px; }
.rd-h {
  font-family: var(--font-head);
  font-size: 20px;
  letter-spacing: -.014em;
  font-weight: 600;
  margin: 40px 0 18px;
}
.rd-note {
  margin-top: 40px;
  padding: 20px 24px;
  background: var(--cream);
  border: 1px solid var(--lilac);
  border-radius: var(--r-card);
  font-size: 15px;
  line-height: 1.55;
  color: var(--charcoal);
}

/* ---- the application ---- */
.applyform {
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  padding: clamp(28px, 3.4vw, 44px);
}
.applyform .cform {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 26px 28px;
}
/* the native control is the label's own child, so the whole plate is the hit
   target and no `for=`/id pairing can drift out of step */
.cvdrop {
  position: relative;
  display: block;
  border: 1px dashed var(--mist);
  border-radius: var(--r-card);
  padding: 22px;
  text-align: center;
  cursor: pointer;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.cvdrop input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.cvdrop:hover, .cvdrop.is-over { border-color: var(--ink); background: var(--cream); }
.cvdrop.is-bad { border-color: var(--bubblegum-deep); background: var(--blush); }
.cvdrop-in { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.cvdrop-in svg { width: 20px; height: 20px; color: var(--fog); }
.cvdrop-in b { font-size: 15px; font-weight: 600; }
.cvdrop-sub { font-size: 12.5px; color: var(--fog); }
.cvdrop.is-bad .cvdrop-sub { color: var(--ink); font-weight: 500; }

.applydone { text-align: center; padding: 24px 0 12px; }
.applydone .eyebrow { color: var(--fog); }
.applydone h3 { margin: 16px 0 0; }
.applydone .lead { margin: 16px auto 28px; max-width: 44ch; }

@media (max-width: 640px) {
  .applyform .cform { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------- application status */
.appstat { max-width: 640px; margin: 0 auto; text-align: center; }
.appstat-role { margin-top: 10px; font-size: 15px; color: var(--charcoal); }
.appstat-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
  margin: 44px 0 8px;
}
.appstat-step { text-align: left; }
.appstat-bar {
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--hairline);
  transition: background .4s var(--ease);
}
.appstat-step.is-done .appstat-bar { background: var(--void); }
.appstat-step.is-now .appstat-bar { background: var(--bubblegum); }
.appstat-cap {
  margin-top: 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--fog);
}
.appstat-step.is-now .appstat-cap, .appstat-step.is-done .appstat-cap { color: var(--ink); }
.appstat-now { margin-top: 40px; }
.appstat-log {
  margin-top: 40px;
  text-align: left;
  border-top: 1px solid var(--hairline);
}
.appstat-log li {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  padding: 16px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 14.5px;
}
.appstat-log time { color: var(--fog); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* On a phone the five bars stacked into five full width rules with a word
   under each, which read as five separate statements rather than one journey
   with a position in it. Narrow, the track turns on its side: a rail of dots,
   filled behind you, ringed where you are, hollow ahead. */
@media (max-width: 640px) {
  /* Centred type reads as a poster. This is a status page somebody opened on a
     phone to answer one question, so it aligns left like the rail does, and
     the hero stops holding a screenful of air above it. */
  .appstat { text-align: left; }
  .pagehero:has(.appstat) { padding-top: 44px; }
  .appstat-track {
    grid-auto-flow: row;
    gap: 0;
    margin: 34px 0 6px;
    padding-left: 26px;
    position: relative;
    text-align: left;
  }
  .appstat-track::before {
    content: "";
    position: absolute;
    left: 6px;
    top: 16px;
    bottom: 16px;
    width: 1px;
    background: var(--hairline);
  }
  .appstat-step { position: relative; padding: 8px 0; }
  /* The progress bar becomes the dot on the rail. */
  .appstat-bar {
    position: absolute;
    left: -26px;
    top: 10px;
    width: 11px;
    height: 11px;
    border-radius: var(--r-pill);
    background: var(--canvas);
    border: 1px solid var(--mist);
    transition: background .4s var(--ease), border-color .4s var(--ease);
  }
  .appstat-step.is-done .appstat-bar { background: var(--void); border-color: var(--void); }
  .appstat-step.is-now .appstat-bar {
    background: var(--bubblegum);
    border-color: var(--bubblegum);
    box-shadow: 0 0 0 4px rgba(255, 131, 218, .28);
  }
  .appstat-cap { margin-top: 0; font-size: 12px; }
  .appstat-step.is-now .appstat-cap { font-weight: 800; }
  /* A date and its line have no business fighting for one row this narrow. */
  .appstat-log li { flex-direction: column; align-items: flex-start; gap: 4px; padding: 14px 0; }
  .appstat-log time { font-size: 13px; }
  .appstat-now { margin-top: 28px; }
}


/* ---- about · the values ------------------------------------------------- */
/* Four beliefs on the house top-rule column. Deliberately not the icon cards
   the careers and partners pages use: an icon on a value is decoration standing
   in for a reason, and the reason is the only part worth printing. */
.values {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 clamp(24px, 2.8vw, 52px);
}
.values > div { border-top: 2px solid var(--bubblegum); padding-top: 24px; }
/* The values as a quad of white panels on the cream: one object each, lit
   in the panel's tone on hover, the title in the serif, the paragraph
   beneath. Four grey columns under a pink rule read as a table of
   contents; four panels read as four things. */
.tenets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.tenet {
  --iso-fill: #fff;
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: clamp(280px, 25vw, 380px);
  padding: clamp(28px, 3vw, 48px);
  border-radius: var(--r-panel);
  /* white cards on the cream, the way the contact page sets its four
     addresses; the tone shows only when the object lights up */
  background: var(--canvas);
  border: 1px solid var(--hairline);
  position: relative;   /* the wires sit on its edges */
}
.tenet-mark { display: block; width: clamp(76px, 7.5vw, 108px); aspect-ratio: 1; --iso-size: 92; }
.tenet-title { margin-top: auto; padding-top: 12px; transition: color .25s var(--ease); }
.tenet p { font-size: 15px; line-height: 1.6; color: var(--charcoal); max-width: 42ch; text-wrap: pretty; }
@media (max-width: 760px) {
  .tenets { grid-template-columns: 1fr; gap: 12px; }
  .tenet { min-height: 0; padding: 24px 22px 28px; }
  .tenet-title { padding-top: 6px; }
}
@media (hover: hover) {
  }
.values h3 {
  font-family: var(--font-title);
  font-weight: 300;
  font-size: clamp(19px, 1.5vw, 28px);
  line-height: 1.24;
  letter-spacing: -.01em;
  text-wrap: balance;
}
.values p { margin-top: 14px; font-size: 14px; line-height: 1.6; color: var(--charcoal); }
@media (max-width: 900px) {
  .values { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px clamp(24px, 2.8vw, 52px); }
}
@media (max-width: 540px) { .values { grid-template-columns: minmax(0, 1fr); } }

/* ---- about · the creed -------------------------------------------------- */
/* Each practice written as the thing it replaces. This was a two column ledger
   of dos beside don'ts first, and it read as a comparison table, which invites
   a visitor to weigh two options; there are not two options here. So a couplet
   per row instead: the practice being refused, ruled through, and directly
   under it the one that takes its place. */
.creed { border-top: 1px solid var(--hairline); }
.creed-item {
  display: grid;
  grid-template-columns: clamp(42px, 4.6vw, 88px) minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 0 clamp(24px, 3vw, 56px);
  align-items: start;
  border-bottom: 1px solid var(--hairline);
  padding: clamp(26px, 2.6vw, 42px) 0;
}
.creed-num {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--fog);
  font-variant-numeric: tabular-nums;
  padding-top: .62em;
}
.creed-no, .creed-yes {
  font-family: var(--font-title);
  font-weight: 300;
  font-size: clamp(19px, 1.7vw, 32px);
  line-height: 1.24;
  letter-spacing: -.01em;
  text-wrap: balance;
}
/* the rule through it is the whole argument, so it is drawn at a weight that
   survives the serif rather than at the browser's hairline default */
.creed-no {
  color: var(--fog);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-decoration-color: var(--mist);
}
.creed-yes { margin-top: 10px; }
.creed-why { font-size: 14px; line-height: 1.6; color: var(--charcoal); padding-top: .7em; }
@media (max-width: 900px) {
  .creed-item { grid-template-columns: clamp(34px, 6vw, 48px) minmax(0, 1fr); }
  .creed-why { grid-column: 2; padding-top: 16px; }
}

/* ---- about · the GoodWork strip ----------------------------------------- */
/* The programme as one poster, for pages that mention GoodWork rather than run
   it. Two earlier versions died here. The first was cream, on a page that is
   already cream. The second went dark but stayed a paragraph beside two
   numerals with a text link under it, which is a description of an offer rather
   than an offer. This one is built like the work panels: copy on one side,
   a photograph bleeding to the panel edge on the other, the two figures as a
   row, and a button that starts an application. The inverted ground and the
   foil along its edge are the sub-brand's own. They used to be shared with
   the two ways panel above; that is a docket on the page ground now, so this
   is the only place the inversion lives. */
.gwstrip {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .8fr);
  align-items: stretch;
  background: var(--void);
  color: var(--on-void);
  border-radius: var(--r-panel);
  overflow: hidden;
}
/* the same edge the free build carries on the GoodWork page */
.gwstrip::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--brand-foil));
  background-size: 200% 100%;
  animation: gwPan 14s linear infinite;
}
.gwstrip-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(32px, 3.6vw, 68px);
}
.gwstrip .gwmark { margin-bottom: 22px; }
.gwstrip .gwmark-name { color: var(--on-void); }
.gwstrip .gwmark-by { color: rgba(255, 255, 255, .55); }
.gwstrip .gwmark-by::before { background: rgba(255, 255, 255, .28); }
.gwstrip .lead { margin-top: 18px; color: rgba(255, 255, 255, .72); max-width: 46ch; }

.gwstrip-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(20px, 2.2vw, 40px);
  margin-top: clamp(28px, 2.8vw, 44px);
}
.gwstrip-facts > div { border-top: 1px solid rgba(255, 255, 255, .18); padding-top: 18px; }
/* figure and unit on one baseline, so "Free" and "in full" read as one word */
.gwstrip-fig {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-head);
  font-size: clamp(34px, 3.1vw, 72px);
  font-weight: 800;
  letter-spacing: -.042em;
  line-height: .9;
}
.gwstrip-unit {
  font-size: clamp(12px, 1vw, 15px);
  font-weight: 700;
  letter-spacing: -.01em;
  color: rgba(255, 255, 255, .6);
}
.gwstrip-facts dt {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .72);
  margin-top: 12px;
  line-height: 1.4;
}
.gwstrip-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  margin-top: clamp(28px, 2.8vw, 44px);
}

.gwstrip-media { position: relative; overflow: hidden; }
.gwstrip-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.gwstrip:hover .gwstrip-media img { transform: scale(1.03); }

@media (max-width: 900px) {
  .gwstrip { grid-template-columns: minmax(0, 1fr); }
  /* the photograph leads on a phone, the way the work panels do */
  .gwstrip-media { aspect-ratio: 16 / 10; order: -1; }
  .gwstrip-copy { padding: 30px 24px 36px; }
}
/* two figures across a phone leaves the unit wrapping under its own numeral,
   which reads as a second caption rather than as part of the word */
@media (max-width: 540px) {
  .gwstrip-facts { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .gwstrip-cta .btn { width: 100%; justify-content: center; }
}

/* ---- careers · the studio photograph ------------------------------------ */
/* Just the picture. It was a .wpanel--deep poster with a headline and a stat
   sitting beside it, which made a supporting image behave like a headline
   project. The same clamped band the home hero plate uses, and nothing on it. */
.carephoto {
  position: relative;   /* the screen canvas stands on the photograph */
  border-radius: var(--r-panel);
  overflow: hidden;
  height: clamp(432px, 58vh, 576px);
  background: var(--cream);
}
/* anchored above centre: the plate is much wider than the photo is tall, so
   a centred crop clipped the top of her hair and a high one cut the frame
   at the chest. 32% keeps headroom, the whole face and the shoulders. */
.carephoto img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 32%; }

/* ---- careers · nothing open --------------------------------------------- */
/* A careers page is empty more months than it is full, so the empty state is a
   designed block rather than one grey sentence. It still has to do the two jobs
   the list would have done: say when to look again, and take an application. */
.rolesnone {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
  /* the same cream-on-lilac surface .wpanel--cream uses */
  background: var(--cream);
  border: 1px solid var(--lilac);
  border-radius: var(--r-panel);
  padding: clamp(32px, 4.4vw, 60px);
}
.rolesnone h3 {
  font-family: var(--font-head);
  font-size: clamp(26px, 3vw, 76px);
  line-height: 1.06;
  letter-spacing: -.02em;
  font-weight: 600;
}
.rolesnone p { margin-top: 18px; font-size: 16px; line-height: 1.6; color: var(--charcoal); }
.rolesnone-side { display: flex; flex-direction: column; }
.rolesnone-fact {
  display: flex;
  gap: 14px;
  align-items: baseline;
  padding: 16px 0;
  border-top: 1px solid var(--mist);
}
.rolesnone-fact b {
  font-family: var(--font-head);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -.02em;
  white-space: nowrap;
}
.rolesnone-fact span { font-size: 14px; line-height: 1.45; color: var(--charcoal); }
.rolesnone .btn { margin-top: 26px; align-self: flex-start; }
@media (max-width: 860px) { .rolesnone { grid-template-columns: 1fr; } }

/* ---- careers · a live count in the hero ---------------------------------- */
/* A quiet line, not a badge. The one piece of colour is the same 11px pink
   square .assure-mark uses, which is how this site points at something. */
/* The count is the link. It is an .alink like every other tertiary CTA, plus
   the live dot; there is no separate "see open roles" beside it, because two
   controls one line apart pointing at the same anchor is one too many. */
.carecount { margin-top: 22px; }
.carecount i {
  font-style: normal;
  width: 9px; height: 9px;
  border-radius: 2px;
  background: var(--bubblegum);
  flex: none;
}
.carecount[hidden] { display: none; }

/* ---- careers · the form, inside a role ---------------------------------- */
/* The application used to sit at the bottom of the page as a catch-all. There
   is no general application, so it lives in the role instead: one form per
   opening, and none at all when nothing is open. */
.rd-apply {
  margin-top: 56px;
  padding-top: 44px;
  border-top: 1px solid var(--mist);
  scroll-margin-top: 96px;
}
.rd-apply .rd-h { margin-top: 0; }
.rd-apply .applyform { padding: 0; border: 0; background: none; }
/* On a page the panel needs its own edge. In the dialog the dialog is the edge. */
.fpanel--framed {
  border: 1px solid var(--hairline);
  border-radius: var(--r-panel);
  overflow: hidden;
}

/* ------------------------------------------------------------ products page */
/* The listing is the home page's work posters (.wpanel), reused wholesale so
   our own products get the same stage as the client work — same stack, same
   sticky pile-up, same surfaces. Only two things are new: a CTA row (the
   posters on the home page carry one link, a product carries its buttons),
   and an empty slot for the product that has nothing to show yet. */
.wpanel-cta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 24px;
  margin-top: 34px;
}
.wpanel-cta .alink { margin-top: 0; }
/* the store buttons: the house pill with the store's own mark in it */
.btn .store-glyph { width: 15px; height: 15px; flex: none; }
/* Nothing to show is shown as nothing: the unannounced product gets a dashed
   slot where the others put a screen, not a photograph of something else. */
.wpanel-media--slot {
  display: grid;
  place-items: center;
  border-left: 1.5px dashed var(--mist);
}
.wpanel-media--slot > span:not(.wpanel-pill) {
  width: 60px; height: 60px;
  border-radius: var(--r-pill);
  background: var(--cotton);
  position: relative;
}
.wpanel-media--slot > span:not(.wpanel-pill)::before,
.wpanel-media--slot > span:not(.wpanel-pill)::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  translate: -50% -50%;
  background: var(--void);
  border-radius: 1px;
}
.wpanel-media--slot > span:not(.wpanel-pill)::before { width: 20px; height: 2px; }
.wpanel-media--slot > span:not(.wpanel-pill)::after { width: 2px; height: 20px; }
@media (max-width: 860px) {
  .wpanel-media--slot { border-left: 0; border-bottom: 1.5px dashed var(--mist); min-height: 200px; }
}

/* ------------------------------------------------------------ holographic headings */
/* The wordmark's spotlight, generalised to type. The heading itself stays
   ink; site.js clones its markup into an aria-hidden layer on top, the foil
   is clipped to the glyphs with background-clip, and the same pointer pool
   decides where the finish shows. Same rules as the mark: every stop
   feathered, near-black anchors doing the metal. The pool is wider than the
   wordmark's 72px because a display heading is a much bigger surface.
   Fine pointers only — the layer is never built on touch, and without JS a
   heading is just a heading. */
[data-holo] {
  position: relative;
  --holo-x: 50%;
  --holo-y: 50%;
  --holo-bg: 50%;
  --holo-o: 0;
  --holo-pool: 170px;
}
.holo-text {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* not 0: display headings set line-height under 1, so the last line's
     descenders hang below the heading's own box. The foil background ends
     at the layer's edge, and a glyph past the edge falls back to ink — a
     p or y with a foiled bowl and a black tail. The clone flows from the
     top, so the bottom is the one edge that can grow without misaligning. */
  bottom: -.4em;
  pointer-events: none;
  opacity: var(--holo-o);
  transition: opacity .38s ease;
  background:
    /* the broad sheen travels faster than the colour, same as the mark */
    linear-gradient(100deg,
      rgba(255,255,255,0) 38%,
      rgba(255,255,255,.14) 46%,
      rgba(255,255,255,.52) 50%,
      rgba(255,255,255,.14) 54%,
      rgba(255,255,255,0) 62%),
    var(--brand-foil-linear);
  background-size: 260% 100%, 200% 100%;
  background-position: calc(var(--holo-bg) * 1.4) 50%, var(--holo-bg) 50%;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-mask-image: radial-gradient(circle var(--holo-pool) at var(--holo-x) var(--holo-y),
    rgba(0,0,0,1) 0%,   rgba(0,0,0,.97) 17%, rgba(0,0,0,.89) 30%,
    rgba(0,0,0,.75) 42%, rgba(0,0,0,.56) 54%, rgba(0,0,0,.37) 65%,
    rgba(0,0,0,.20) 76%, rgba(0,0,0,.08) 87%, rgba(0,0,0,0) 100%);
          mask-image: radial-gradient(circle var(--holo-pool) at var(--holo-x) var(--holo-y),
    rgba(0,0,0,1) 0%,   rgba(0,0,0,.97) 17%, rgba(0,0,0,.89) 30%,
    rgba(0,0,0,.75) 42%, rgba(0,0,0,.56) 54%, rgba(0,0,0,.37) 65%,
    rgba(0,0,0,.20) 76%, rgba(0,0,0,.08) 87%, rgba(0,0,0,0) 100%);
}
/* the layer's own text is a clipping shape, nothing more: no ink of its own,
   and none of the backgrounds its clone may have carried along */
.holo-text, .holo-text * {
  color: transparent;
  -webkit-text-fill-color: transparent;
  background-color: transparent;
}

/* ------------------------------------------------------------ footer fine print */
/* The privacy stance in one breath, sitting above the legal line. The claims
   here repeat the cookie notice on purpose: the notice is dismissed once, the
   footer keeps saying it. The links are the way to the whole story. */
.foot-fine {
  font-size: 13px;
  line-height: 1.6;
  color: var(--fog);
  max-width: 72ch;
  margin-bottom: 22px;
}
.foot-fine a, .cookie p a {
  color: var(--charcoal);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--mist);
  transition: color .18s ease, text-decoration-color .18s ease;
}
.foot-fine a:hover, .cookie p a:hover { color: var(--ink); text-decoration-color: var(--ink); }

/* ---- service pages · the menu ------------------------------------------- */
/* The item by item list of what a service actually contains. This is the
   part a buyer screenshots, so it carries ledger weight: numbered rows of
   heading type on hairlines, two abreast, the service's wash on each
   square. Each row is a phrase somebody really searches for. */
/* The twelve, under the four. These were two sections with a headline each,
   twenty words apart, both answering "what do I get" — the four are the shape
   of the work and the twelve are the things you can point at, which is one
   idea at two grains rather than two ideas. One heading covers both now and
   the second becomes a label: the list still gets announced, without a second
   h2 claiming a new subject.

   Block rather than the shared inline-flex, because .eyebrow is inline-level
   and vertical margins on an inline box do not push anything — the same
   reason .pagehero .eyebrow overrides it. */
.svcmenu-lead {
  display: flex;
  width: fit-content;
  margin: clamp(56px, 6vw, 88px) 0 22px;
  color: var(--fog);
}
.svcmenu {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(40px, 5vw, 88px);
  border-top: 1px solid var(--mist);
}
.svcmenu li {
  display: flex;
  align-items: baseline;
  gap: 18px;
  padding: 19px 4px;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-head);
  font-size: clamp(17px, 1.6vw, 21px);
  font-weight: 600;
  letter-spacing: -.014em;
}
.svcmenu .n {
  font-family: var(--font);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--fog);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.svcmenu li::after {
  content: "";
  margin-left: auto;
  align-self: center;
  width: 10px; height: 10px;
  border-radius: 3px;
  background: var(--wash, var(--blush));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
  flex: none;
}
@media (max-width: 760px) {
  .svcmenu { grid-template-columns: 1fr; }
  .svcmenu li { font-size: 17px; padding: 15px 2px; }
}

/* The lean. Every picture a reader can click leans in 1.03 over a slow ease
   while the pointer is on it — a lean rather than a zoom, and the same amount
   everywhere, so the site answers the pointer with one gesture instead of a
   different one per component. It is the only motion left on a photograph: the
   pointer wake that used to drag them about is gone.

   Where it is deliberately not: pictures that go nowhere. A hero plate, a
   product shot, a photograph set into a page. An image that moves under the
   pointer while nothing is clickable is an affordance with nothing behind it.

   An Insights card needs it most, and is written three times because the same
   card is framed three ways: the teaser on the home page, the lead story at
   the top of /insights, and the rack below it. The card has no chrome and its
   title is a run-in, so the lean is the only thing on it saying it is a link
   at all. ---------------------------------------------------------------- */
/* -------------------------------------------------------- insights · the home teaser */
/* The home page's way into the writing. The invitation stands still on the
   left while four posts scroll past it on the right, which is what stops a
   2x2 of photographs reading as a product grid: the sentence beside them is
   doing the asking, and it never leaves.

   No card chrome. The photograph is the card, and the title runs into its own
   summary the way a dictionary entry does — one paragraph to the eye, an h3
   and a p to a crawler. Boxes were tried first and four of them stacked into
   something that looked like pricing tiers.

   Deliberately not .paper-rack: that one is a front page setting a whole
   month's writing in columns with rules between them, and it needs the width
   of /insights to do it. Three columns squeezed into two thirds of the home
   page lost the photographs, which are the only reason anyone stops here. */
.insteaser {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.55fr);
  column-gap: clamp(40px, 6vw, 96px);
  align-items: start;
}
/* the same 108px every sticky panel on the site holds to */
.insteaser-rail { position: sticky; top: 108px; }
.insteaser-rail .eyebrow { color: var(--fog); margin-bottom: 20px; }
.insteaser-title {
  font-family: var(--font-title);
  font-size: clamp(38px, 3.5vw, 88px);
  line-height: 1.1;
  letter-spacing: -.012em;
  font-weight: 300;
}
/* The second half of the sentence, said quieter. --fog and not --mist: at
   this size the lighter grey is the better picture and a 1.6:1 heading, and
   the sentence is the section's only heading rather than decoration. */
.insteaser-title .dim { color: var(--fog); }
.insteaser-rail .btn { margin-top: clamp(28px, 2.6vw, 44px); }

.insgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(38px, 3.6vw, 64px) clamp(24px, 2.4vw, 40px);
}
.inspost { display: flex; flex-direction: column; gap: 20px; }
.inspost-media {
  border-radius: var(--r-panel);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  /* the hero images arrive at every ratio there is; a ground under the crop
     means a slow one is a grey frame rather than a collapsed row */
  background: var(--hairline);
}
.inspost-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
a.inspost:hover .inspost-media img { transform: scale(1.03); }
.inspost-body { font-size: 15px; line-height: 1.6; color: var(--charcoal); }
/* the run-in: inline so the two elements set as one paragraph, and the space
   between them generated, because the markup is written without whitespace */
.inspost-t {
  display: inline;
  font-family: var(--font-head);
  font-size: inherit;
  font-weight: 700;
  letter-spacing: -.002em;
  color: var(--ink);
}
.inspost-t::after { content: " "; }
.inspost-s { display: inline; }
a.inspost:hover .inspost-t {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

@media (max-width: 1100px) {
  .insteaser { grid-template-columns: 1fr; row-gap: clamp(40px, 5vw, 64px); }
  .insteaser-rail { position: static; }
  .insteaser-title { max-width: 18ch; }
}
@media (max-width: 640px) {
  .insgrid { grid-template-columns: 1fr; }
  /* one column: a 4:5 crop is most of a phone screen, and four of them is a
     scroll rather than a teaser */
  .inspost-media { aspect-ratio: 3 / 2; }
}

/* -------------------------------------------------------- insights · the front page */
/* The notes index reads as a front page, not a feed: one lead story at
   full width, then a rack of columns with rules between them the way a
   paper sets them. */
.lead-story {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 32px clamp(40px, 5vw, 88px);
  align-items: center;
  padding: clamp(44px, 5vw, 72px) 0;
  border-bottom: 1px solid var(--mist);
}
.lead-story-copy .eyebrow { color: var(--fog); }
.lead-title {
  font-family: var(--font-title);
  font-size: clamp(34px, 4vw, 102px);
  font-weight: 300;
  letter-spacing: -.01em;
  line-height: 1.12;
  margin-top: 18px;
}
.lead-story-copy .lead { margin-top: 18px; max-width: 46ch; }
.lead-story-copy .alink { margin-top: 28px; }
.lead-media {
  position: relative;
  border-radius: var(--r-panel);
  overflow: hidden;
  aspect-ratio: 4 / 3;
}
.lead-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.lead-story:hover .lead-media img { transform: scale(1.03); }

.paper-rack {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.rack-item {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(28px, 3vw, 40px) clamp(24px, 2.6vw, 40px);
  border-left: 1px solid var(--hairline);
  transition: background .3s var(--ease);
}
/* the first column keeps its text on the shell edge the way svcrow does:
   pulled into the gutter by exactly the gutter, with padding to match, so
   the hover wash ends flush at the page edge. Not the item's own padding
   clamp: past 1230px that grows wider than the gutter and the wash pokes
   out of the page. Every third item, not :first-child — the rack wraps
   into rows now, and a pull applied to the first card alone leaves the
   cards below it in the same column indented behind a stray hairline. */
.rack-item:nth-child(3n + 1) {
  border-left: 0;
  margin-left: calc(var(--gutter) * -1);
  padding-left: var(--gutter);
}
a.rack-item:hover { background: var(--wash, var(--blush)); }
.rack-thumb {
  border-radius: var(--r-panel);
  overflow: hidden;
  aspect-ratio: 16 / 10;
}
.rack-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
a.rack-item:hover .rack-thumb img { transform: scale(1.03); }
.rack-cap {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fog);
}
.rack-title {
  font-family: var(--font-head);
  font-size: clamp(19px, 1.8vw, 46px);
  font-weight: 600;
  letter-spacing: -.016em;
  line-height: 1.15;
}
/* capped for the same reason as .perk p: a third of a wide page is far more
   width than a 14px line should be asked to cross */
.rack-sub { font-size: 14px; line-height: 1.55; color: var(--charcoal); max-width: 48ch; }
.rack-item .alink { margin-top: auto; padding-top: 6px; }

@media (max-width: 960px) {
  .lead-story { grid-template-columns: 1fr; }
  .lead-media { order: -1; }
  .paper-rack { grid-template-columns: 1fr; }
  /* single column: the wash bleeds the whole gutter, not a hardcoded 16px —
     between 640 and 960 the gutter is 32px, and a bleed narrower than the
     gutter leaves the wash hanging half out of the column on hover */
  .rack-item { border-left: 0; border-bottom: 1px solid var(--hairline); padding: 26px var(--gutter); margin: 0 calc(var(--gutter) * -1); }
  .rack-item:nth-child(3n + 1) { margin-left: calc(var(--gutter) * -1); }
  .rack-item:last-child { border-bottom: 0; }
}

/* ---- insights · long form furniture ------------------------------------- */
/* The pieces a post is built from beyond plain paragraphs: the intro that
   runs before the first section, photographs set into the reading column,
   and the pulled line. All of it is written in the post's markdown and
   rendered by insights.js; nothing here is hand-placed markup. */
.post-intro {
  max-width: 62ch;
  font-size: clamp(18px, 1.7vw, 21px);
  line-height: 1.6;
  color: var(--charcoal);
}
.post-intro p + p { margin-top: 18px; }
.post-fig {
  border-radius: var(--r-panel);
  overflow: hidden;
  margin: 28px 0 6px;
}
.post-fig img { width: 100%; height: auto; display: block; }
.post-fig figcaption {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--fog);
  padding: 10px 2px 0;
}
/* the pulled line: the same sentence-level presence the dossier fit note
   carries, so a post's one big claim reads at heading weight */
.post-pull {
  margin: 30px 0 6px;
  padding-left: 22px;
  border-left: 3px solid var(--bubblegum);
  font-family: var(--font-head);
  font-size: clamp(19px, 1.9vw, 48px);
  line-height: 1.4;
  letter-spacing: -.016em;
  font-weight: 600;
  text-wrap: pretty;
}
/* a photograph standing alone between sections gets the full shell width */
.post-wide {
  border-radius: var(--r-panel);
  overflow: hidden;
}
.post-wide img { width: 100%; height: auto; display: block; }

/* ---- insights · loading skeletons --------------------------------------- */
/* The mounts hold the page's real shape while the post arrives, so the
   footer never flashes to the top of an empty page. Plain grey, gentle
   sweep: the same doctrine as the portal skeleton — it says loading and
   nothing else. */
[data-insights-post] { min-height: 90vh; }
.skel { position: relative; overflow: hidden; background: #f1efe9; border-radius: var(--r-card); }
.skel::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.7), transparent);
  animation: skel-sweep 1.3s ease-in-out infinite;
}
@keyframes skel-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }
.skel--line { height: 16px; }
.skel--title { height: 52px; max-width: 720px; }
.skel--lead { height: 20px; max-width: 560px; }
.skel--plate { height: clamp(320px, 48vh, 520px); border-radius: var(--r-panel); }
.skel-stack > * + * { margin-top: 14px; }

/* ============================================================
   WHERE THE SERIF GOES
   Merriweather Light carries the headline tier: the four steps
   of the type scale, the panel titles that run at the same
   sizes, and any h2 with no class of its own. Everything that
   labels a piece of interface, row titles, h3 and h4, stats,
   nav and eyebrows, stays on Plus Jakarta Sans.
   The face is decided by the class, never by the tag. Tag rules
   split .wpanel-title across two faces the moment one page set
   it on an h2 and another on an h3.
   The portal is fenced out at body.p-app rather than selector by
   selector. It builds most of its markup in JS, and three of its
   h2s carry a weight of 700 with no family of their own: a bare
   h2 rule reached them, found no 700 cut of the serif, and had
   the browser fake one. Fencing the whole app off also means a
   view added tomorrow inherits the right face without anyone
   remembering this paragraph.
   The split is the point. A page of serif reads as a document;
   a serif headline over sans working type reads as a studio.
   Only the 300 cut of Merriweather is loaded, so nothing on the
   serif side may ask for a heavier number or the browser fakes
   it. That is also why the <b> rule below exists: inside these
   headings a <b> marks a line or a phrase, never a weight.
   ============================================================ */
body:not(.p-app) h2,
.display, .h-xl, .h-lg, .h-md,
.wpanel-title, .lead-title, .dossier-title,
.rdhead-title, .fpanel-title, .case-next h2, .madlib {
  font-family: var(--font-title);
  font-weight: 300;
}

/* Every headline on the site is broken by hand — <br> between the lines, or
   .ln wrappers in the two heroes — and those breaks were chosen against a
   desktop column. On a phone each segment wraps again inside itself, and what
   comes out is a stranded last word: "We design and / build", "From idea to /
   launch,", "Two ways in, / and one of them is / free". Balancing the lines
   below 760 leaves the author's breaks in place and only decides where the
   overflow inside one of them falls, so the same headline reads as two even
   lines instead of a long one and an orphan. The list is the site's own
   heading tier, the same one that gets the serif face further down. */
@media (max-width: 760px) {
  body:not(.p-app) h2,
  .display, .display .ln, .h-xl, .h-lg, .h-md,
  .wpanel-title, .lead-title, .dossier-title, .pitem-name,
  .rdhead-title, .fpanel-title, .case-next h2, .madlib,
  .svcrow-title, .rack-title, .exprow-title, .gwband-line {
    text-wrap: balance;
  }
  /* The inner-page and case headlines are the one shape balance will not
     touch: a <br> with the second half wrapped in <b>, which the balancer
     leaves alone — "rebuilt for daily / use." stays exactly as it wrapped.
     `pretty` does reach it, because it is looking for the short last line
     rather than trying to even out the block, and pulls it back to
     "rebuilt for / daily use." */
  .pagehero .display, .case-hero .display { text-wrap: pretty; }
}

.display b, .h-xl b, .h-lg b, .h-md b,
.wpanel-title b, .lead-title b, .dossier-title b,
.rdhead-title b, .fpanel-title b, body:not(.p-app) h2 b,
.display strong, .h-xl strong, .h-lg strong, .h-md strong,
.wpanel-title strong, .lead-title strong, .dossier-title strong,
.rdhead-title strong, .fpanel-title strong,
body:not(.p-app) h2 strong { font-weight: inherit; }

/* the studio's phone, under the mail in the brand block */
.foot-phone {
  display: block;
  font-size: 15px;
  font-weight: 500;
  color: var(--charcoal);
  margin-top: 10px;
}
.foot-phone:hover { color: var(--ink); }

/* ---- the display scale above a laptop ----------------------------------- */
/* Every ceiling in this file runs on its vw term to 2560 and holds. Right in
   principle, too steep in practice: on a 2560 Studio Display the home headline
   reached 138px, which is not confident, it is shouting. Everything here comes
   down an eighth at the top.

   Only at the top. Each line below is drawn through two points — the size the
   element already has at 1440, and seven eighths of the size it had at 2560 —
   so it meets the base rule exactly where this query opens and diverges from
   there. A 1440 laptop and everything under it is untouched, which matters
   because those are the sizes the site was drawn at. 1920 comes down about a
   fourteenth, 2560 the full eighth.

   The px term is what holds the join. A bare vw would step down an eighth the
   moment this query took effect, and the jump would be visible on a slow drag
   of the window edge. If a ceiling above ever moves, recompute both terms
   together: .714 of the vw term, and 4.11 times it in px. */
@media (min-width: 1440px) {
  .display { font-size: clamp(42px, 3.29vw + 18.9px, 103px); }
  .h-xl { font-size: clamp(38px, 2.86vw + 16.5px, 89px); }
  .h-lg { font-size: clamp(28px, 2.07vw + 11.9px, 65px); }
  .h-md { font-size: clamp(22px, 1.43vw + 8.2px, 45px); }
  .hero-mono .display { font-size: clamp(40px, 3.86vw + 22.2px, 121px); }
  .svcrow-title { font-size: clamp(22px, 1.57vw + 9.1px, 49px); }
  .wpanel-title { font-size: clamp(26px, 2.07vw + 11.9px, 65px); }
  .dossier-title { font-size: clamp(30px, 2.29vw + 13.2px, 72px); }
  .wpanel-stat .num { font-size: clamp(48px, 3.57vw + 20.6px, 112px); }
  .quote-glyph { font-size: clamp(72px, 5vw + 28.8px, 156px); }
  .quote blockquote { font-size: clamp(23px, 1.71vw + 9.9px, 52px); }
  .stat:first-child .stat-num { font-size: clamp(56px, 4.57vw + 26.3px, 144px); }
  .stat:not(:first-child) .stat-num { font-size: clamp(30px, 2.14vw + 12.3px, 66px); }
  .stat-num { font-size: clamp(52px, 4.29vw + 24.7px, 135px); }
  .ledger-num { font-size: clamp(72px, 7.86vw + 45.3px, 245px); }
  .ledger-row .ledger-num { font-size: clamp(40px, 3vw + 17.3px, 94px); }
  .pitem-name { font-size: clamp(32px, 2.43vw + 14px, 75px); }
  .model h3 { font-size: clamp(22px, 1.43vw + 8.2px, 44px); }
  .fpanel-title { font-size: clamp(26px, 2vw + 11.5px, 63px); }
  .gwband-line { font-size: clamp(24px, 2.07vw + 11.9px, 65px); }
  .pagehero .display, .case-hero .display { font-size: clamp(40px, 3.57vw + 20.6px, 112px); }
  .case-next h2 { font-size: clamp(26px, 2vw + 11.5px, 63px); }
  .madlib { font-size: clamp(26px, 2.29vw + 13.2px, 72px); }
  .exprow-title { font-size: clamp(20px, 1.43vw + 8.2px, 45px); }
  .rdhead-title { font-size: clamp(28px, 2.43vw + 14px, 75px); }
  .rolesnone h3 { font-size: clamp(26px, 2.14vw + 12.3px, 66px); }
  .lead-title { font-size: clamp(34px, 2.86vw + 16.5px, 89px); }
  .rack-title { font-size: clamp(19px, 1.29vw + 7.4px, 40px); }
  .post-pull { font-size: clamp(19px, 1.36vw + 7.8px, 42px); }
}

/* ---- cards on a phone: closer to the edge ------------------------------ */
/* A card inside the 20px gutter, with its own padding inside that, put its
   text 44px in from the edge of a 375px screen, which read as a column down
   the middle of the phone. The card stacks step 8px into the gutter, so a
   card sits 12px from the edge, and their inner padding comes down a step,
   so the text lands nearer 30. The site's own gutter is unchanged: only the
   card stacks reach past it, the way a photograph in the case studies
   does. */
@media (max-width: 640px) {
  .wstack, .offer-stack, .tenets, .qual, .notfor, .pswitch { margin-inline: -8px; }
  .wpanel-copy { padding: 22px 18px 26px; }
  .offer, .offer:nth-child(n) { padding: 22px 18px 26px; }
  .tenet { padding: 22px 18px 26px; }
  .qual-item { padding: 20px 18px 22px; }
  .notfor { padding: 20px 18px 22px; }
  .pitem-subrow .alink { margin-left: 0; }
}
