/* ============================================================
   Velvet - the pages added after the design freeze.

     /locations/<slug>   city pages      (location.html + location.js)
     /blog               the journal     (blog.html    + blog.js)
     /blog/<slug>        a single post   (post.html    + post.js)

   site.css is frozen and belongs to another agent, so nothing in
   here edits it. This sheet is ADDITIVE only: it reuses the shared
   furniture that already exists (.wrap, .section, .eyebrow, .btn,
   .therapist-*, .location-*, .journal-*, .site-nav, .site-footer)
   and adds the handful of classes the three new page types need.

   Every colour goes through the brand tokens by name, with the
   Velvet values repeated as fallbacks so this file still reads
   correctly on its own if it is ever loaded without site.css.
   Palette: --oxblood #0E0002, --wine #1B0206, --gold #C9A45C,
   --ivory #F2E9DC. Display Fraunces, body Instrument Sans.
   ============================================================ */

:root {
  --vp-ink:      var(--ivory, #F2E9DC);
  --vp-muted:    var(--muted, #BCA697);
  --vp-gold:     var(--gold, #C9A45C);
  --vp-gold-soft:var(--gold-bright, #E3C88A);
  --vp-canvas:   var(--oxblood, #0E0002);
  --vp-canvas-2: var(--black-2, #310B16);
  --vp-card:     var(--wine, #1B0206);
  --vp-line:     var(--hairline, rgba(201, 164, 92, 0.16));
  --vp-line-2:   var(--hairline-2, rgba(201, 164, 92, 0.30));
  --vp-ease-1:   var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
  --vp-display:  var(--font-display, 'Fraunces', Georgia, 'Times New Roman', serif);
  --vp-body:     var(--font-body, 'Instrument Sans', system-ui, -apple-system, sans-serif);
  /* The rose pair, added for the careers glam of 2026-08-24 and defined the
     way every alias above is: read the brand token first, carry the Velvet
     value as the fallback, so if site.css ever mints a --rose of its own it
     wins here without an edit. site.css owns no rose today, which is why the
     fallback is the working value. Both were measured with the WCAG 2.1
     formula before they were written down: #DE9DAD reads 9.24:1 on the
     oxblood canvas and 7.88:1 on the wine card, and the canvas ink on a
     filled rose button reads the same 9.24:1 back; #EBB9C5 reads 11.91:1 on
     the canvas. Nothing below uses either as body text; they are the call to
     action and its hover, which is what the brief asked the rose to be. */
  --vp-rose:      var(--rose, #DE9DAD);
  --vp-rose-soft: var(--rose-bright, #EBB9C5);
  /* The deepest gold the brand owns, aliased here on 2026-08-25
     for the careers headline's gradient - champagne at the top of
     the letterforms falling to this at the baseline. Same
     construction as every alias above: read the brand token, keep
     the Velvet value as the fallback. site.css HAS owned this one
     since the freeze (--gold-deep: #8E6E33), so the fallback here
     is the safety net and not the working value. */
  --vp-gold-deep: var(--gold-deep, #8E6E33);
  /* The one new step the block library adds to the spacing scale, built by
     the same construction as .vp-section and --tight below and defined here,
     once, so the scale stays readable in one place. See the block library at
     the foot of this file. */
  --vp-space-generous: clamp(84px, 12vw, 156px);
}

/* Standalone safety net: if site.css is ever missing, the page is still
   dark, legible and readable rather than black-on-white with no layout. */
body.vp-page {
  background: var(--vp-canvas);
  color: var(--vp-ink);
  font-family: var(--vp-body);
}
.vp-page .wrap { max-width: var(--wrap, 1240px); margin: 0 auto; padding: 0 var(--gutter, clamp(20px, 5vw, 64px)); }

/* ---- keyboard access -------------------------------------- */

.vp-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 14px 22px;
  background: var(--vp-gold);
  color: var(--vp-canvas);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: 0 0 var(--radius, 2px) 0;
}
.vp-skip:focus { left: 0; }

.vp-page a:focus-visible,
.vp-page button:focus-visible {
  outline: 2px solid var(--vp-gold);
  outline-offset: 4px;
}
.vp-page main:focus { outline: none; }

/* ---- page rhythm ------------------------------------------ */

.vp-section { padding: clamp(64px, 9vw, 118px) 0; }
.vp-section--tight { padding: clamp(44px, 6vw, 80px) 0; }
.vp-section--lift { background: var(--vp-canvas-2); }
.vp-section__head { max-width: 760px; margin-bottom: clamp(34px, 5vw, 60px); }
.vp-section__head h2 {
  font-family: var(--vp-display);
  font-weight: 380;
  font-size: clamp(30px, 4.2vw, 48px);
  line-height: 1.06;
  letter-spacing: -0.012em;
  margin: 0;
  color: var(--vp-block-head, var(--vp-ink));
}
.vp-section__head h2 em { font-style: italic; color: var(--vp-gold-soft); font-weight: 320; }
.vp-section__head p {
  margin: 16px 0 0;
  color: var(--vp-muted);
  font-size: 17px;
  max-width: 56ch;
}

/* ---- page header ------------------------------------------ */

.vp-hero {
  position: relative;
  padding: clamp(52px, 8vw, 104px) 0 clamp(40px, 6vw, 76px);
  border-bottom: 1px solid var(--vp-line);
  overflow: hidden;
}
.vp-hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.vp-hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.82) brightness(0.5);
}
.vp-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--vp-canvas) 8%, rgb(var(--canvas-rgb, 14 0 2) / 0.72) 58%, rgb(var(--canvas-rgb, 14 0 2) / 0.45) 100%);
}
.vp-hero > .wrap { position: relative; z-index: 1; }
.vp-hero h1 {
  font-family: var(--vp-display);
  font-weight: 320;
  font-size: clamp(38px, 6vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--vp-block-head, var(--vp-ink));
  max-width: 18ch;
}
.vp-hero h1 em { font-style: italic; color: var(--vp-gold-soft); }
.vp-hero__lede {
  margin: clamp(20px, 3vw, 30px) 0 0;
  color: var(--vp-muted);
  font-size: clamp(17px, 1.4vw, 19.5px);
  line-height: 1.7;
  max-width: 58ch;
}
.vp-hero__lede + .vp-hero__lede { margin-top: 16px; }
.vp-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: clamp(28px, 4vw, 40px);
}

/* breadcrumb: small, serialised, never louder than the eyebrow */
.vp-crumbs { margin: 0 0 22px; }
.vp-crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0;
  padding: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--vp-muted);
}
.vp-crumbs a {
  display: inline-block;
  color: var(--vp-muted);
  text-decoration: none;
  transition: color 0.3s var(--vp-ease-1);
  /* Invisible hit-slop: padding widens the tap target toward the 44px
     floor, the matching negative margin cancels it out of layout so
     nothing visibly moves. Checked clear at 390px on every template that
     renders .vp-crumbs (blog index, post, both locations, services,
     join-us): >45px of empty hero above, 22px of non-interactive text
     below, and the adjacent crumb stays 10px+ clear sideways. */
  padding: 15px 8px;
  margin: -15px -8px;
}
.vp-crumbs a:hover { color: var(--vp-gold-soft); }
.vp-crumbs li + li::before { content: "/"; margin-right: 10px; color: var(--vp-gold); opacity: 0.6; }
.vp-crumbs [aria-current="page"] { color: var(--vp-gold); }

/* ---- the visit panel: address, hours, contact --------------
   Only ever rendered from data that exists. A city whose address
   has not been confirmed simply has no panel: see location.js. */

.vp-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: clamp(18px, 2.6vw, 32px);
}
.vp-fact {
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  background: var(--vp-card);
  padding: 30px 30px 28px;
}
.vp-fact h3 {
  margin: 0 0 14px;
  font-family: var(--vp-body);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--vp-gold);
}
.vp-fact p, .vp-fact address {
  margin: 0;
  font-style: normal;
  color: var(--vp-ink);
  font-size: 16px;
  line-height: 1.7;
}
.vp-fact a { color: var(--vp-gold-soft); text-decoration: none; border-bottom: 1px solid var(--vp-line-2); }
.vp-fact a:hover { color: var(--vp-gold); }
.vp-fact__more { margin-top: 14px !important; font-size: 13px; }

.vp-hours { margin: 0; font-size: 15px; }
.vp-hours div {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 7px 0;
  border-bottom: 1px solid var(--vp-line);
}
.vp-hours div:last-child { border-bottom: none; }
.vp-hours dt { color: var(--vp-muted); letter-spacing: 0.06em; }
.vp-hours dd { margin: 0; color: var(--vp-ink); text-align: right; }
.vp-hours .vp-hours__closed { color: var(--vp-muted); }

/* ---- treatments ------------------------------------------- */

.vp-treatments { list-style: none; margin: 0; padding: 0; }
.vp-treatment {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px 32px;
  align-items: baseline;
  padding: 26px 0;
  border-top: 1px solid var(--vp-line);
}
.vp-treatment:last-child { border-bottom: 1px solid var(--vp-line); }
.vp-treatment h3 {
  font-family: var(--vp-display);
  font-weight: 380;
  font-size: 24px;
  margin: 0;
  color: var(--vp-ink);
  letter-spacing: -0.01em;
}
.vp-treatment p {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--vp-muted);
  max-width: 62ch;
}
.vp-treatment__prices {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px 10px;
  margin: 0;
  list-style: none;
  padding: 0;
}
.vp-treatment__prices li {
  border: 1px solid var(--vp-line-2);
  border-radius: 999px;
  padding: 6px 15px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--vp-gold-soft);
  white-space: nowrap;
}
@media (max-width: 560px) {
  .vp-treatment { grid-template-columns: minmax(0, 1fr); }
  .vp-treatment__prices { justify-content: flex-start; }
}

/* ---- areas served ----------------------------------------- */

.vp-areas { display: flex; flex-wrap: wrap; gap: 10px 12px; margin: 0; padding: 0; list-style: none; }
.vp-areas li {
  border: 1px solid var(--vp-line);
  border-radius: 999px;
  padding: 9px 18px;
  color: var(--vp-muted);
  font-size: 13.5px;
  letter-spacing: 0.04em;
}

/* ---- the therapist card's name link (M16) ------------------
   The card markup is shared with the homepage (pages.js
   therapistCard, site.js renderTherapists both build
   h3 > a.t-name-link), but the STYLING for that anchor was never
   shared: it lives in a style element inside index.html, which is
   a file only the homepage loads. So on a city page nothing at all
   named the anchor, the browser's own sheet won by default, and
   eight therapist names rendered #0000EE underlined on oxblood.
   Measured in Chrome: 1.95:1, against 15.24:1 for the heading
   they sit in. Unreadable, and it looks unfinished.

   These are the site's own link rules, not new ones: the same
   three declarations index.html carries, so the two pages now
   render the identical card. site.css and index.html are frozen
   and belong to other agents, so the copy lives here, which is
   what the header of this file says additive means.

   The focus ring is deliberately repeated rather than left to
   site.css's global a:focus-visible: a keyboard user landing on a
   name inside a dark card is the case that has to be obvious, and
   this sheet has to stand up if site.css is ever missing. */

.vp-page .therapist-body h3 .t-name-link {
  color: inherit;
  text-decoration: none;
  transition: color 0.35s var(--vp-ease-1);
}
.vp-page .therapist-card:hover .therapist-body h3 .t-name-link {
  color: var(--vp-gold-soft);
}
.vp-page .therapist-card a:focus-visible {
  outline: 2px solid var(--vp-gold);
  outline-offset: 4px;
}

/* ---- the other cities -------------------------------------
   Reuses .location-grid / .location-card from site.css and adds
   only what a linked card needs. */

.vp-page .location-card { display: block; text-decoration: none; color: inherit; }
.vp-page a.location-card:hover h3 { color: var(--vp-gold-soft); }
.vp-page .location-card h3 { transition: color 0.35s var(--vp-ease-1); }
.vp-page .location-card .vp-card-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--vp-muted);
  transition: color 0.35s var(--vp-ease-1);
}
.vp-page a.location-card:hover .vp-card-link { color: var(--vp-gold-soft); }
.vp-page .location-card .vp-card-link span:last-child { transition: transform 0.4s var(--vp-ease-1); }
.vp-page a.location-card:hover .vp-card-link span:last-child { transform: translateX(5px); }

/* ---- quiet empty states ----------------------------------- */

.vp-empty {
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  padding: clamp(38px, 6vw, 68px);
  text-align: center;
  background: var(--vp-card);
}
.vp-empty p {
  margin: 0 auto;
  max-width: 46ch;
  color: var(--vp-muted);
  font-size: 17px;
  line-height: 1.7;
}
.vp-empty p + p { margin-top: 14px; }
.vp-empty .btn { margin-top: 26px; }

.vp-note {
  margin: 0;
  color: var(--vp-muted);
  font-size: 15px;
}

/* ---- the post itself -------------------------------------- */

.vp-article { padding: clamp(48px, 7vw, 92px) 0 clamp(40px, 6vw, 72px); }
.vp-article__inner { max-width: 760px; margin: 0 auto; }

.vp-postmeta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  margin: 0 0 26px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--vp-muted);
}
.vp-postmeta__sep { color: var(--vp-gold); opacity: 0.6; }
.vp-postmeta a { color: var(--vp-gold-soft); text-decoration: none; }
.vp-postmeta a:hover { color: var(--vp-gold); }

.vp-cover { margin: 0 0 clamp(34px, 5vw, 56px); }
.vp-cover img { width: 100%; height: auto; border-radius: var(--radius, 2px); }
.vp-cover figcaption { margin-top: 12px; color: var(--vp-muted); font-size: 13.5px; }

/* Post and intro bodies are PLAIN TEXT, split into paragraphs in JS and
   written with textContent. Single line breaks inside a paragraph survive
   through white-space, never through markup. */
.vp-prose p {
  margin: 0 0 1.35em;
  color: var(--vp-ink);
  font-size: 18.5px;
  line-height: 1.78;
  white-space: pre-line;
}
.vp-prose p:last-child { margin-bottom: 0; }
.vp-prose--lede p { color: var(--vp-muted); }
@media (max-width: 560px) { .vp-prose p { font-size: 17px; } }

.vp-tags { display: flex; flex-wrap: wrap; gap: 9px 10px; margin: clamp(32px, 4vw, 48px) 0 0; padding: 0; list-style: none; }
.vp-tags li {
  border: 1px solid var(--vp-line);
  border-radius: 999px;
  padding: 7px 16px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--vp-muted);
}

/* who or where a post is attached to */
.vp-attach {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: clamp(34px, 5vw, 52px) 0 0;
  padding: 26px 0 0;
  border-top: 1px solid var(--vp-line);
}
.vp-attach__card {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  border: 1px solid var(--vp-line);
  border-radius: 999px;
  padding: 10px 22px 10px 10px;
  text-decoration: none;
  color: var(--vp-ink);
  transition: border-color 0.4s var(--vp-ease-1), transform 0.4s var(--vp-ease-1);
}
.vp-attach__card:hover { border-color: var(--vp-line-2); transform: translateY(-2px); }
.vp-attach__card img {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  object-fit: cover;
}
.vp-attach__card span { display: block; }
.vp-attach__role {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--vp-gold);
}
.vp-attach__name { font-family: var(--vp-display); font-size: 19px; margin-top: 3px; }

/* ---- the treatment tiles, /services ------------------------
   The owner calls these "rectangles" and "the beginning of a
   carousel": two treatments today, more whenever she adds one.
   Each tile reads TOP TO BOTTOM in the order she asked for -
   name, picture, description, book - and the row shape is chosen
   from the COUNT rather than left to auto-fit, which would leave
   five as a full row and one stretched orphan.

   flex rather than grid for one reason: a last row with fewer
   tiles than the rows above it is CENTRED, which is what stops
   five reading as "three and a mistake".

       1  a single panel, not a lonely column
       2  halves            4  two by two
       3  thirds            5  three then two, centred
       6  three by two      8  three, three, then two, centred

   A phone is one column at every count. Colours go through the
   shared --vp-* aliases at the top of this file, which resolve to
   the brand tokens in site.css; no literal hex is written here.
   ---------------------------------------------------------- */

.vp-tiles {
  --vp-tile-gap: clamp(18px, 2.6vw, 30px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--vp-tile-gap);
  margin: 0;
  padding: 0;
}

.vp-tile {
  flex: 0 1 100%;
  display: flex;
  flex-direction: column;
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--block-radius, var(--radius, 2px));
  padding: clamp(22px, 2.6vw, 32px);
  transition: border-color 0.5s var(--vp-ease-1), transform 0.5s var(--vp-ease-1);
}
.vp-tile:hover { border-color: var(--vp-line-2); transform: translateY(-3px); }

@media (min-width: 720px) {
  .vp-tile { flex-basis: calc(50% - var(--vp-tile-gap) / 2); }
  /* Two lines held for the name from the width where tiles sit side by side,
     so the pictures in a row start on the same line whether a treatment is
     called "Hot Stone" or "Deep Tissue Massage". A longer name still wraps
     and simply takes the room it needs. */
  .vp-tile__name { min-height: 2.24em; }
}
@media (min-width: 1080px) {
  .vp-tile { flex-basis: calc(33.3333% - var(--vp-tile-gap) * 2 / 3); }
  .vp-tiles[data-count="1"] .vp-tile { flex-basis: min(100%, 560px); }
  .vp-tiles[data-count="2"] .vp-tile,
  .vp-tiles[data-count="4"] .vp-tile { flex-basis: calc(50% - var(--vp-tile-gap) / 2); }
}

/* 1. the name, and it is the loudest thing in the tile */
.vp-tile__name {
  font-family: var(--vp-display);
  font-weight: 300;
  font-size: clamp(25px, 2.5vw, 33px);
  line-height: 1.12;
  letter-spacing: -0.01em;
  color: var(--vp-block-head, var(--vp-ink));
  margin: 0 0 clamp(15px, 1.8vw, 22px);
}

/* 2. the picture. The plate keeps its shape whether or not there is
      a photograph in it, so a menu that is only half photographed
      still reads as a row of tiles rather than as a ragged edge. */
.vp-tile__media {
  position: relative;
  margin: 0 0 clamp(15px, 1.8vw, 22px);
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-canvas-2);
}
.vp-tile__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-tile__media.is-empty { border: 1px solid var(--vp-line); }

/* 3. the description, then how long it takes and what it costs */
.vp-tile__body { flex: 1 1 auto; }
.vp-tile__desc {
  margin: 0;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.72;
}
.vp-tile__desc + .vp-tile__desc { margin-top: 12px; }
.vp-tile__meta {
  margin: 14px 0 0;
  color: var(--vp-gold);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* 4. the button, on the floor of the tile, so every tile in a row ends
      on the same line however long the description above it runs */
.vp-tile__book {
  margin-top: clamp(20px, 2.4vw, 28px);
  align-self: flex-start;
}

@media (prefers-reduced-motion: reduce) {
  .vp-tile { transition: none !important; }
}

/* ---- loading + failure ------------------------------------ */

.vp-loading {
  padding: clamp(90px, 16vw, 190px) 0;
  color: var(--vp-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

/* ---- footer additions ------------------------------------- */

.vp-page .site-footer .footer-col a { text-decoration: none; }

/* ---- motion -----------------------------------------------
   .reveal comes from site.css and is only ever ADDED by pages.js
   once the observer is armed, so a page whose script half-runs is
   never left invisible. */
@media (prefers-reduced-motion: reduce) {
  .vp-page * { animation: none !important; }
  .vp-hero__media img,
  .vp-attach__card,
  .vp-page .therapist-body h3 .t-name-link,
  .vp-page .location-card { transition: none !important; }
}

/* ---- the careers page, /join-us ----------------------------
   The invitation to apply was a band at the foot of the
   homepage until 2026-08-21, when the owner asked for it off
   the scroll and given a page of its own. Glam and chic on the
   same dark ground as the rest of the site, and NOTHING here is
   a colour of its own: every value goes through the --vp-*
   aliases at the top of this file, which resolve to the brand
   tokens in site.css. A literal here would be a second, silent
   source of truth for a brand colour (test 8.31), and this
   sheet is the one that is already scanned and already clean,
   which is why the page's rules are here rather than in a
   careers stylesheet of their own.
   ---------------------------------------------------------- */

/* A quieter hero than a city page's: no photograph BEHIND the
   heading, because a photograph on this page belongs where it
   can be looked at rather than under a scrim. Since 2026-08-23
   there is one beside the heading instead, which is the same
   rule kept rather than broken. */
.vp-hero--careers {
  background:
    radial-gradient(circle at 84% -14%, var(--vp-line), transparent 55%),
    var(--vp-canvas);
}

/* ---- the top of the careers page, and its poster ---------- */
/* The grid is drawn whether or not there is a picture, so both
   renderers have one shape to agree on; --split is what says a
   picture is there, which is why a store with no photograph
   gets exactly the single column this page has always had.

   IT WAS TWO COLUMNS UNTIL 2026-08-25, then one, and since
   2026-08-26 it is one on a narrow screen and two on a wide
   one. The banner centres the words across the full width, so a
   screen with nothing to spare has no right-hand column left to
   stand a photograph in, and the two grid children stack with
   the poster as the plate at the foot. Above the width where
   the page stops filling the screen there ARE margins to spare,
   and the careers dress below spends them on a second track.
   The markup did not move for any of it and neither renderer
   changed; where the second child stands is this sheet's
   business, which is why the modifier still means what it
   always meant.

   ONE COLUMN IS WHAT THIS RULE LAYS AND THAT IS DELIBERATE.
   There is no grid-template-columns here, so a grid with a
   photograph and a grid without one both stack by default and
   the stacked case needs nothing written for it at all. The
   second track is stated once, in the careers dress, behind the
   width that pays for it. A base rule laying two tracks and a
   careers rule taking one of them away again would be two rules
   arguing about the same thing, which is what stood here before
   the banner and is what this avoids. */
.vp-hero__grid { display: grid; gap: clamp(28px, 4vw, 56px); align-items: center; }
.vp-hero__poster {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-card);
}
.vp-hero__poster img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-hero__poster.is-empty { border: 1px solid var(--vp-line); }

/* The two lines the banner puts under the heading, 2026-08-25.
   Both are careers-only - neither renderer draws them anywhere
   else - and both stand down when their box is empty, so most
   of what is here paints nothing on a store that has cleared
   them. The rose line's two hairlines are drawn HERE and not by
   a renderer: they are rules, they carry no words, and a screen
   reader has no business meeting them.

   NO text-indent COMPENSATING THE TRACKING, and that is measured
   rather than assumed. Wide letter-spacing is applied after the
   last letter as well as between them, which on a centred line
   can leave the words sitting left of true centre - so the usual
   fix is to indent the line by one tracking step. Measured off
   the rendered page at all four widths, this engine drops the
   trailing step at the end of a line already, so the indent was
   an OVER-correction: it pushed the heading 5px right of centre
   at 1280 and 6px at 1920, which is what 18.59 caught. Every
   line here measures dead centre without it. */
.vp-hero__subline {
  margin: clamp(16px, 2vw, 30px) 0 0;
  color: var(--vp-ink);
  font-size: clamp(11px, 1.05vw, 14px);
  font-weight: 500;
  letter-spacing: 0.12em;
  line-height: 1.6;
  text-transform: uppercase;
}
.vp-hero__vacancy {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.8vw, 24px);
  margin: clamp(22px, 3vw, 38px) 0 0;
  color: var(--vp-rose);
  font-size: clamp(10px, 0.9vw, 12.5px);
  font-weight: 600;
  letter-spacing: 0.3em;
  line-height: 1.5;
  text-transform: uppercase;
}
.vp-hero__vacancy::before,
.vp-hero__vacancy::after {
  content: "";
  flex: 1 1 0;
  height: 1px;
  min-width: 0;
  background: currentColor;
  opacity: 0.34;
}

/* The three columns under the heading. Across the page from 760
   up, which is the width the points list uses, and stacked
   below it. They are quieter than .vp-points on purpose: they
   sit inside the hero, above a heading and a button, and a
   third bordered box in that space fights everything near it. */
.vp-benefits {
  display: grid;
  gap: clamp(12px, 1.6vw, 18px);
  margin: clamp(26px, 3vw, 38px) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 760px) { .vp-benefits { grid-template-columns: repeat(3, 1fr); } }
.vp-benefits li {
  padding: clamp(14px, 1.6vw, 20px) clamp(15px, 1.8vw, 22px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-top: 2px solid var(--vp-gold);
  border-radius: var(--block-radius, var(--radius, 2px));
}
/* The small line ABOVE the big one, 2026-08-25 and optional: a
   row that has nothing in it draws no span at all, so this rule
   paints nothing rather than reserving an empty line. */
.vp-benefits__lead {
  display: block;
  margin-bottom: 8px;
  color: var(--vp-gold-soft);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2em;
  line-height: 1.5;
  text-transform: uppercase;
}
.vp-benefits__label {
  display: block;
  font-family: var(--vp-display);
  font-weight: 400;
  font-size: clamp(16px, 1.35vw, 18.5px);
  line-height: 1.25;
  color: var(--vp-ink);
}
.vp-benefits__detail {
  display: block;
  margin-top: 6px;
  color: var(--vp-muted);
  font-size: 14px;
  line-height: 1.6;
}

/* The live "how many places are open" line, above the heading.
   It ships EMPTY and stays empty until the owner writes one, so
   most of the time this rule paints nothing at all. */
.vp-spaces {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 20px;
  padding: 7px 16px;
  border: 1px solid var(--vp-line-2);
  border-radius: 999px;
  color: var(--vp-gold-soft);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  line-height: 1.5;
  text-transform: uppercase;
}
.vp-spaces i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--vp-gold);
  box-shadow: 0 0 9px 1px var(--vp-gold);
}

/* The intro and the photograph beside it. One column on a
   phone, and one column at every width when there is no
   photograph, so an owner who has not uploaded one gets a
   paragraph across the page rather than a half-width column
   with a hole next to it. */
.vp-split {
  display: grid;
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
}
@media (min-width: 900px) {
  .vp-split { grid-template-columns: 1.05fr 0.95fr; }
  .vp-split--text { grid-template-columns: 1fr; }
  .vp-split--text .vp-split__text { max-width: 62ch; }
}
.vp-split__text h2 {
  font-family: var(--vp-display);
  font-weight: 300;
  font-size: clamp(28px, 3.4vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--vp-block-head, var(--vp-ink));
  margin: 0 0 clamp(18px, 2vw, 26px);
}
.vp-split__media {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-card);
}
.vp-split__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-split__media.is-empty { border: 1px solid var(--vp-line); }
@media (max-width: 899px) {
  .vp-split__media { aspect-ratio: 3 / 2; }
}

/* The two lists: what the studio can tell somebody, and what it
   needs from them. Same shape for both on purpose, because they
   are the same shape to read. */
.vp-points {
  display: grid;
  gap: clamp(16px, 2vw, 26px);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 760px) { .vp-points { grid-template-columns: 1fr 1fr; } }
.vp-points li {
  padding: clamp(20px, 2.2vw, 28px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-left: 2px solid var(--vp-gold);
  border-radius: var(--block-radius, var(--radius, 2px));
  transition: border-color 0.5s var(--vp-ease-1), transform 0.5s var(--vp-ease-1);
}
.vp-points li:hover { border-color: var(--vp-line-2); border-left-color: var(--vp-gold-soft); transform: translateY(-2px); }
.vp-points__label {
  display: block;
  font-family: var(--vp-display);
  font-weight: 400;
  font-size: clamp(19px, 1.7vw, 23px);
  line-height: 1.2;
  color: var(--vp-ink);
}
.vp-points__detail {
  display: block;
  margin-top: 8px;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.7;
}

/* ---- the application form --------------------------------- */
/* Narrow on purpose. A form is read down a single column, and a
   name box the full width of a desktop page invites an essay
   into a field the server caps at 80 characters. */
.vp-apply {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(28px, 3.4vw, 46px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--block-radius, var(--radius, 2px));
}
.vp-apply .vp-section__head { margin-bottom: clamp(24px, 3vw, 34px); }

.vp-form { display: block; }
/* THE DEFAULT LABEL: small gold capitals over a box.
   READ THE NOTE BEFORE ADDING A RULE FOR A LABEL BELOW. This
   selector is `.vp-form label`, which is a class AND an element,
   so it outranks any bare single class - `.vp-form__opt` and
   `.vp-form__check` included. Both of those were written in the
   long-form increment as if they overrode this and neither did:
   every radio and every declaration tick shipped as 11px gold
   capitals in a block box, the tick boxes stretched to the full
   width of their row, and the three declarations came out 41px
   tall, under the project's own 44px tap floor. Nothing said so,
   because a rule that loses is silent.
   So every rule below that dresses a LABEL carries this
   selector in front of it, and any new one must too. */
.vp-form label {
  display: block;
  margin: 20px 0 7px;
  color: var(--vp-gold);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}
.vp-form label:first-child { margin-top: 0; }

/* ---- the long form's sections ------------------------------ */
/* The owner asked for the application "grouped into labelled
   sections with clear dividers", and a fieldset with a legend
   IS that in the markup as well as in the paint. A browser's
   own fieldset carries a border, a margin and its own padding,
   so all three are cleared first and the divider is drawn back
   deliberately, as a hairline above each section but the first. */
.vp-form__group {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0; /* a fieldset will not shrink below its content without this */
}
.vp-form__group + .vp-form__group {
  margin-top: clamp(30px, 3.4vw, 44px);
  padding-top: clamp(26px, 3vw, 38px);
  border-top: 1px solid var(--vp-line);
}
.vp-form__legend {
  display: block;
  float: left;   /* a legend ignores width and margin as a legend; floating it
                    makes it an ordinary block that the clear below closes */
  width: 100%;
  padding: 0;
  margin: 0 0 clamp(14px, 1.6vw, 20px);
  font-family: var(--vp-display);
  font-weight: 400;
  font-size: clamp(20px, 1.9vw, 25px);
  line-height: 1.2;
  letter-spacing: 0;
  text-transform: none;
  color: var(--vp-block-head, var(--vp-ink));
}
.vp-form__legend + * { clear: both; }

/* The small word beside a box she may leave alone. Marked on
   EVERY optional box rather than on the awkward ones, because a
   form where some are marked reads as one where the rest are
   compulsory. */
.vp-form__optional {
  margin-left: 9px;
  color: var(--vp-muted);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
}

/* A question with a fixed set of answers. Its own fieldset, so
   the question is attached to the buttons for anybody not
   looking at the screen. */
.vp-form__choice {
  margin: 20px 0 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
.vp-form__choice legend {
  display: block;
  float: left;
  width: 100%;
  padding: 0;
  margin: 0 0 9px;
  color: var(--vp-gold);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}
.vp-form__opts {
  clear: both;
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}
/* Each answer is a whole tappable block rather than a dot with
   words next to it: at 375 a bare radio is a 13px target and
   the label beside it is the part a thumb actually lands on. */
.vp-form label.vp-form__opt {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 11px 15px;
  background: var(--vp-canvas);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  color: var(--vp-ink);
  font-family: var(--vp-body);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}
.vp-form label.vp-form__opt:hover { border-color: var(--vp-line-2); }
/* `.vp-form input` further down is a class and an element too,
   and it comes LATER in this file, so it took the width, the
   padding and the border of every radio and tick box on the
   form: a 13px radio was being stretched to the width of its
   label. Named through the label here so it outranks both. */
.vp-form label.vp-form__opt input {
  flex: none;
  width: 13px;
  height: 13px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  margin: 0;
  accent-color: var(--vp-gold);
}
.vp-form label.vp-form__opt:focus-within { border-color: var(--vp-gold); }
/* :has() is what lets a chosen answer look chosen without a
   line of script. Where it is not understood the tick or the
   dot still shows which one it is, so nothing is lost. */
.vp-form label.vp-form__opt:has(input:checked) { border-color: var(--vp-gold); background: var(--vp-card); }

/* A single tick and the sentence beside it: the three
   declarations, and nothing else. Left-aligned words at reading
   size, because these are sentences somebody is agreeing to
   rather than labels over a box.

   THE 44px FLOOR IS LOAD-BEARING HERE AND NOWHERE ELSE ON THIS
   FORM. These three are the over-18 confirmation, the accuracy
   confirmation and the Article 9 consent: the controls an
   applicant MUST hit before she can send anything. Every other
   target on the page clears 44px on its own; a one-line
   declaration does not - it is 23px of text and a 18px box - so
   the floor is set here rather than left to whether the sentence
   happens to wrap. Measured at 560, 640, 768, 900, 1024 and
   1280, where before this it was 41px at every one of them and
   44px at 375 only because the sentence ran to three lines.
   min-height with border-box, so the padding is INSIDE the
   floor: a one-line tick is exactly 44 and a wrapped one grows
   from there. Test 18.60 measures it at 375 and 18.61 at the
   six widths above. */
.vp-form label.vp-form__check {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 20px 0 0;
  padding: 10px 0;
  box-sizing: border-box;
  min-height: 44px;
  color: var(--vp-ink);
  font-family: var(--vp-body);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.55;
  text-transform: none;
  cursor: pointer;
}
.vp-form label.vp-form__check input {
  flex: none;
  width: 18px;
  height: 18px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  margin: 1px 0 0;
  accent-color: var(--vp-gold);
}

/* The three photograph boxes, and the line under each one that
   says how it went. A file input cannot be restyled far without
   replacing it, and replacing it costs the keyboard behaviour
   every browser already gives it, so it is left as the browser
   draws it and given room instead. */
.vp-form__photo { margin-top: 20px; }
.vp-form__photo input[type="file"] {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  background: var(--vp-canvas);
  border: 1px dashed var(--vp-line-2);
  border-radius: var(--radius, 2px);
  color: var(--vp-muted);
  font-family: var(--vp-body);
  font-size: 14px;
}
.vp-form__photo input[type="file"]:focus { outline: none; border-color: var(--vp-gold); }
.vp-form__photostate {
  margin: 7px 0 0;
  color: var(--vp-muted);
  font-size: 13px;
  line-height: 1.5;
}
.vp-form__photostate[hidden] { display: none; }

/* The sentences the owner writes between the boxes: the line
   under the photographs heading, the monitoring explanation,
   the note about file types. Reading size and reading colour,
   never label case. */
.vp-form__note {
  margin: 0 0 14px;
  max-width: 62ch;
  color: var(--vp-muted);
  font-size: 15px;
  line-height: 1.65;
}

/* The monitoring section, set apart from the application it is
   not part of. A tinted panel rather than another hairline,
   because "this is a different question" is the whole point of
   it and a divider identical to the others would say the
   opposite. */
.vp-form__group--monitoring {
  padding: clamp(22px, 2.6vw, 30px);
  background: var(--vp-canvas);
  border: 1px solid var(--vp-line);
  border-radius: var(--block-radius, var(--radius, 2px));
}
.vp-form__group--monitoring + .vp-form__group { border-top: 0; }
.vp-form input,
.vp-form textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 13px 15px;
  background: var(--vp-canvas);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  color: var(--vp-ink);
  font-family: var(--vp-body);
  font-size: 16px;
  line-height: 1.5;
  transition: border-color 0.35s var(--vp-ease-1);
}
.vp-form textarea { min-height: 148px; resize: vertical; }
.vp-form input::placeholder,
.vp-form textarea::placeholder { color: var(--vp-muted); opacity: 0.62; }
.vp-form input:focus,
.vp-form textarea:focus { outline: none; border-color: var(--vp-gold); }

/* The length line under the name and the message. Hidden until
   it has something to say, so an empty one is not a gap with a
   margin on it. */
.vp-form__limit {
  margin: 6px 0 0;
  color: var(--vp-muted);
  font-size: 12px;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
.vp-form__limit[hidden] { display: none; }
.vp-form__error {
  margin: 18px 0 0;
  padding: 12px 15px;
  border: 1px solid var(--vp-line-2);
  border-left: 2px solid var(--vp-gold);
  border-radius: var(--radius, 2px);
  color: var(--vp-ink);
  font-size: 14.5px;
  line-height: 1.6;
}
.vp-form__error[hidden] { display: none; }
.vp-form__send { margin-top: clamp(22px, 2.6vw, 30px); }
.vp-form__send[disabled] { opacity: 0.55; cursor: default; }
/* The owner's line under the button. Quiet and centred under
   it, because it is a reassurance rather than an instruction. */
.vp-form__subnote {
  margin: 12px 0 0;
  color: var(--vp-muted);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* The confirmation, which replaces the form in place. */
.vp-form__done { text-align: center; padding: clamp(14px, 2vw, 26px) 0; }
.vp-form__done:focus { outline: none; }
.vp-form__tick {
  width: 54px;
  height: 54px;
  margin: 0 auto 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--vp-gold);
  border-radius: 50%;
  color: var(--vp-gold);
  font-size: 25px;
  line-height: 1;
}
.vp-form__done h3 {
  font-family: var(--vp-display);
  font-weight: 300;
  font-size: clamp(26px, 3vw, 36px);
  line-height: 1.14;
  color: var(--vp-block-head, var(--vp-ink));
  margin: 0 0 12px;
}
.vp-form__done p {
  margin: 0 auto;
  max-width: 44ch;
  color: var(--vp-muted);
  font-size: 16px;
  line-height: 1.7;
}

@media (prefers-reduced-motion: reduce) {
  .vp-points li { transition: none !important; }
}

/* ============================================================
   THE BOOKING FUNNEL ON /services

   The owner, 2026-08-21: "Book Online -> Select a Massage ->
   Show Available Durations & Prices -> Select Duration -> Next
   -> Show Available Time Slots", and "its like a funnel looking
   whos is available". Pressing Book under a treatment replaces
   the tiles in place with that one treatment's lengths, then the
   times that are genuinely free with a masseuse and a studio
   against each one, then the details, then the confirmation.

   WHY THESE RULES ARE HERE AND NOT REUSED. The masseuse page
   already has a picker that walks the same journey one step
   later, and it looks exactly like this. Every rule for it
   (.bk__step, .bk__opt, .bk__day, .bk__slot, .bk__summary and
   the rest) lives in a style element inside public/therapist.html,
   a frozen file that no other page loads, so /services cannot see
   a single one of them. This is the same situation, and the same
   answer, as .t-name-link two hundred lines above: the copy lives
   here, which is what the header of this file says additive means.
   The two are visually identical on purpose, so a visitor who has
   seen one recognises the other.

   The one deliberate difference is the palette route.
   therapist.html names the raw brand tokens (--gold, --ivory,
   --wine-lift); everything below goes through the --vp-* aliases
   at the top of this file, because a literal or a second name
   would be a silent second source of truth for a brand colour and
   test 8.31 exists to catch exactly that.
   ---------------------------------------------------------- */

.vp-hero--funnel { padding-bottom: clamp(28px, 4vw, 52px); }
.vp-funnel__back { margin-top: clamp(22px, 3vw, 30px); }

.vp-funnel [hidden] { display: none !important; }
.vp-funnel__step {
  border-top: 1px solid var(--vp-line);
  padding: clamp(26px, 3.4vw, 38px) 0 clamp(28px, 3.6vw, 40px);
  transition: opacity 0.4s var(--vp-ease-1);
}
.vp-funnel__step:first-of-type { border-top: 0; padding-top: 0; }
/* A step the visitor cannot use yet is dimmed rather than hidden, so the
   shape of what is still to come is visible from the first screen. */
.vp-funnel__step.is-waiting { opacity: 0.42; }
.vp-funnel__step .vp-section__head { margin-bottom: clamp(18px, 2.4vw, 26px); }
.vp-funnel__step .vp-section__head h2 { font-size: clamp(24px, 3vw, 34px); }
.vp-funnel__step .vp-section__head p { font-size: 15.5px; margin-top: 10px; }

/* What happened, at the top, above everything. Never beside the control that
   caused it: a sentence 700px above the fold is a sentence nobody reads. */
.vp-funnel__notice {
  margin: 0 0 clamp(22px, 3vw, 30px);
  padding: 14px 17px;
  border-left: 2px solid var(--vp-gold);
  border-radius: 0 var(--radius, 2px) var(--radius, 2px) 0;
  background: var(--vp-card);
  color: var(--vp-ink);
  font-size: 15px;
  line-height: 1.55;
}
.vp-funnel__notice:focus { outline: none; }

/* THE HEADER IS STICKY, so scrolling something to the top of the document puts
   it UNDERNEATH the bar. Every recovery in the funnel ends by scrolling the
   visitor to whichever of these says what just happened, and every one of them
   landed behind the header: the notice that explains the time went, the times
   step, and the details form. Measured on the live page, the bar is 84px tall
   up to 1279px and 123px from 1280, so this clears the taller of the two with
   about 15px of air and is written once for all three rather than three times.
   Same lever as .contact-block in site.css, which carries the same note. */
.vp-funnel__notice,
.vp-funnel__step { scroll-margin-top: 100px; }
@media (min-width: 1280px) {
  .vp-funnel__notice,
  .vp-funnel__step { scroll-margin-top: 138px; }
}

.vp-funnel__small {
  margin: 12px 0 0;
  color: var(--vp-muted);
  font-size: 13px;
  line-height: 1.55;
}
.vp-funnel__busy { margin: 0; color: var(--vp-gold-soft); font-size: 15px; }

/* ---- step one: the lengths and their prices ---------------- */

.vp-funnel__opts { display: flex; flex-direction: column; gap: 9px; }
.vp-funnel__opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  text-align: left;
  cursor: pointer;
  padding: 16px 19px;
  font: inherit;
  color: var(--vp-ink);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  transition: border-color 0.3s var(--vp-ease-1), background 0.3s var(--vp-ease-1);
}
.vp-funnel__opt:hover { border-color: var(--vp-line-2); background: var(--vp-canvas-2); }
.vp-funnel__opt.is-on { border-color: var(--vp-gold); background: var(--vp-canvas-2); }
.vp-funnel__opt-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.vp-funnel__opt-name { font-family: var(--vp-display); font-size: 20px; font-weight: 380; }
.vp-funnel__opt-side { display: flex; flex-direction: column; align-items: flex-end; flex: 0 0 auto; }
.vp-funnel__opt-price { font-family: var(--vp-display); font-size: 22px; font-weight: 380; color: var(--vp-gold); }

.vp-funnel__actions {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: clamp(20px, 2.6vw, 28px);
}
.vp-funnel__next:disabled { opacity: 0.45; cursor: default; }

/* ---- step two: the studio switch, the days, the times ------ */

.vp-funnel__studios { margin: 0 0 clamp(16px, 2.2vw, 22px); }
.vp-funnel__studio-title {
  margin: 0 0 9px;
  color: var(--vp-gold);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}
.vp-funnel__studio-only { margin: 0; color: var(--vp-muted); font-size: 15px; }
.vp-funnel__studio-opts { display: flex; flex-wrap: wrap; gap: 8px; }
.vp-funnel__studio {
  cursor: pointer;
  padding: 10px 20px;
  font: inherit;
  font-size: 14.5px;
  color: var(--vp-ink);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: 999px;
  transition: border-color 0.3s var(--vp-ease-1), background 0.3s var(--vp-ease-1), color 0.3s var(--vp-ease-1);
}
.vp-funnel__studio:hover { border-color: var(--vp-line-2); }
.vp-funnel__studio.is-on {
  background: var(--vp-gold);
  border-color: var(--vp-gold);
  color: var(--vp-canvas);
  font-weight: 600;
}

.vp-funnel__days {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 2px 2px 12px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.vp-funnel__day {
  flex: 0 0 auto;
  scroll-snap-align: start;
  width: 68px;
  padding: 11px 4px 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  font: inherit;
  color: var(--vp-ink);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  transition: border-color 0.3s var(--vp-ease-1), background 0.3s var(--vp-ease-1);
}
.vp-funnel__day:hover { border-color: var(--vp-line-2); }
.vp-funnel__day.is-on { border-color: var(--vp-gold); background: var(--vp-canvas-2); }
.vp-funnel__day-wd {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--vp-muted);
}
.vp-funnel__day.is-on .vp-funnel__day-wd { color: var(--vp-gold-soft); }
.vp-funnel__day-num { font-family: var(--vp-display); font-size: 23px; line-height: 1.1; font-weight: 380; }
.vp-funnel__day-mo { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--vp-muted); }
/* "Later dates" is a word, not a number, so it needs the room a date does not. */
.vp-funnel__day--more {
  width: auto;
  min-width: 96px;
  justify-content: center;
  padding-left: 16px;
  padding-right: 16px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--vp-gold);
}

.vp-funnel__count {
  margin: 0 0 clamp(16px, 2.2vw, 22px);
  color: var(--vp-gold-soft);
  font-size: 13.5px;
  font-weight: 500;
}

/* ---- one masseuse's times --------------------------------- */
/* EVERY TIME SAYS WHO AND WHERE, and this block is how: the name and the
   studio are the heading over the grid, and both are repeated inside each
   button's own label for anybody listening rather than looking. */
.vp-funnel__who {
  padding: clamp(16px, 2.2vw, 22px) clamp(16px, 2.2vw, 22px) clamp(18px, 2.4vw, 24px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
}
.vp-funnel__who + .vp-funnel__who { margin-top: 14px; }
.vp-funnel__whohead { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.vp-funnel__avatar {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  overflow: hidden;
  border-radius: 50%;
  background: var(--vp-canvas-2);
}
.vp-funnel__avatar img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-funnel__avatar.is-empty { border: 1px solid var(--vp-line); }
.vp-funnel__wholines { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.vp-funnel__whoname { font-family: var(--vp-display); font-size: 19px; font-weight: 380; color: var(--vp-ink); }
.vp-funnel__whometa {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--vp-gold);
}

.vp-funnel__slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: 7px; }
.vp-funnel__slot {
  padding: 12px 6px;
  cursor: pointer;
  font: inherit;
  font-size: 14.5px;
  font-variant-numeric: tabular-nums;
  color: var(--vp-ink);
  background: var(--vp-canvas);
  border: 1px solid var(--vp-line);
  border-radius: var(--radius, 2px);
  transition: border-color 0.25s var(--vp-ease-1), background 0.25s var(--vp-ease-1), color 0.25s var(--vp-ease-1);
}
.vp-funnel__slot:hover { border-color: var(--vp-gold); color: var(--vp-gold-soft); }
.vp-funnel__slot.is-on {
  background: var(--vp-gold);
  border-color: var(--vp-gold);
  color: var(--vp-canvas);
  font-weight: 600;
}

/* ---- nothing free, which is never a blank ------------------ */

.vp-funnel__empty p { max-width: 52ch; }
.vp-funnel__emptyactions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 24px;
}
.vp-funnel__emptyactions .btn { margin-top: 0; }

/* ---- step three: the summary and the details --------------- */

.vp-funnel__summary {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  padding: 17px 19px;
  margin: 0 0 clamp(22px, 3vw, 30px);
  background: var(--vp-canvas-2);
  border-left: 2px solid var(--vp-gold);
  border-radius: 0 var(--radius, 2px) var(--radius, 2px) 0;
}
.vp-funnel__summary-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--vp-muted);
}
.vp-funnel__summary-what { font-family: var(--vp-display); font-size: 21px; font-weight: 380; color: var(--vp-ink); }
.vp-funnel__summary-when { font-size: 15px; color: var(--vp-gold-soft); }
.vp-funnel__summary-price { font-size: 13.5px; color: var(--vp-muted); }
.vp-funnel__change {
  margin-top: 8px;
  padding: 0;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  background: none;
  border: 0;
  color: var(--vp-gold);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.vp-funnel__change:hover { color: var(--vp-gold-soft); }

/* The fields reuse .vp-form from the careers page above, including the 16px
   font size on the input, whose reason is written down in
   public/therapist.html: below 16px, mobile Safari zooms the page in when a
   field takes focus and never zooms back out, and this is the most expensive
   form on the site to lose somebody on. Only the two-column arrangement and
   the required/optional tag are new. */
.vp-funnel__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 18px; }
.vp-funnel__field { min-width: 0; }
.vp-funnel__field.is-wide { grid-column: 1 / -1; }
.vp-funnel__form label { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.vp-funnel__tag {
  flex: 0 0 auto;
  letter-spacing: 0.1em;
  font-size: 9.5px;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--vp-muted);
}
.vp-funnel__form .vp-form__error { flex: 1 1 260px; margin: 0; }
.vp-funnel__form .vp-form__send { margin-top: 0; }

.vp-funnel__done-what { font-size: 17px; color: var(--vp-ink) !important; margin-bottom: 4px !important; }
.vp-funnel__done-when {
  font-family: var(--vp-display);
  font-size: 21px;
  color: var(--vp-gold-soft) !important;
  margin-bottom: 6px !important;
}
.vp-funnel__done-price { font-size: 14px; margin-bottom: 16px !important; }
/* The reference. Set apart rather than shouted: a hairline box and the ink
   colour are enough to say "this line is the one to keep", and the number
   inside it is given room to be read off a screen and typed somewhere else. */
.vp-funnel__done-ref {
  margin: 18px auto !important;
  padding: 12px 16px;
  max-width: 46ch !important;
  border: 1px solid var(--vp-line);
  border-radius: 6px;
  color: var(--vp-ink) !important;
  font-size: 15px !important;
  line-height: 1.6 !important;
  /* The id itself is a long unbroken string. Without this it forces the box
     wider than the panel on a narrow phone and pushes the page sideways. */
  overflow-wrap: anywhere;
}
.vp-form__done .btn { margin-top: 26px; }

@media (max-width: 719px) {
  .vp-funnel__grid { grid-template-columns: 1fr; }
  .vp-funnel__opt { padding: 14px 16px; }
  .vp-funnel__opt-name { font-size: 18px; }
  .vp-funnel__opt-price { font-size: 20px; }
  .vp-funnel__slots { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); }
  .vp-funnel__who { padding: 15px 14px 17px; }
}

@media (prefers-reduced-motion: reduce) {
  .vp-funnel__step,
  .vp-funnel__opt,
  .vp-funnel__studio,
  .vp-funnel__day,
  .vp-funnel__slot { transition: none !important; }
}

/* ============================================================
   THE BLOCK LIBRARY

   Everything the page designer can put on /services and /join-us
   that the sheet above did not already dress. The inventory is
   docs/DESIGNER-BLOCKS.md section 11 and the reflow rules are its
   section 5; the two colour rules are docs/DESIGNER-COLOUR.md
   sections 1.3 and 1.4 and they are the only edits that document
   asks for beyond the five heading declarations already changed
   above.

   THE RULES THIS SECTION KEEPS.

   1. NO COLOUR LITERALS. Every value is a var() chain ending in a
      token site.css already owns, so test 8.31 stays green and a
      retune of the palette reaches these blocks for free. A
      colour of Kate's own never appears here at all: it arrives
      as inline custom properties on the block's own section and
      recolours what is inside it by ordinary inheritance.
   2. ONLY THE EXISTING BREAKPOINTS: 560, 720, 760, 900, 1080.
   3. NOTHING OVERFLOWS AT 360. Every multi-column component
      collapses and every picture is capped at 100% width.
   4. A CLASS ONLY APPEARS WHEN A SETTING IS NOT AT ITS DEFAULT,
      so a default arrangement is byte-for-byte the markup the
      page shipped with. The two exceptions are the quote and the
      invitation, whose default alignment is Middle: they are new
      block types with no legacy markup to match.
   5. ANY TRANSITION ADDED HERE JOINS THE REDUCED-MOTION LIST at
      the foot of this section.
   ============================================================ */

/* ---- the section shell: space, width, alignment ------------ */

.vp-section--generous { padding: var(--vp-space-generous) 0; }

/* Wide rides the existing --wrap variable rather than adding a
   width rule of its own, so the gutter, the centring and every
   .wrap inside the block follow it with nothing new to keep in
   step. Below 1240 the viewport is narrower than either value,
   so Normal and Wide are identical at 360, 720 and 1080 and
   differ only on a wide desktop. */
.vp-section--wide { --wrap: 1440px; }

/* Edge to edge drops the measure but KEEPS the gutter, so the
   words never touch the glass. Only a picture may do that, and
   its own rule below is what lets it. */
.vp-section--edge > .wrap { max-width: none; }

/* Middle, per block. docs/DESIGNER-BLOCKS.md section 2a names the
   set that moves and says everything else keeps its left set; the
   OUTCOME below is exactly that set, and the two places the
   MECHANISM has to differ from the words of 2a are named here
   rather than left for somebody to find with a ruler. Card
   columns of differing lengths look torn when they are centred,
   so only the section head moves on the card-bearing blocks.

   WHY THE WRAP CARRIES text-align AT ALL, when 2a names four
   elements and not their parent: .vp-spaces is an inline-flex
   pill (line 620). An inline-level box is centred by its
   PARENT's text-align and by nothing it can be given itself -
   text-align on the pill lays out the words inside it, and
   margin-inline: auto does nothing to a box that is not
   block-level. So the wrap is the only place the pill can be
   centred from, and h1 and .vp-hero__lede take their centring
   from the same declaration.

   THE TWO CONSEQUENCES, both settled here:
   1. .eyebrow is display: flex (site.css 121), so the
      text-align it inherits centres nothing - its rule and its
      words stay packed at flex-start. Measured before this rule
      existed: under Middle at 1280 the eyebrow's ink sat 112px
      from the left of a 1240px wrap and 1051px from its right.
      A flex row is centred with justify-content, which is what
      2a itself prescribes for the other two flex rows in this
      hero, so the eyebrow gets the same treatment and the
      element 2a names really does centre.
   2. .vp-crumbs is NOT in 2a's set - only its ol is, and only
      for justification - but the wrap's text-align reaches its
      li and its a. Measured: both computed center under Middle
      and start under Left, so the reset below puts them back.
      Their BOXES still move, by the justify-content 2a asks
      for on the ol; it is the words inside them that stay
      left-set.

   3. AND THE REST OF IT, HONESTLY, because text-align inherits
      and a reset per element would be a rule for every element
      in the hero. Measured at 1280, Left against Middle: the
      computed text-align ALSO goes start to center on
      .vp-hero__actions, on the a.btn inside it, and on the i
      and the span inside .vp-spaces. 2a names none of those
      for text-align and names the button nowhere at all - and
      no ink moves on any of them. The button box is its own
      content width plus symmetric padding, so its words sit
      35px from both its edges under Left and under Middle; the
      span in the pill fills its box exactly, 0px both sides
      either way; the i holds no text; and .vp-hero__actions is
      a flex container, whose one flex item is placed by
      justify-content - the value 2a does prescribe for it -
      and never by its own text-align.

      So what MOVES under Middle is exactly the set 2a names:
      h1, .eyebrow, .vp-spaces and .vp-hero__lede, plus the two
      flex rows 2a justifies. What INHERITS is wider than that
      and is inert everywhere it lands past the named set. That
      is the true sentence: "everything not named keeps its
      left set" holds of the PAGE, not of the computed styles,
      and the gap between the two is written down here so that
      the next person to measure it knows they have not found a
      fault. */
.vp-hero--centre > .wrap { text-align: center; }
.vp-hero--centre h1,
.vp-hero--centre .vp-hero__lede { margin-inline: auto; }
.vp-hero--centre .vp-crumbs ol,
.vp-hero--centre .eyebrow,
.vp-hero--centre .vp-hero__actions { justify-content: center; }
.vp-hero--centre .vp-crumbs { text-align: start; }

.vp-section--centre .vp-section__head { margin-inline: auto; }
.vp-section--centre .vp-section__head h2,
.vp-section--centre .vp-section__head p { text-align: center; }
.vp-section--centre .vp-text { margin-inline: auto; }
.vp-section--centre .vp-text h2,
.vp-section--centre .vp-text p { text-align: center; }
.vp-section--centre .vp-quote { margin-inline: auto; }
.vp-section--centre .vp-quote p,
.vp-section--centre .vp-quote__by { text-align: center; }
.vp-section--centre .vp-cta h2,
.vp-section--centre .vp-cta p { text-align: center; }
.vp-section--centre .vp-cta__actions { justify-content: center; }
.vp-section--centre .vp-figure__cap { text-align: center; }

/* ---- the section shell: ground, corners, edge, shadow ------ */

.vp-section--panel { background: var(--vp-card); }

/* HER OWN BACKGROUND BEATS BOTH PRESETS, which is the whole
   reason this rule sits after --lift and --panel rather than
   beside them (docs/DESIGNER-COLOUR.md section 1.3). The custom
   property is written on the same element, inline, by both
   renderers; the class is what turns it into a background. */
.vp-tint { background: var(--vp-block-bg); }

/* Softened corners. --block-radius is set here and read by the
   five shipped components above and by every new one below,
   always with today's --radius as the fallback, so the DEFAULT
   computed value does not move by a pixel and the suite 19
   baselines hold. */
.vp-section--soft { --block-radius: 10px; }

/* The gold hairline. The control exists on exactly two block
   types, and both of them carry cards, so the line is drawn
   around the cards and never around the section: one class, and
   the only context it can reach in this version. If a type
   without cards is ever granted the control, the shell half of
   docs/DESIGNER-BLOCKS.md section 2a is the rule to add here.

   ONE LABEL, ONE MEANING, ON BOTH TYPES. It used to be two.
   Measured at 360/720/760/1080/1440 before this rule was
   rewritten: on points, "None" already drew 1px
   rgba(201,164,92,0.16) and "Gold hairline" drew 1px
   rgba(201,164,92,0.30) - a fourteen-point shift in alpha, not a
   line arriving; on feature-grid the same control went 0px to
   1px. Kate gets no manual, so a control that means one thing
   here and another thing there is a fault whichever way round it
   is. The fix is on the DEFAULT, not on the control: every other
   card on this site - .vp-tile (486), .vp-card (1510), the empty
   plates - already rests at a 1px var(--vp-line) hairline, and
   .vp-grid__item was the only one that did not. It does now (see
   its own rule), so on both types "None (the usual)" is the
   house's faint structural edge and "Gold hairline" lifts that
   same edge one step to var(--vp-line-2). Points cannot go the
   other way: its faint edge is the legacy render section 5.4
   holds byte-exact.

   AND THE HOVER, RESOLVED DELIBERATELY. This rule and
   .vp-points li:hover (700) both weigh (0,2,1), so the later of
   the two wins outright. Written as the border-color shorthand
   it set all four sides and took the hover's left-border colour
   with it: measured, choosing the Edge line moved the hover from
   rgb(227,200,138) to rgb(201,164,92) - it silently switched the
   hover off. The Edge line's business is the hairline on the
   three sides the component draws faintly; the 2px gold left
   rule and its hover lift belong to the points component, so
   this rule now names three sides and leaves the fourth alone.
   The grid item has no such side and takes the shorthand. */
.vp-section--line .vp-points > li {
  border-top-color: var(--vp-line-2);
  border-right-color: var(--vp-line-2);
  border-bottom-color: var(--vp-line-2);
}
.vp-section--line .vp-grid__item { border-color: var(--vp-line-2); }

/* The gentle lift, on the block's panel, cards or picture plate.
   Never on bare text: a shadow under a paragraph is a paragraph
   nobody can read. */
.vp-section--shadow .vp-tile,
.vp-section--shadow .vp-split__media,
.vp-section--shadow .vp-apply,
.vp-section--shadow .vp-figure__media,
.vp-section--shadow .vp-gallery__media,
.vp-section--shadow .vp-card,
.vp-section--shadow .vp-grid__item { box-shadow: var(--lift-shadow); }

/* ---- the hero's own quieter setting ------------------------ */

.vp-hero--quiet h1 { font-size: clamp(30px, 4.2vw, 48px); }

/* ---- the photograph tint -----------------------------------
   docs/DESIGNER-COLOUR.md section 1.4: one extra solid scrim
   layer of the block's own ground, at the chosen strength, laid
   OVER the photograph. Both renderers emit the gradient as the
   custom property --vp-photo-tint on the plate; this is the rule
   that paints it. A background-image on the plate would sit
   BEHIND the picture and tint nothing, which is why the property
   and this pseudo-element exist rather than a plain background.
   With no tint chosen the property is unset, the fallback is
   none, and the layer paints nothing at all. */
.vp-figure__media,
.vp-gallery__media { position: relative; }
.vp-figure__media::after,
.vp-gallery__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--vp-photo-tint, none);
  pointer-events: none;
}

/* ---- forced column modes on the shipped components --------- */

/* Two across from 720 up, at every width above it. The single
   column below 720 is untouched, so no forced mode can break a
   phone. The doubled class ties with the count-driven rules
   above and wins on source order, which is what "forced" means
   here: the count still describes the menu, and her choice still
   describes the row. */
@media (min-width: 720px) {
  .vp-tiles.vp-tiles--2 .vp-tile,
  .vp-tiles.vp-tiles--3 .vp-tile { flex-basis: calc(50% - var(--vp-tile-gap) / 2); }
}
@media (min-width: 1080px) {
  .vp-tiles.vp-tiles--2 .vp-tile { flex-basis: calc(50% - var(--vp-tile-gap) / 2); }
  .vp-tiles.vp-tiles--3 .vp-tile { flex-basis: calc(33.3333% - var(--vp-tile-gap) * 2 / 3); }
}

/* One column everywhere, rather than the two the list takes from
   760 up. */
.vp-points.vp-points--1 { grid-template-columns: 1fr; }

/* CENTRED MEANS THE COLUMN IS CENTRED, and until 2026-08-23 the
   Middle setting moved the heading and left the list where it
   was: on a 1240px page that is a four-item bullet list in
   boxes over 1100px wide with a few words at the far left of
   each, under a heading in the middle. Measured at 1280 on the
   owner's own What We Offer band, which is what asked for it.

   720px is .vp-apply's measure, one band further down the same
   page, so a centred list and the form under it read as one
   column rather than as two different pages. The rows keep
   their own alignment: what he asked to be centred is the list,
   and turning every label into centred text as well would be
   this sheet deciding something he did not ask for. */
.vp-section--centre .vp-points {
  max-width: 720px;
  margin-inline: auto;
}

/* The photograph on the left. Only ever at 900 and up, because
   below that the split is one column and there are no sides to
   swap: the text comes first and the picture under it at 360 and
   at 720 whichever side is chosen. */
@media (min-width: 900px) {
  .vp-split--flip .vp-split__text { order: 2; }
  .vp-split--flip .vp-split__media { order: 1; }
}

/* ---- text: a heading and as many paragraphs as she needs ---- */
/* NOT .vp-prose. That class already exists above, is 18.5px with
   pre-line paragraphs and no measure, and is emitted TODAY inside
   the /join-us intro, so redefining it would restyle a shipped
   section. The two coexist and the text block never uses it. */
.vp-text { max-width: 62ch; }
.vp-text h2 {
  font-family: var(--vp-display);
  font-weight: 380;
  font-size: clamp(30px, 4.2vw, 48px);
  line-height: 1.06;
  letter-spacing: -0.012em;
  color: var(--vp-block-head, var(--vp-ink));
  margin: 0 0 clamp(18px, 2vw, 26px);
}
.vp-text p {
  margin: 0 0 1.35em;
  color: var(--vp-ink);
  font-size: 17px;
  line-height: 1.75;
}
.vp-text p:last-child { margin-bottom: 0; }

/* ---- image: one photograph on its own ---------------------- */

.vp-figure { margin: 0; }
.vp-figure__media {
  overflow: hidden;
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-canvas-2);
}
.vp-figure__media img { display: block; width: 100%; height: auto; }
.vp-figure__media.is-empty { border: 1px solid var(--vp-line); min-height: 180px; }
.vp-figure--strip .vp-figure__media { aspect-ratio: 21 / 9; }
.vp-figure--tall .vp-figure__media { aspect-ratio: 4 / 5; }
.vp-figure--strip .vp-figure__media img,
.vp-figure--tall .vp-figure__media img { height: 100%; object-fit: cover; }
/* A strip on a phone is a sliver unless it is told not to be. */
@media (max-width: 560px) {
  .vp-figure--strip .vp-figure__media { min-height: 150px; }
}
.vp-figure__cap {
  margin: 14px 0 0;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.6;
}
/* Edge to edge lets the PICTURE meet the glass while the caption
   keeps the gutter, which is the one place the gutter is dropped
   anywhere in this library. */
.vp-section--edge .vp-figure__media,
.vp-section--edge .vp-gallery {
  margin-inline: calc(-1 * var(--gutter, clamp(20px, 5vw, 64px)));
}

/* ---- gallery: photographs side by side ---------------------
   Flex rather than grid, DECLARED: section 5.8 asks for a grid
   AND for a short last row to be centred, and a CSS grid cannot
   centre a short last row. The treatment tiles above solve the
   same problem with flex and this is the same construction, so
   the two read as one site and the stated behaviour is the one
   that ships. */
.vp-gallery {
  --vp-gallery-gap: clamp(18px, 2.6vw, 30px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--vp-gallery-gap);
}
.vp-gallery__item { flex: 0 1 calc(50% - var(--vp-gallery-gap) / 2); margin: 0; }
.vp-gallery--2 .vp-gallery__item { flex-basis: 100%; }
@media (min-width: 720px) {
  .vp-gallery--2 .vp-gallery__item { flex-basis: calc(50% - var(--vp-gallery-gap) / 2); }
}
@media (min-width: 900px) {
  .vp-gallery--3 .vp-gallery__item,
  .vp-gallery--4 .vp-gallery__item { flex-basis: calc(33.3333% - var(--vp-gallery-gap) * 2 / 3); }
}
@media (min-width: 1080px) {
  .vp-gallery--4 .vp-gallery__item { flex-basis: calc(25% - var(--vp-gallery-gap) * 3 / 4); }
}
/* One picture alone is never the full width of the page: it sits
   at the two-across measure, centred. */
.vp-gallery__item:only-child { flex-basis: calc(50% - var(--vp-gallery-gap) / 2); }
.vp-gallery__media {
  overflow: hidden;
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-canvas-2);
}
.vp-gallery__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-gallery__media.is-empty { border: 1px solid var(--vp-line); }
.vp-gallery--upright .vp-gallery__media { aspect-ratio: 4 / 5; }
.vp-gallery--square .vp-gallery__media { aspect-ratio: 1 / 1; }
.vp-gallery--wide .vp-gallery__media { aspect-ratio: 3 / 2; }
.vp-gallery__cap {
  margin: 10px 0 0;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.55;
}

/* ---- masseuse cards ---------------------------------------- */
/* Text-bearing cards, so one column below 720 at every choice:
   words need width in a way thumbnails do not. */
.vp-cards {
  --vp-card-gap: clamp(18px, 2.6vw, 30px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--vp-card-gap);
}
.vp-card {
  flex: 0 1 100%;
  display: block;
  padding: clamp(18px, 2vw, 24px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--block-radius, var(--radius, 2px));
  text-decoration: none;
  transition: border-color 0.5s var(--vp-ease-1), transform 0.5s var(--vp-ease-1);
}
.vp-card:hover { border-color: var(--vp-line-2); transform: translateY(-3px); }
@media (min-width: 720px) {
  .vp-cards--2 .vp-card,
  .vp-cards--3 .vp-card,
  .vp-cards--4 .vp-card { flex-basis: calc(50% - var(--vp-card-gap) / 2); }
  .vp-card:only-child { flex-basis: calc(50% - var(--vp-card-gap) / 2); }
}
@media (min-width: 1080px) {
  .vp-cards--3 .vp-card { flex-basis: calc(33.3333% - var(--vp-card-gap) * 2 / 3); }
  .vp-cards--4 .vp-card { flex-basis: calc(25% - var(--vp-card-gap) * 3 / 4); }
}
.vp-card__media {
  /* position: relative carries the corner ribbon below; the plate had no
     positioned children before it, so nothing else moves. */
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  margin-bottom: clamp(14px, 1.6vw, 20px);
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-canvas-2);
}
.vp-card__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-card__media.is-empty { border: 1px solid var(--vp-line); }
/* THE CORNER SASH's own vocabulary on a block card. Every page that draws one
   of these loads site.css first (services.html and join-us.html link both, and
   the designer canvas iframe writes both links into its own head, designer.js
   866), so the whole 45-degree geometry above comes in from there and is NOT
   restated here: one shape, one set of numbers, and a change to the sash
   cannot leave this sheet behind. What this rule does carry is the two colours
   in this sheet's own tokens, so a block page that re-themes its gold re-themes
   the sash with it. The BORDERS have to be repainted as well as the background:
   they are the sash's clipped overhang, and left alone the flush ends would be
   drawn in site.css's gold against this sheet's. The block card has no
   availability dot, so its whole top edge is clear either way. Present only
   when the element is - an unset ribbon renders no element at all. */
.vp-card__media .t-ribbon {
  background: var(--vp-gold);
  border-left-color: var(--vp-gold);
  border-right-color: var(--vp-gold);
  color: var(--vp-canvas);
}
.vp-card__name {
  margin: 0;
  font-family: var(--vp-display);
  font-weight: 300;
  font-size: clamp(25px, 2.5vw, 33px);
  line-height: 1.12;
  letter-spacing: -0.01em;
  color: var(--vp-block-head, var(--vp-ink));
}
.vp-card__title {
  margin: 8px 0 0;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.6;
}
.vp-card__note {
  margin: 8px 0 0;
  color: var(--vp-gold);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ---- feature grid: a row of highlights --------------------- */

.vp-grid {
  --vp-grid-gap: clamp(16px, 2vw, 26px);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--vp-grid-gap);
}
/* THE HAIRLINE IS THE HOUSE DEFAULT, not the Edge line setting.
   Every other card on this site rests at 1px var(--vp-line) -
   .vp-tile (486), .vp-points li (692), .vp-card (1510) - and
   this one did not, which is what made "Edge line" mean a line
   arriving here and a line darkening there. Adding it costs no
   layout: site.css 95 puts everything in border-box, so the
   flex-basis arithmetic below is untouched. */
.vp-grid__item {
  flex: 0 1 100%;
  padding: clamp(20px, 2.2vw, 28px);
  background: var(--vp-card);
  border: 1px solid var(--vp-line);
  border-radius: var(--block-radius, var(--radius, 2px));
}
@media (min-width: 720px) {
  .vp-grid--2 .vp-grid__item,
  .vp-grid--3 .vp-grid__item,
  .vp-grid--4 .vp-grid__item { flex-basis: calc(50% - var(--vp-grid-gap) / 2); }
  .vp-grid__item:only-child { flex-basis: calc(50% - var(--vp-grid-gap) / 2); }
}
@media (min-width: 1080px) {
  .vp-grid--3 .vp-grid__item { flex-basis: calc(33.3333% - var(--vp-grid-gap) * 2 / 3); }
  .vp-grid--4 .vp-grid__item { flex-basis: calc(25% - var(--vp-grid-gap) * 3 / 4); }
}
.vp-grid__media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  margin-bottom: clamp(14px, 1.6vw, 20px);
  border-radius: var(--block-radius, var(--radius, 2px));
  background: var(--vp-canvas-2);
}
.vp-grid__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.vp-grid__media.is-empty { border: 1px solid var(--vp-line); }
.vp-grid__label {
  margin: 0 0 10px;
  font-family: var(--vp-display);
  font-weight: 400;
  font-size: clamp(19px, 1.7vw, 23px);
  line-height: 1.2;
  color: var(--vp-block-head, var(--vp-ink));
}
.vp-grid__item p {
  margin: 0 0 10px;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.7;
}
.vp-grid__item p:last-child { margin-bottom: 0; }

/* ---- quote ------------------------------------------------- */
/* The opening mark is DRAWN, so nobody types one and no screen
   reader reads one. It follows the accent slot because it is
   var(--vp-gold) like every other flourish on the site. */
.vp-quote { margin: 0; max-width: 30ch; }
.vp-quote p {
  margin: 0;
  font-family: var(--vp-display);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(25px, 2.5vw, 33px);
  line-height: 1.28;
  color: var(--vp-ink);
}
.vp-quote p + p { margin-top: 0.6em; }
.vp-quote--grand p { font-size: clamp(28px, 3.4vw, 44px); }
.vp-quote::before {
  content: "\201C";
  display: block;
  font-family: var(--vp-display);
  font-size: 3em;
  line-height: 0.6;
  color: var(--vp-gold);
  opacity: 0.5;
}
.vp-quote__by {
  margin-top: clamp(16px, 2vw, 22px);
  color: var(--vp-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.vp-quote__by cite { font-style: normal; color: var(--vp-gold-soft); }
.vp-quote__detail { display: block; margin-top: 6px; letter-spacing: 0.12em; }

/* ---- invitation -------------------------------------------- */

.vp-cta h2 {
  margin: 0 0 14px;
  font-family: var(--vp-display);
  font-weight: 300;
  font-size: clamp(28px, 3.4vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--vp-block-head, var(--vp-ink));
}
.vp-cta p {
  margin: 0 0 10px;
  color: var(--vp-muted);
  font-size: clamp(17px, 1.4vw, 19.5px);
  line-height: 1.7;
  max-width: 52ch;
}
.vp-section--centre .vp-cta p { margin-inline: auto; }
.vp-cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: clamp(22px, 2.6vw, 30px);
}

/* ---- questions and answers ---------------------------------
   Native details and summary, no script: the served page opens
   and closes with JavaScript off, and the browser-rendered one
   draws the same elements so it behaves identically after. */
.vp-faq { max-width: 760px; }
.vp-faq__item { border-top: 1px solid var(--vp-line); }
.vp-faq__item:last-child { border-bottom: 1px solid var(--vp-line); }
.vp-faq__q {
  padding: 18px 0;
  color: var(--vp-ink);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.5;
  cursor: pointer;
  list-style: none;
}
.vp-faq__q::-webkit-details-marker { display: none; }
.vp-faq__q::after {
  content: "+";
  float: right;
  margin-left: 16px;
  color: var(--vp-gold);
  font-weight: 400;
}
.vp-faq__item[open] .vp-faq__q::after { content: "\2212"; }
.vp-faq__a { padding: 0 0 18px; }
.vp-faq__a p {
  margin: 0 0 10px;
  color: var(--vp-muted);
  font-size: 15.5px;
  line-height: 1.75;
  max-width: 62ch;
}
.vp-faq__a p:last-child { margin-bottom: 0; }

/* ---- breathing space --------------------------------------- */

/* THE BASE CLASS CARRIES THE BLOCK, and until this increment it
   carried nothing: both renderers emitted vp-divider (pages.js
   1900, seo.js 3249) and not one rule in any sheet matched the
   bare selector, so the whole of the block's geometry hung off
   the three size modifiers. docs/DESIGNER-BLOCKS.md sections
   5.14 and 11 both name vp-divider as a shipped class, so the
   answer is a rule rather than dropping it from the markup.
   Written the way .vp-section--soft writes --block-radius: the
   base owns the geometry and the default step, the modifiers own
   only the step they are named for. Computed padding is
   unchanged at all three sizes (measured at 1280: 25.6 / 51.2 /
   76.8px, side padding 0 throughout - the wrap inside holds the
   gutter). What it buys: a divider whose size class ever goes
   missing - a walker taught a fourth size before the sheet is -
   now renders the medium step instead of collapsing to no space
   at all, which is a block that has silently stopped existing. */
.vp-divider { padding: var(--vp-divider-step, clamp(28px, 4vw, 56px)) 0; }
.vp-divider--s { --vp-divider-step: clamp(16px, 2vw, 26px); }
.vp-divider--m { --vp-divider-step: clamp(28px, 4vw, 56px); }
.vp-divider--l { --vp-divider-step: clamp(44px, 6vw, 80px); }
.vp-divider__line {
  margin: 0;
  border: 0;
  border-top: 1px solid var(--vp-line-2);
}

/* ============================================================
   THE CAREERS GLAM, 2026-08-24

   The owner asked for /join-us taken to a luxury standard: the
   feel of a high end spa brand, gold worn as jewellery rather
   than decoration, a rose call to action, and space as the
   loudest signal of expense. Everything below is that one page's
   dress, and three rules keep it honest.

   1. EVERY SELECTOR IS SCOPED under body.vp-page--careers, a
      class only public/join-us.html wears, so not one pixel here
      can reach /services, a city page or the journal. Suite 19
      holds those pages' photographs byte-exact and this is what
      lets it keep doing so.
   2. NOT ONE COLOUR IS NEW IN A RULE. The two rose tokens join
      the alias block at the top of this sheet, defined the way
      the palette already is; everything else is the tokens the
      site already owns. Every pair introduced here was measured
      with the WCAG 2.1 formula against the ground it sits on and
      the worst is quoted beside the rule that makes it.
   3. THE MARKUP IS TOUCHED AS LITTLE AS IT CAN BE. The server
      body is held byte-exact by the 9.37 oracle and the two
      renderers by 18.51/18.55. The banner of 2026-08-25 needed
      two elements the page did not have - the line under the
      heading and the rose line under that - and they are two
      paragraphs, added to both renderers together. EVERYTHING
      ELSE it wanted and the markup does not have is drawn as a
      pseudo-element: the hairlines either side of the rose
      line, the rules between the three columns, and the three
      icons above them.

   The designer's own controls stay meaningful on this page: the
   corner radius is set through --block-radius, which a block's
   Soft setting still overrides on its own section, and no rule
   here re-colours a border the Edge line control owns.
   ============================================================ */

/* The whole page softens its corners once, through the same
   custom property the Soft corner setting uses, so every plate,
   card and panel that already reads --block-radius rounds
   together and a block's own setting still wins by proximity. */
.vp-page--careers { --block-radius: 14px; }

/* THE BANNER'S TOP NOTE, NAMED ONCE. The heading's size is read
   in two places since 2026-08-25 - by the heading itself, and by
   the ceiling on the big line inside a column, which has to stay
   under it - so it is a token rather than the same clamp() typed
   out twice and left to drift apart. */
.vp-page--careers { --vp-hero-title: clamp(34px, 7vw, 104px); }
/* AND "HEADING SIZE: QUIETER" MOVES IT AGAIN, 2026-08-25. The
   designer offers Grand and Quieter on every hero and writes
   .vp-hero--quiet for the second, which the base sheet reads as
   `.vp-hero--quiet h1` - one class and one element, outranked
   here by `.vp-page--careers .vp-hero h1`, which is two classes
   and an element. So the control moved nothing on this banner
   and said nothing about it: exactly the fault the align
   control was caught in, in the same block, found by reading
   specificity rather than by watching. Restated at this scope
   it decides again, and because the size is a TOKEN rather than
   a font-size typed twice, the columns follow it down: the big
   line's ceiling is written against --vp-hero-title, so a
   quieter heading quietens the whole banner in proportion
   instead of leaving the figure shouting over a smaller
   headline. Same numbers as the base rule, so Quieter means the
   same thing here as it does everywhere else. */
.vp-page--careers .vp-hero--quiet { --vp-hero-title: clamp(30px, 4.2vw, 48px); }

/* ---- the hero: full bleed, warm dark, enormous space -------- */
/* The photograph is no longer named here: it arrives from
   hiring.heroBackgroundUrl as --vp-hero-ground on the section
   itself (both renderers write it, seo.js renderHeroBlock and
   pages.js blockHero), so Kate chooses the material of her own
   page from /manage and this rule only says how any photograph
   is worn. The shipped store names the velvet still from
   /uploads/glam - near black, lit amber at the folds, material
   rather than picture - and an emptied box paints nothing, so
   the hero stands on the plain canvas. Two scrims lie over
   whatever she names and both are built from the canvas triple
   site.css already owns: the left-to-right one puts the darkest
   ground under the words, the top-to-bottom one fades the
   photograph fully into the canvas so the page below joins it
   without a seam. Because the scrims are this sheet's and sit
   on top, no photograph can put the words on a bright ground.

   THE FIRST SCRIM TURNED ROUND ON 2026-08-25, and it had to.
   It ran left to right because the words ran down the left; the
   banner centres them, so the darkest ground has to be in the
   middle and a left-to-right sweep would have put the largest
   type on the amber folds. It is a radial now, weighted where
   the words are, and the amber survives at the corners where no
   word ever sits. Measured over the shipped velvet with the
   browser's own pixels, worst pixel inside the boxes the words
   occupy, at 390/820/1280/1920 - the figures are in the note
   over the type scale below. */
/* HER OWN BACKGROUND COLOUR IS THE BOTTOM LAYER, 2026-08-25.
   The designer's Colours group offers this block a background,
   which every other block wears through `.vp-tint { background:
   var(--vp-block-bg) }` - one class, outranked here by two, so
   picking a colour on the banner moved nothing at all. It is
   read as the LAST layer of the stack this rule already paints
   rather than as a rule of its own, because that is the layer
   the plain `var(--vp-canvas)` was: her colour is the ground the
   photograph and the two scrims sit on, which is what a ground
   is. With no colour picked the fallback is the canvas and this
   declaration computes to exactly what it computed to before. */
.vp-page--careers .vp-hero--careers {
  padding: clamp(64px, 8vw, 140px) 0 clamp(64px, 9vw, 132px);
  background:
    radial-gradient(120% 105% at 50% 40%, rgb(var(--canvas-rgb, 20 0 3) / 0.88) 0%, rgb(var(--canvas-rgb, 20 0 3) / 0.72) 44%, rgb(var(--canvas-rgb, 20 0 3) / 0.30) 100%),
    linear-gradient(180deg, rgb(var(--canvas-rgb, 20 0 3) / 0.50) 0%, rgb(var(--canvas-rgb, 20 0 3) / 0.30) 45%, var(--vp-canvas) 98%),
    var(--vp-hero-ground, none) center 40% / cover no-repeat,
    var(--vp-block-bg, var(--vp-canvas));
}

/* ---- the banner: centred, and centred by this sheet --------- */
/* The owner drew the top of this page as a banner and asked for
   it built: one enormous gold line, a quieter line under it, a
   rose line between two hairlines, three columns of what is on
   offer, and the way in beneath them. All of it centred, on the
   velvet it already stands on.

   CENTRED HERE RATHER THAN THROUGH THE DESIGNER'S OWN CONTROL,
   and the consequence is written down rather than left to be
   found: "Line the words up" no longer decides anything on the
   careers hero, because a banner that is only a banner when a
   setting happens to say Middle is a design held together by a
   preference. The control still decides it on the /services
   hero and on every block Kate adds, which is where 18.59 now
   measures it. The set that moves is DESIGNER-BLOCKS 2a's, to
   the element: the crumb LIST, the eyebrow, the places pill,
   the heading, the lede and the buttons. The crumb rows and
   links keep their left set here exactly as they do there.

   AND THE CONTROL NOW SAYS SO, 2026-08-25. Leaving a switch that
   quietly does nothing is worse than not offering one: Kate
   picks Left, watches the banner not move, and has no way to
   know whether the page or her own eyes are at fault. The panel
   prints a plain line under the control on this one block
   (public/designer.js, OURS.careersAlign) saying the banner is
   centred by design and this setting will not move it. It is
   printed for the hero on joinUsBlocks and nowhere else, so the
   /services hero still offers the control with nothing beside
   it, because there it decides.

   WHY IT SAYS SO RATHER THAN OBEYING, which was the better fix
   and is not available. Obeying means "centred unless she asks
   for Left", and Left is this system's DEFAULT: BLOCK_ENUM.align
   defaults to left and a block at its default writes no class at
   all, so nothing in the markup tells a stylesheet apart a Kate
   who chose Left from a Kate who has never touched the control.
   The two ways to make that difference visible both cost more
   than they buy - shipping the careers hero at align: middle
   changes the hiring-full and hiring-emptied server-body
   snapshots, which this round is not authorised to move, and
   reading the STORED setting rather than the effective one
   invents a second kind of "left" that no other block on the
   site has. So the control keeps its word by saying what it
   does, and the /services hero is where 18.59 measures it
   actually deciding.

   AND THE REST OF THE BLOCK'S CONTROLS WERE SWEPT ON PURPOSE
   AFTER THAT, 2026-08-25, because two of them had by then been
   caught doing nothing here by accident and two accidents is a
   pattern. Every setting the walker grants a hero, at every
   value that is not its default, and every colour slot it is
   granted, published through the real admin API and the banner
   photographed against itself. It found three more, all beaten
   on specificity by rules written at this deeper scope for this
   page: "Background: Panel" (while its own other half, Tint,
   reached the page and replaced the velvet), a heading colour
   she picks, and a background colour she picks. All three are
   obeyed now, each beside the rule that used to outrank it, and
   none of them changes a shipped byte because none of them
   writes a class or a property until she picks one. The sweep
   itself is now test 18.66, so the next control to lose an
   argument with this sheet is caught by a test rather than by
   somebody reading the cascade. */
.vp-page--careers .vp-hero--careers > .wrap { text-align: center; }
.vp-page--careers .vp-hero--careers .vp-crumbs ol,
.vp-page--careers .vp-hero--careers .eyebrow,
.vp-page--careers .vp-hero--careers .vp-hero__actions { justify-content: center; }
.vp-page--careers .vp-hero--careers .vp-crumbs { text-align: start; }
.vp-page--careers .vp-hero--careers .vp-hero__lede { margin-inline: auto; }

/* THE HEADLINE. Uppercase serif, tracked open, poured through a
   vertical gold gradient: pale champagne at the top edge of the
   letterforms falling to deep gold at the baseline. Three
   things keep that honest.

   1. A SOLID COLOUR IS PAINTED FIRST and the gradient is only
      reached inside @supports. Where clipping a background to
      text is not understood the heading is --vp-gold-soft and
      perfectly legible, rather than transparent letters on a
      photograph, which is what a bare background-clip would
      leave. -webkit-text-fill-color is set beside colour
      because WebKit reads that one in preference.
   2. NO NEW COLOUR, AND NO COLOUR FUNCTION EITHER. The three
      stops are --vp-gold-soft, --vp-gold and --vp-gold-deep,
      three tokens site.css has owned since the freeze. A
      color-mix() between two of them would have been prettier
      and would have failed dangerously: a custom property that
      cannot be substituted takes the whole background-image
      with it, and inside this @supports block the letters are
      already transparent, so an engine without color-mix would
      have shown an invisible heading rather than a plainer one.
   3. THE LAST STOP SITS AT 160%, WHICH IS NOT A TYPO. The
      gradient is clipped to the WHOLE heading, so a headline
      Kate types that runs to two or three lines puts its last
      line at the bottom of it - and if --vp-gold-deep landed at
      100% that line would be painted in a gold whose contrast
      ceiling against pure black is 4.4:1, under AA for normal
      text and thin even for large. Ending the run past the box
      means the deepest colour ever painted is about 40% of the
      way from --vp-gold to --vp-gold-deep, whatever the heading
      does, and the fall from champagne to bronze is still the
      whole of what the eye sees.

   MEASURED OVER THE SHIPPED VELVET, at 390/820/1280/1920, by
   two methods because gradient-filled text has no single ink to
   ask the browser about. Everything with a solid colour is the
   computed ink against the WORST composited ground pixel inside
   its box: crumbs 8.5 or better, eyebrow 7.1, the line under
   the heading 14.5, the rose vacancy line 7.4, the lede 6.9,
   the column leads 11.9, the big lines 11.9, the small lines
   under them 16.5, the rose call to action 9.1. The heading is
   measured on its own PIXELS - every glyph pixel of it against
   the ground pixel behind it - and the worst is 6.1:1 on the
   shipped one-line heading, 6.3:1 where a longer one is made to
   run to four or five lines, on a deepest painted gold of about
   #A98746. Every pair clears AA and all but three clear AAA. */
.vp-page--careers .vp-hero h1 {
  max-width: 22ch;
  margin-inline: auto;
  font-weight: 400;
  font-size: var(--vp-hero-title);
  line-height: 1.04;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  overflow-wrap: break-word;
  color: var(--vp-block-head, var(--vp-gold-soft));
}
/* AND A HEADING COLOUR SHE PICKS IS OBEYED, 2026-08-25. The
   designer grants this block a heading colour and writes it as
   --vp-block-head, which the rest of the sheet reads as
   `color: var(--vp-block-head, var(--vp-ink))` on every heading
   it draws. This one poured its letters through a gradient and
   painted them transparent, so her colour was substituted into
   a `color` nothing could see and the control did nothing at
   all. Threading it through all THREE stops is what obeys her
   without a second rule and without a class the renderers do
   not write: pick a colour and the three stops are that one
   colour, so the gradient flattens to a solid fill of the same
   letters; pick none and every stop falls back to the champagne
   -to-bronze run that ships. The contrast argument written above
   is about THAT run - a colour of her own is hers to answer for,
   and the designer's own readout is where it is measured. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .vp-page--careers .vp-hero h1 {
    background-image: linear-gradient(
      180deg,
      var(--vp-block-head, var(--vp-gold-soft)) 0%,
      var(--vp-block-head, var(--vp-gold)) 60%,
      var(--vp-block-head, var(--vp-gold-deep)) 160%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

/* The two lines under it, dressed up from their base rules: the
   subline centred and given room, the rose line held to a width
   so its two hairlines are rules rather than the whole page. */
/* THE MEASURES ARE IN em AND NOT ch, AND THAT IS A CLS FIX,
   2026-08-25. A ch is the width of a "0" IN THE FONT THAT IS
   ACTUALLY PAINTING - so while the fallback face is on screen a
   ch is one width, and the moment the real face arrives it is
   another, and the box these lines sit in RESIZES under them.
   The browser blamed exactly that: this paragraph's box went
   294px to 355px at 820 and the lede's 466px to 575px, which is
   most of the layout shift the banner added on a phone. An em is
   the type size, which is a number the sheet already decided, so
   it cannot move when a font arrives. The measures are unchanged
   to the pixel with the real face: 46ch measured 0.671em per
   character here and 54ch measured 0.666em on the lede, and the
   rendered widths after the swap are the same as before it. */
.vp-page--careers .vp-hero__subline {
  max-width: 30.9em;
  margin-inline: auto;
  color: var(--vp-ink);
  font-size: clamp(11.5px, 1.1vw, 15px);
}
.vp-page--careers .vp-hero__vacancy {
  max-width: 52rem;
  margin-inline: auto;
}

/* ---- the three columns: icons, hairlines, three weights ----- */
/* The cards are gone. They were three bordered boxes with a gold
   star each, which is right for a list of rewards and wrong for
   a banner: a banner divides, it does not enclose. So the boxes
   come off, the columns are separated by a single hairline each,
   and above each one is a line-art mark in rose.

   THE ICONS ARE MASKS, NOT MARKUP. Each is a stroked drawing
   carried in a mask-image, with the pseudo-element's own colour
   showing through it, so it inherits the rose the same way the
   text does and recolours with it - and no renderer has to learn
   to write SVG, which is the same reasoning that drew the star
   this replaces. They are decoration and carry no words: the
   words underneath say what each column is. Where masking is not
   understood the mark is not drawn at all, rather than leaving a
   rose block where a drawing should be.

   BY POSITION, AND ONLY THE FIRST THREE. Kate can reorder,
   rename or add columns; a fourth wears no mark rather than
   repeating one, because a mark that does not mean anything is
   worse than no mark. */
.vp-page--careers .vp-benefits {
  gap: 0;
  margin-top: clamp(44px, 5.5vw, 76px);
  /* WHAT CLIPS THE OUTER HAIRLINES, and the whole reason the
     dividers below can be written as one line. See the note on
     the columns themselves. */
  overflow: hidden;
}
/* AS MANY COLUMNS AS THERE ARE, 2026-08-25. It was repeat(3, 1fr)
   inherited from the base rule, which is right for the three the
   store ships and wrong the moment Kate adds a fourth: the fourth
   wrapped onto a second row and kept the divider on its left, so
   the page carried a hairline standing on its own with nothing
   beside it. auto-fit lays exactly as many tracks as there are
   columns, up to as many as will fit, so three columns still
   divide the width in three and four divide it in four. The
   150px floor is the narrowest a column may be squeezed to
   before the row wraps; min(100%, ...) is what stops that floor
   overflowing a phone, where the track has to be allowed to be
   narrower than 150px. */
@media (min-width: 760px) {
  .vp-page--careers .vp-benefits {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  }
}
/* THE DIVIDERS ARE SHADOWS AND THE LIST CLIPS THEM, and that is
   what makes any number of columns divide cleanly. A border-left
   on every column but the first is only right while there is one
   row: wrap to a second and the first column of THAT row keeps a
   left border with nothing on the other side of it, and no
   nth-child sum can find it once the number of tracks is decided
   by how many will fit rather than by a number typed in a rule.
   So each column paints a hairline one pixel OUTSIDE its own top
   and left edges, and the list hides what overflows it: the
   column at the left of any row draws its left hairline outside
   the list and it is clipped, the columns on the first row draw
   their top hairline outside the list and it is clipped, and
   every hairline that survives has a column on both sides of it.
   Three across, four across, or stacked on a phone, the rule is
   the same one and the outside edge is never ruled. */
.vp-page--careers .vp-benefits li {
  /* A column is a stack, so the small line under can be pushed
     to the foot of it and the big line can take the room left
     between the two. Since the big lines are no longer all the
     same size (see .vp-benefits__label), a fixed slot for them
     would fit exactly one column; a flexible one lets the three
     small lines above sit on one line at the top, the three
     small lines under sit on one line at the bottom, and each
     big line centre itself in whatever is left. */
  display: flex;
  flex-direction: column;
  /* Named rather than typed inline because the label's viewport
     fallback has to subtract it (see --vp-label-fill below), and
     two copies of one clamp is two things to retune and one of
     them will be missed. */
  --vp-benefits-pad: clamp(14px, 2vw, 30px);
  padding: clamp(24px, 2.8vw, 38px) var(--vp-benefits-pad);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: -1px 0 0 var(--vp-line), 0 -1px 0 var(--vp-line);
  text-align: center;
}
.vp-page--careers .vp-benefits li::before {
  content: "";
  display: block;
  width: clamp(34px, 3.4vw, 46px);
  height: clamp(34px, 3.4vw, 46px);
  margin: 0 auto clamp(16px, 1.8vw, 24px);
  background-color: var(--vp-rose);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  mask-size: contain;
}
/* A faceted gem: crown facets over a tapering pavilion. */
.vp-page--careers .vp-benefits li:nth-child(1)::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M14 10h20l8 10-18 20L6 20z'/%3E%3Cpath d='M6 20h36'/%3E%3Cpath d='M14 10l-8 10M34 10l8 10'/%3E%3Cpath d='M14 10l10 10 10-10M24 20v20'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M14 10h20l8 10-18 20L6 20z'/%3E%3Cpath d='M6 20h36'/%3E%3Cpath d='M14 10l-8 10M34 10l8 10'/%3E%3Cpath d='M14 10l10 10 10-10M24 20v20'/%3E%3C/svg%3E");
}
/* A card with a magnetic stripe and a short signature line. */
.vp-page--careers .vp-benefits li:nth-child(2)::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Crect x='5' y='12' width='38' height='26' rx='3.5'/%3E%3Cpath d='M5 20h38'/%3E%3Cpath d='M11 30h9'/%3E%3Crect x='30' y='27.5' width='8' height='5.5' rx='1.2'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Crect x='5' y='12' width='38' height='26' rx='3.5'/%3E%3Cpath d='M5 20h38'/%3E%3Cpath d='M11 30h9'/%3E%3Crect x='30' y='27.5' width='8' height='5.5' rx='1.2'/%3E%3C/svg%3E");
}
/* A clock, hands at ten past ten so the face reads open. */
.vp-page--careers .vp-benefits li:nth-child(3)::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Ccircle cx='24' cy='24' r='17'/%3E%3Cpath d='M24 13v11l7.5 4.5'/%3E%3Cpath d='M24 7v3M24 38v3M41 24h-3M10 24H7'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Ccircle cx='24' cy='24' r='17'/%3E%3Cpath d='M24 13v11l7.5 4.5'/%3E%3Cpath d='M24 7v3M24 38v3M41 24h-3M10 24H7'/%3E%3C/svg%3E");
}
/* A FOURTH COLUMN KEEPS THE SPACE AND LOSES THE DRAWING, and
   the difference between those two matters, 2026-08-25. The
   owner drew three marks and there is no fourth to draw; this
   used to be display:none, which took the box away as well as
   the picture, and a column is a flex stack - so a column with
   no mark started 66px higher than the three beside it and the
   whole row went out of register. Reserving the space keeps the
   small line above, the big line and the small line under on
   the same three baselines across every column, whatever Kate
   adds. Inventing a mark for it was the alternative and it is
   worse: reusing the gem or the clock on a fourth column would
   have the page say something about her words that nobody drew.
   Hidden rather than transparent because the mark is painted as
   a colour through a mask, and a pseudo-element with no mask
   image paints the whole square. */
.vp-page--careers .vp-benefits li:nth-child(n+4)::before { visibility: hidden; }
@supports not ((mask-image: url("#i")) or (-webkit-mask-image: url("#i"))) {
  .vp-page--careers .vp-benefits li::before { display: none; }
}

/* The three weights inside a column: a small line above, one
   loud line, a small line under. The loud line is the display
   face at heading size, which is what makes a figure read as a
   figure; the two small ones are tracked capitals, the lead in
   gold and the line under it in the page ink, so the column
   reads top to bottom as one sentence in three voices. */
.vp-page--careers .vp-benefits__lead {
  margin-bottom: clamp(8px, 1vw, 14px);
  font-size: clamp(10px, 0.85vw, 12px);
  letter-spacing: 0.24em;
}
/* THE BIG LINE IS SIZED FROM ITS OWN LENGTH AND CAPPED AGAINST
   THE HEADING, 2026-08-25, and this is the rule the whole
   banner turns on.

   THE FAULT IT FIXES. Until this rule all three big lines were
   set at one size, so the amount of the page each one covered
   came down to how many characters were in it - and the
   shortest string, which is the FIGURE, covered the least.
   Measured at 1920: "Every appointment" laid down 22,412 square
   pixels of type against the figure's 8,473. The banner the
   owner drew has the figure unmistakably second only to the
   heading; the page had it fifth. The hierarchy was exactly the
   wrong way up.

   AND THE FAULT THE FIRST VERSION OF THIS RULE LEFT BEHIND,
   found and fixed the same day. That version gave a column
   "8.6 heading-sizes" of room and set the ceiling at a fifth of
   it, so a line of five characters or fewer could set at 1.72
   heading-sizes - and with fewer than three columns nothing
   else was binding, so it did. Driven on the real page through
   the real admin API:

     headline "Jobs", the shipped three columns, per-line ink
       390   figure 9,415  against the heading's 3,915   (2.4:1)
       820   figure 11,945 against 10,963
     one column left, the shipped headline
       1280  the figure set 128.4px against an 89.6px heading
       1920  149.1px against 104px
     one column, a ten-character headline: the figure out-inked
       the heading at 390, 1280 and 1920.

   The 8.6 was the SHIPPED HEADLINE'S OWN LENGTH in disguise -
   thirteen characters at about 0.66em each - so the cap held
   for a headline of about that length and for no other, and the
   note that used to stand here claimed it held always. Kate can
   retype the headline and delete rows from /manage, so both of
   those are states she can reach.

   WHAT DOES THE PICKING. Two counts, and both are handed over
   by the renderers because CSS cannot work either of them out:
   --vp-label-len is this column's own big line and rides on the
   li, --vp-head-len is the heading's and rides on the list
   (seo.js renderHeroBlock and pages.js blockHero, twinned, with
   the reasoning written out there). NO ROW IS NAMED AND NO
   STRING IS NAMED. Kate can retype every one of them, reorder
   them, add a fourth, shorten the headline to one word, and the
   rule holds because it never knew what any of them said.

   THE ONE THING IT LEANS ON is that --vp-label-len is never
   zero, because dividing by zero would throw the whole
   declaration away. It cannot be: migrateLabelDetailList() in
   content.js drops a row whose big line is empty before either
   renderer ever sees it (`if (!label ...) return;`), which is
   the same rule the /manage warning tells Kate about in words.
   The 12 in the var() fallback is for a column drawn with no
   property at all - a cached renderer from before today - and
   puts such a column at a middling size rather than at none.
   --vp-head-len is allowed to be zero and nothing divides by
   it, so an emptied headline simply makes the room zero and
   every big line falls to its floor.

   THE SIZE ITSELF is the room a column has divided by the
   number of characters that must fit in it. That is a
   designer's own arithmetic - set it as large as the measure
   allows - and it is why a short line comes out large and a
   long one steps down.

   WHY DIVISION AND NOT A LADDER OF SIZES. Because the ink a
   line lays down goes as length x size squared: step the size
   down in fixed amounts and a line half again as long still
   ends up louder than the figure, which is the fault all over
   again in a politer form. Dividing by the length makes the ink
   fall as one over the length, so a longer line is ALWAYS
   quieter.

   THE ROOM IS THE COLUMN'S OWN MEASURE OR ONE LINE OF THE
   HEADING, WHICHEVER IS SMALLER. 155cqw is 155% of the column's
   own content width, so the arithmetic follows the column when
   Kate adds a fourth and the tracks narrow, and it needs no
   breakpoint of its own; the 155 is 97% of 1/0.6283, and
   0.6283em is this face's average character advance measured
   off the rendered page, so a line of len characters comes out
   about 97% of the way across the column's text measure with
   the column's own padding still standing between it and the
   divider. The heading's line is --vp-head-measure above.

   THAT SECOND TERM IS WHAT HOLDS THE HIERARCHY ON A PHONE,
   where the column is the whole page and the heading sits at
   the 34px floor of its clamp. Driven with it taken out of the
   declaration in the browser, at 390: "Working hours" goes from
   22.44px to 34px and from 4,420 square pixels per line to
   9,928, "Every appointment" from 20px to 29.36px and 4,511 to
   9,427, and both of them are then LOUDER than the figure's
   8,562 - the fault the round began with, arriving from the
   other direction. Only 390 moves, because at the wider three
   the column's own measure is the smaller of the two anyway. It
   also means a SHORT headline makes a QUIET banner, which is
   the rule working rather than failing: everything under the
   heading is sized against the heading. Photographed at 390
   with the headline "Jobs" and it reads as a quiet banner, not
   a broken one.

   Where container units are not understood the same division
   runs against a measure guessed from the viewport instead, in
   two regimes because the column is the whole width when the
   list is stacked and a share of it when it is not; the heading
   term is the same one in both branches. THAT BRANCH WAS
   DRIVEN and not left to be believed: the two fallback
   declarations re-stated over the page in the browser so they
   win, which is the same arithmetic an engine without container
   units would run. The figure comes out 46.53/50.00/76.80/
   80.00px at 390/820/1280/1920 against this branch's
   46.53/55.08/82.53/80.25 - the same order and the same
   proportions, a slightly quieter banner at 820 and 1280 and
   the same one at the other two.

   THE CEILING, AND THE THIRD TERM IN IT IS WHAT MAKES THE
   GUARANTEE. A fifth of the room is what a five-character line
   would take, so no line can be blown up past the width of its
   own column. The floor is 20px, below which a display line
   stops being one. Between them sits the term this round added:
   the size at which this line would lay down THE SAME INK AS A
   LINE OF THE HEADING, and never more.

   THE ARITHMETIC, because the shape of it is not obvious. Ink
   goes as length x size squared, so the two are equal when
   size = heading-size x sqrt(head-line / this-line). CSS has no
   square root. But the harmonic mean of 1 and a ratio is never
   larger than its square root, and the harmonic mean of 1 and
   L/len is exactly 2L / (L + len) - twice the heading's count
   over the two counts added - which is arithmetic CSS can do.
   So the ceiling is the heading's size times 2L/(L+len). It is
   EXACT where the two lines are the same length, which is the
   case a cap has to be tight in, and conservative everywhere
   else, which is the direction to be wrong in. No safety factor
   is multiplied on top, and the reason is that the heading has
   one already: it carries 0.06em more tracking than a big line
   does, and the two are set in the same face at the same line
   -box ratio (measured 1.235 to 1.240em on the heading and 1.20
   to 1.26em on the big lines, which is the same number and the
   rounding of a rect height at small sizes). So at equal size
   and equal length the heading is ahead on the page before this
   term is applied at all. Over the matrix the test walks the
   figure reaches 0.727 of a heading line - but that matrix uses
   the three big lines this store ships, 6, 17 and 13 characters,
   so it never visits the ratio where this bound is tightest,
   which is a big line about half the length of the headline.
   Driven there on purpose (headline "Come Join!" at ten
   characters, big line "£1000" at five) it reaches 0.795 to
   0.800 across the four widths, and the arithmetic maximum of
   the rule is 0.813. Still a comfortable margin, which is why
   the rule stands; "anywhere" was the wrong word for 0.727.

   MEASURED, THREE INSTRUMENTS, and the claim is made under the
   first two read PER LINE:

   1. the area of every line box of rendered type, the
      instrument that caught the original fault;
   2. the count of literally PAINTED GLYPH PIXELS - the banner
      photographed twice, once as it is and once with the words
      hidden, and the pixels that differ counted;
   3. either of those totalled over ALL the lines of an element
      instead of per line.

   Per line, instrument 1 puts the figure second only to the
   heading at all four widths. Instrument 2 agrees at three of
   them and NOT at 820, where Kate's lede sets a widest line of
   3,567 painted pixels against the figure's 2,820 - a
   comparison the table below never made, because it tabulates
   the heading and the figure and not the lede. The figures
   below are therefore the heading against the figure, which is
   the pair the rule governs, and not a ranking of the page:

     width   h1 line-box   FIGURE    painted h1   painted FIGURE
     390       12,446       8,562       2,764        2,082
     820       34,705      11,945       6,625        2,772
     1280      78,089      24,915      12,142        5,058
     1920     104,579      23,535      15,651        4,897

   and ahead of both phrases: "Working hours" 4,420/5,527/
   12,396/11,549 and "Every appointment" 4,511/4,511/9,095/
   8,853 by line box.

   TOTALLED, INSTRUMENT 3 DISAGREES, AND BOTH READINGS OF IT ARE
   WRITTEN DOWN HERE RATHER THAN THE CONVENIENT ONE. Kate's lede
   is three lines of body copy at 390 and two at 820: totalled
   by line-box area the figure is second at 1280 and 1920 and
   THIRD at 390 and 820, behind that paragraph; totalled by
   painted pixels it is THIRD AT ALL FOUR WIDTHS (lede 4,873 /
   4,898 / 5,434 / 6,210 against the figure's 2,082 / 2,772 /
   5,058 / 4,897), and at 390 the lede is ahead of the heading
   as well. What totalling over a paragraph measures is how LONG
   it is, not how loud, which is why the tables above are per
   line - but the sentence "second only to the h1" is true under
   two instruments of three and it is not true under the third,
   and that is the honest way to say it.

   THE SHIPPED BANNER BARELY MOVED FOR ANY OF THIS. The only
   width where the new ceiling binds at the shipped content is
   390, where the figure goes from 48.73px to 46.53px. 820,
   1280 and 1920 are unchanged to the hundredth of a pixel.

   AND THE STATES THAT USED TO INVERT DO NOT. Same instrument,
   same page, after:

     headline "Jobs", three columns
       390   figure 1,646 against the heading's 3,915
       820   figure 2,569 against 10,963
       1280  figure 6,271 against 25,063
       1920  figure 8,429 against 33,615
     one column, the shipped headline
       390   8,562 against 12,446      1280  53,062 against 78,089
       820   22,989 against 34,705     1920  71,266 against 104,579
     one column, a ten-character headline
       390   5,716 against 9,281       1280  34,419 against 58,082
       820   15,506 against 25,865     1920  46,279 against 77,773

   Driven across the whole matrix the widened test now walks -
   headlines of 4, 13 and 40 characters against one, two, three
   and four columns, at 390, 820, 1280 and 1920, 48 states - the
   worst the figure reaches is 0.727 of a heading line, and no
   big line the rule actually sized reaches the heading at any
   of them. That is the worst over THIS matrix, not the worst
   the rule allows: see the note above for the tightest ratio,
   0.813, and boundary 2 below for the one state where the
   guarantee does not hold at all.

   AND IT STILL READS AT THE TOP OF THE RANGE. Growing a line
   does not change its colour, but it does move it over more of
   the photograph, so it was measured there: with every big line
   forced to the ceiling and the headline long enough to lift
   that ceiling as far as it goes, the worst pixel IN THE CORE
   OF A STROKE, against the composited velvet directly behind
   it, is 7.15:1 to 7.26:1 at 390/820/1280/1920. Clear of AA at
   4.5 and of AAA at 7.

   AND THE METHOD IS DESCRIBED AS IT WAS RUN, which the note
   that stood here was not. "The core of a stroke" means every
   pixel the words changed by at least 75% of the largest change
   anywhere in that box. It is NOT every anti-aliased pixel: the
   same measurement taken half way out to the edge of the glyphs
   gives 3.65:1 to 3.81:1, a quarter of the way gives 1.71:1 to
   1.74:1, and the very faintest pixel the words touched at all
   gives 1.07:1. Those three are measurements of the
   anti-aliasing rather than of the type - every renderer blends
   the edge of a glyph into whatever is behind it, and no
   contrast standard measures the blend - but the earlier note
   reported the 75% number and called it the strict method that
   counts anti-aliased edges as ink, and that was two different
   methods in one sentence.

   WHERE THE GUARANTEE STOPS, and both boundaries are the rule's
   own and are written down rather than left to be found.

   1. THE 20px FLOOR OUTRANKS THE CEILING, deliberately. When
      the heading is short enough that the ceiling falls under
      20px the floor wins, because a line nobody can read is a
      worse failure than a line that is louder than a four
      -character heading. WALKED AT 390 with the three lines
      this store ships, one character of headline at a time:
        1 character   all three big lines out-ink the heading
        2 and 3       the two phrases do, the figure does not
        4             "Every appointment" alone does
        5 and up      every big line is under the heading
      FIVE IS WHERE IT STOPS FOR THE LINES THIS STORE SHIPS,
      and the length of the longest big line is what moves it:
      at the floor a seventeen-character line lays down 4,511
      square pixels a line, and a heading needs about five
      characters at 34px to beat that. A LONGER BIG LINE PUSHES
      THE CROSSOVER OUT, and Kate's live store already holds a
      31-character one ("Payment after every appointment").
      Walked against that line at 390: a five-character heading
      is out-inked 1.472:1, six 1.334:1, eight 1.176:1, and only
      at ten does it fall under, at 0.850. So the honest
      statement is that above the floor the ceiling decides, and
      the floor's exemption runs to roughly a third of the
      longest big line's length - not to a fixed five.

      AND SINCE 2026-08-26 THE SHIPPED STORE SITS ON THIS
      BOUNDARY AT 1280, which is why it is written here and not
      left as a footnote about contrived content. The plate
      beside the banner takes the words' column from 1112 to
      804 at that width, so the fill a column offers falls with
      it, and "Every appointment" - seventeen characters - comes
      out at 19.75px and is held at 20 by the floor. It reaches
      the floor by the fill and not by the ceiling, which is
      67.2px there and nowhere near it, but the size on the page
      is the floor's either way. So this exemption is no longer
      a statement about four-character headlines: it is where
      Kate's own live page stands at the commonest desktop
      width. THE GUARANTEE ITSELF IS NOT IN DANGER THERE and
      that was measured rather than assumed - at 1280 that line
      lays down 4,511 square pixels against a heading line's
      78,089, which is 0.058 of it. What has changed is which
      rule is holding it: the floor, not the ceiling.
   2. THE COUNT IS OF CHARACTERS, NOT OF GLYPH WIDTHS.
      String.length is the count both renderers agree on for
      nothing, and 18.51 compares their bytes, so a cleverer
      count would have to be identically clever in two
      languages. A headline of narrow letters against a big line
      of wide ones is therefore a state where THE GUARANTEE
      SIMPLY DOES NOT HOLD - not a footnote about precision.
      Driven: the headline "Illlilliil" (ten narrow characters)
      against the big line "WWWWW" (five wide) sets the big line
      at 1.51 to 1.53 of a heading line at 390, 1280 and 1920,
      with the label at 44.9 to 137.3px, nowhere near the 20px
      floor. It is contrived content and no real headline of
      Kate's behaves like it, but the claims above are written
      for characters and this is what characters cannot see.
      Measured spread of the per-character advance on this page:
      0.53em to 0.76em. Counting rendered widths instead would
      have to be identically clever in two renderers whose bytes
      18.51 compares, which is the price that bought this.

   Past about thirty-five characters on a phone a big line
   covers as much of the page as the figure simply by being that
   long; that is the same floor, seen from the other end. */
.vp-page--careers .vp-benefits__label {
  /* Centred in the room the column has left between its two
     small lines - see the note on the column itself. */
  margin-block: auto;
  /* HOW MANY CHARACTERS OF THE HEADING LAND ON ONE LINE, which
     is what the cap below has to compare against - not how many
     Kate typed. A long headline wraps, and the cap has to be
     against the ink of ONE LINE of it or it would let the
     columns grow by the length of a paragraph. Driven with a
     forty-character headline: it wraps to three lines at every
     one of the four widths, and the WIDEST of those lines
     measures 14.4 characters at 390, 19.0 at 820 and at 1280,
     and 16.4 at 1920. Fourteen is therefore at or under the
     truth at every width, which is the direction an estimate
     inside a cap has to be wrong in, and it is above the
     shipped thirteen so the shipped banner is untouched by it.
     The heading's own max-width of 22ch is the ceiling on this
     whatever the viewport does. */
  --vp-head-line: min(var(--vp-head-len, 13), 14);
  /* One line of the heading, in pixels: its size times the
     characters on it times what one character of it costs. The
     0.66 is that cost in ems, tracking included, measured off
     the rendered page (0.67 at 390, 0.66 at 820, 0.60 at 1280
     and 1920 for the shipped headline). AT THE SHIPPED THIRTEEN
     CHARACTERS THIS IS 8.58 HEADING-SIZES, which is where the
     8.6 that used to be typed here came from: the number was
     the shipped headline's own length in disguise, and the note
     beside it claimed to keep the figure under the heading when
     it only did so for a headline of about that length. */
  --vp-head-measure: calc(var(--vp-hero-title) * var(--vp-head-line) * 0.66);
  /* THE CEILING, AND THE HEADING TERM IS THE ONE THAT MATTERS.
     A fifth of the measure is what a five-character line would
     take, so no line can be blown up past the width of its own
     column. The second term is the size at which this line
     would lay down the same ink as a LINE OF THE HEADING, and
     the reasoning is over the rule below. */
  --vp-label-ceiling: min(
    calc(var(--vp-label-fill) / 5),
    calc(var(--vp-hero-title) * 2 * var(--vp-head-line)
      / (var(--vp-head-line) + var(--vp-label-len, 12))));
  font-size: clamp(20px, calc(var(--vp-label-fill) / var(--vp-label-len, 12)), var(--vp-label-ceiling));
  line-height: 1.06;
  letter-spacing: 0.005em;
  color: var(--vp-gold-soft);
  text-wrap: balance;
  /* A single word longer than the column breaks rather than
     running out over the dividers. */
  overflow-wrap: break-word;
}
/* The measure guessed from the viewport, for an engine with no
   container units: the whole width less the page and column
   padding when the list is stacked, a third of it when it is
   not - and never more than one line of the heading, for the
   reason written over the rule above. */
.vp-page--careers .vp-benefits__label {
  --vp-label-fill: min(clamp(292px, calc(30vw + 175px), 460px), var(--vp-head-measure));
}
@media (min-width: 760px) {
  .vp-page--careers .vp-benefits__label {
    --vp-label-fill: min(clamp(300px, 36vw, 480px), var(--vp-head-measure));
  }
}
/* AND THE SAME GUESS AGAIN FOR THE SPLIT BANNER, 2026-08-26,
   because from 1240px up the columns no longer have the width
   the two rules above think they do.

   THE FAULT THEY HAD. Both guesses read the SCREEN. That was
   near enough while the words held the whole page, and it stopped
   being true the day the plate took a column out of the banner:
   at 1280 the words' column fell from 1112 to 804 and 36vw did
   not move, so the fallback offered 460.8px of fill into a column
   with 216.66px in it and the figure set at 76.8px and broke mid
   -number, rendering "1,00" over "0". Chrome has container units
   so the gate never walks this path, which is exactly why it has
   to be written correctly rather than watched.

   IT IS NOT A GUESS ANY MORE, IT IS THE ARITHMETIC. Every term
   the layout used is a custom property that inherits down to this
   label, so the column can be rebuilt here rather than estimated:
   the banner is the screen less its two gutters or the page's own
   column plus the plate and the gap, whichever is smaller; the
   words are that less the plate and the gap; a column is a third
   of the words less the two paddings it wears; and 1.55 is the
   same 155cqw the container rule below asks for. At three columns
   this lands on the container-query value to the hundredth of a
   pixel at 1240, 1280, 1366, 1440, 1920 and 2560.

   THE TWO THINGS IT STILL CANNOT SEE, both of which it is wrong
   in the SAFE direction about, and neither of which is new here.
   A fourth column makes every column narrower and this still
   divides by three, which over-estimates - that was true of 36vw
   before this round and is no worse now. And "Edge to edge" drops
   the page's own measure, which var(--wrap) cannot know, so above
   about 1600px this under-estimates the column and the figure
   sets quieter than it could. Quieter is the direction a fallback
   should be wrong in; wider is the one that breaks a number in
   half. */
@media (min-width: 1240px) {
  .vp-page--careers .vp-hero__grid--split .vp-benefits__label {
    --vp-label-fill: min(
      calc(1.55 * (min(
          100vw - 2 * var(--gutter) - var(--vp-plate) - var(--vp-plate-gap),
          min(100vw, var(--wrap)) - 2 * var(--gutter)) / 3
        - 2 * var(--vp-benefits-pad))),
      var(--vp-head-measure));
  }
}
/* The measure the column ACTUALLY has, which is the one this
   wants. It is set on the label and not on the column because a
   container's own units resolve against its container, never
   against itself.

   IT NAMES THE COLUMN IT SITS IN so that it outranks the three
   viewport fallbacks above whatever they are scoped to. The split
   one has to be able to say .vp-hero__grid--split, which is a
   third class, and a fallback that beats the real measurement on
   specificity is the fallback winning on an engine that never
   needed it. The li here is the container itself, named one line
   up, so this is not a weight bolted on: it is the rule saying
   where it applies. */
@supports (container-type: inline-size) {
  .vp-page--careers .vp-benefits li { container-type: inline-size; }
  .vp-page--careers .vp-benefits li .vp-benefits__label {
    --vp-label-fill: min(155cqw, var(--vp-head-measure));
  }
}
.vp-page--careers .vp-benefits__detail {
  margin-top: clamp(10px, 1.2vw, 16px);
  color: var(--vp-ink);
  font-size: clamp(10px, 0.85vw, 12px);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* The way in, centred under the columns, with the space a
   banner's button needs above it. */
.vp-page--careers .vp-hero__actions { margin-top: clamp(36px, 4.5vw, 60px); }
/* In em rather than ch for the reason written over the subline
   above: a ch moves when the web font arrives and takes the
   paragraph's whole box with it. */
.vp-page--careers .vp-hero__lede {
  max-width: 36em;
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.75;
}

/* ---- the poster: under the banner, or beside it ------------ */
/* It used to stand beside the heading; the banner centres the
   words, so on a screen with no room to spare it stands under
   them. Held to a column of its own width rather than run
   across the page: a portrait stretched to 1240px is a wall,
   and this is a photograph of a person. It keeps the lift every
   lifted thing here has, and the crop window stays biased
   toward the upper third for the reason it always was - faces
   live there, and a centred crop of a portrait starts below the
   chin. Portrait for as long as it is UNDER the words, which is
   every width that puts it there: a plate under the words has
   all the room it needs vertically and none of the reasons the
   side-by-side version had to go landscape on a phone. */
.vp-page--careers .vp-hero__poster {
  width: min(100%, clamp(240px, 26vw, 392px));
  margin-inline: auto;
  aspect-ratio: 4 / 5;
  box-shadow: var(--lift-shadow, 0 24px 50px -24px rgb(var(--canvas-rgb, 20 0 3) / 0.85));
}
.vp-page--careers .vp-hero__poster img { object-position: center 30%; }
/* AND IT STANDS BESIDE THE BANNER FROM 1240px UP, 2026-08-26,
   because Sam asked for the portrait next to the hero rather
   than under it.

   THE FAULT. Yesterday's rule right-aligned the plate at the
   foot of the banner from 900px up, which put it on a side of
   the page with nothing on the other side of it: measured
   inside the page's own column, about 780 by 420 of empty
   velvet at 1280 and about 720 by 490 at 1920. The banner was
   also 1436 tall at 1280 and 1610 at 1920 against viewports of
   900 and 1080, so the plate was pushing the way in off the
   fold in order to stand in a hole.

   THE TENSION, AND IT IS REAL. A right-hand column narrows the
   left one, and the left one carries BOTH the headline and the
   three columns whose big lines are sized from their own
   column's width (the note over .vp-benefits__label is the rule
   this must not break). Neither was argued about: the plate was
   put in a column of the width it already had, the wrap left
   alone, and the banner photographed and measured.

     at 1280  the headline still fitted, the figure fell from
              82.5px to 49.5px and "Every appointment" sat on
              the sheet's 20px reading floor
     at 1920  the headline WRAPPED to two lines, the figure fell
              to 41.7px, and two of the small lines wrapped too

   That is the whole banner paying for the photograph, and the
   figure standing second only to the heading took three rounds
   of verification to get right. So the room comes from
   somewhere the words do not own.

   WHERE THE ROOM COMES FROM: THE PAGE'S OWN MARGINS. The page
   is at most --wrap wide and the screen is not, so every pixel
   of the difference is margin the page is not using. The banner
   grows by exactly what the plate costs and no further:

     measure = min(the screen less its two gutters,
                   the page's own column + the plate + the gap)

   The first term is all there is; the second is all that is
   wanted. Whichever is smaller wins, so the margins pay for the
   plate as far as they go and only the shortfall comes out of
   the words.

   1240 IS NOT A NUMBER INVENTED HERE. It is --wrap: the width
   at which the page stops filling the screen and starts leaving
   margins, which is the width at which there is anything to
   spend. At exactly 1240 the two terms above are equal, so the
   second column opens at the precise width where the first
   pixel of margin exists.

   WHAT DOES NOT JUMP THERE IS THE MEASURE, AND ONLY THE
   MEASURE, and the note that stood here said "the widening
   starts from nothing" as though that were true of the whole
   banner. It is not. Measured at 1239 and at 1240: the banner
   goes 1115.13 to 1116, which is the pixel of margin arriving
   and is what "starts from nothing" is about - and across the
   same pixel the words' column goes 1115.13 to 768.81, the
   figure 83.23px to 53.39px, "Every appointment" 29.37px to the
   20px reading floor, "Working hours" 38.41px to 24.64px and
   the banner 1398 to 915 tall. A second column either exists or
   it does not, so the words give up its width in one step
   whatever this rule opens at. What the arithmetic buys is that
   the banner does not leap out of the page's margins in the
   same step, which is the movement somebody dragging a window
   would actually see. Below 1240 the plate stays under the
   words, centred, which is the only honest thing a screen with
   no margins can do with it.

   THE PLATE IS A QUARTER OF THE PAGE'S OWN COLUMN, not a share
   of the screen, and that is a decision rather than a taste.
   The thing it stands beside is the page's column of words, so
   it is proportioned to that and comes out the same size on a
   1280 laptop and a 1920 monitor, which is what a photograph of
   one person should do. It also keeps the designer's Width
   control alive on this block: --wrap is what "Wide" moves, so
   a wider page really does get a wider plate. Written as a
   clamp so a retune of --wrap at either extreme cannot make the
   plate a stamp or a wall.

   WHAT IT COSTS AND WHAT IT DOES NOT, measured with the ink
   instrument 18.63 uses, per line, on the words the store
   ships:

     width   heading    figure before   figure after
     390      12,446     8,562 @46.5     8,562 @46.5  unchanged
     820      34,705    11,945 @55.1    11,945 @55.1  unchanged
     1280     78,089    24,915 @82.5    12,299 @56.0
     1920    104,579    23,535 @80.3    23,535 @80.3  unchanged

   At 1920 the margins cover the plate whole, so the words keep
   the page's own 1112px column and not one letter of the banner
   moves. At 390 and 820 nothing matches this rule at all. 1280
   is where the margins are 40px against a plate of 310 and the
   columns give up the rest: the figure goes from 82.5px to
   56.0px, which is the price of the composition, and it is
   still 2.1 times the ink of the loudest line beside it and
   0.16 of a line of the heading. The banner's height goes
   1436 to 940 at 1280 and 1610 to 1064 at 1920.

   AT 1920 EVERYTHING IN THE BANNER IS ABOVE THE FOLD AND THE
   BANNER ITSELF IS NOT, and the note that stood here said the
   whole of it stood inside the first screenful, which was 107px
   short of true. Measured: the section opens at y=123 and is
   1064 tall, so it ends at 1187 against a 1080 screen. The
   heading, the columns, the button and the plate all finish at
   1054 and what falls past the fold is the space under them.
   That is the claim worth making and it is the one made here:
   a visitor at 1920 sees the whole composition without
   scrolling, not the whole section.

   THE PLATE FILLS THE HEIGHT OF THE WORDS BESIDE IT rather than
   keeping its 4/5 frame, and that is the difference between
   moving the hole and closing it. Photographed both ways: a 4/5
   plate centred in its column would leave over 400px of velvet
   above and below it at 1920, which is the original fault at
   half the size. A plate as tall as the words has nothing left
   over. What the crop then is depends on how much Kate has
   typed, and it was driven at both ends. Stripped to a headline
   and one column it is 0.52 of a square at 1280 and 0.48 at
   1920; loaded with a forty-character headline, a long lede and
   four columns it is 0.28 and 0.26. The subject stays in frame
   at all four, because the crop window is already biased to
   where faces are.

   AN EMPTIED BOX IS UNTOUCHED BY EVERY LINE OF THIS. Both
   renderers write --split only when there IS a photograph, so a
   store with none never matches this rule: the banner keeps the
   page's own column, centred, with no column-shaped hole where
   a plate would have been. Proven rather than claimed - the
   emptied banner photographs byte-identical to the day before
   at 390, 820, 1280 and 1920.

   AND THE HERO CLIPS ITS OWN OVERFLOW, which is what makes the
   negative margins safe to write: .vp-hero carries overflow:
   hidden, so even on an engine where 100vw counts a scrollbar
   the page cannot grow a horizontal one. The gutters here are
   64px at these widths and a classic scrollbar is about 15.

   WHAT THIS COSTS ELSEWHERE, WRITTEN DOWN RATHER THAN LEFT TO
   BE FOUND. Both were put to Sam on 2026-08-26 as open trades
   and both were answered; docs/DECISIONS.md D24 is the record.

   1. 18.59 MEASURED CENTRING AGAINST THE WRAP, and the words
      are centred in the COLUMN the banner gives them, which
      from 1240 up is no longer the whole wrap. At 1280 the
      heading read 165px from the wrap's left edge and 514px
      from its right, and it is dead centre of its own column:
      the difference is exactly the plate and its gap. The
      banner is still centred; the box it was measured against
      moved. The careers half of that test is anchored on
      .vp-hero__lead now, and it gained the two assertions that
      were previously implicit - that the grid itself is centred
      inside the hero, and that every reading is numerically
      identical under Middle and under Left.
   2. "WIDTH: EDGE TO EDGE" DECIDES NOTHING ON THIS BANNER
      BELOW ABOUT 1600px, and it is PINNED rather than fixed.
      With a photograph beside it the banner already reaches the
      screen at 1280, so Edge has nothing left to give; it
      decides again where the margins pay, and at 1920 it is
      1792 against normal's 1474. The alternative was to open
      this rule higher up instead of at 1240 - at about 1600,
      which is where the margins cover the plate whole and Edge
      starts deciding again, and not the 1550 first proposed -
      which would have given Edge its pixels back at the price
      of putting the photograph under the words again at 1280,
      1366 and 1440,
      the commonest desktop widths, and the whole of what was
      asked for. So the designer prints a note under the Width
      control on this block, exactly as it already does for the
      align setting and for the background colour, and 18.66
      asserts the pinned behaviour instead of the impossible
      one: Edge moves nothing here at 1280, moves the banner at
      1920, and "Wide" moves it at both, which it does only
      because the plate is a share of --wrap. */
@media (min-width: 1240px) {
  .vp-page--careers .vp-hero__grid--split {
    --vp-plate: clamp(240px, calc(var(--wrap) / 4), 392px);
    --vp-plate-gap: clamp(28px, 3vw, 52px);
    --vp-banner-measure: min(
      100vw - 2 * var(--gutter),
      100% + var(--vp-plate) + var(--vp-plate-gap));
    /* Centred on the banner as a whole, words and plate together,
       so the composition is centred even though the words alone
       are not. Negative margins are what let a child of the wrap
       be wider than the wrap; there is nowhere else to take the
       margins from. */
    width: var(--vp-banner-measure);
    margin-inline: calc((100% - var(--vp-banner-measure)) / 2);
    grid-template-columns: minmax(0, 1fr) var(--vp-plate);
    column-gap: var(--vp-plate-gap);
  }
  .vp-page--careers .vp-hero__grid--split > .vp-hero__poster {
    /* The track decides the width now, so the cap that held the
       plate to a column of its own goes; and the height comes
       from the words rather than from a ratio, which is what
       leaves no velvet above or below it. */
    width: 100%;
    margin-inline: 0;
    align-self: stretch;
    aspect-ratio: auto;
  }
  /* AND THE PHOTOGRAPH IS TAKEN OUT OF THE HEIGHT ARITHMETIC,
     2026-08-26, which is the cap that makes the four lines above
     safe to write.

     WITHOUT IT THE PLATE HAS NO CEILING AT ALL. An image lays
     itself out at its own shape in a 310px track, so an upload
     tall enough is taller than the words, and the grid row then
     takes ITS height instead of theirs. Driven through the real
     admin upload with an 800 by 3200 picture: at 1280 the plate
     came out 310 by 1240 against words 722 tall and the banner
     stood 1459 - TALLER THAN THE 1436 THIS ROUND SET OUT TO CUT
     - with the words centred in a tall thin column and a run of
     empty velvet above and below them. At 1920 it was 1513
     against 1064. That is the original fault reopened on the
     other side of the composition, and Kate can upload any
     shape she likes: with the words this store ships the plate
     takes over past about 1:2.33 at 1280 and 1:2.55 at 1920,
     and sooner than that on a banner with fewer words in it.

     TAKING THE PICTURE OUT OF FLOW IS THE WHOLE CAP, and it is
     a cap rather than a number. An absolutely positioned child
     contributes nothing to its parent's height, so the plate
     has no content height of its own, the row is sized by the
     words alone, and align-self: stretch then fills the plate
     to exactly what the words need. Nothing is typed here that
     a retune could make wrong, and no shape of photograph can
     reach past it. The plate is already position: relative and
     already clips what overflows it, so the frame the picture
     is cropped into is the one it always was, and the crop
     window stays biased to where faces live.

     WHAT IT COSTS, said plainly: a photograph is now cropped to
     whatever shape the words leave, so a very tall portrait
     beside very few words is cropped hard. The alternative is a
     banner whose height an upload decides, which is the thing
     this round exists to end. Below 1240 nothing here applies:
     the plate is under the words, in its own 4/5 frame, and its
     height is its own business. */
  .vp-page--careers .vp-hero__grid--split > .vp-hero__poster > img {
    position: absolute;
    inset: 0;
  }
}

/* ---- the page rhythm: space, and a hairline worn once ------- */
/* Space is the single strongest signal of expense, so the bands
   breathe deeper here than anywhere else on the site, and each
   band that sits on the plain canvas opens with one short gold
   hairline, centred, the way a brochure rules off a chapter. The
   lift and panel bands are excluded because their change of
   ground IS their divider, and a rule on top of it would be two
   dividers in a row: the very thing the brief said reads cheap. */
.vp-page--careers .vp-section { padding: clamp(72px, 10vw, 136px) 0; }
.vp-page--careers .vp-section--tight { padding: clamp(48px, 6.5vw, 88px) 0; }
/* The designer's Generous step is restated at this scope, or the
   deeper base above would outrank it and quietly turn the space
   control into a no-op on this one page. It still sits a step
   above the page's own rhythm. */
.vp-page--careers .vp-section--generous { padding: var(--vp-space-generous) 0; }
.vp-page--careers .vp-section:not(.vp-section--lift):not(.vp-section--panel)::before {
  content: "";
  display: block;
  width: min(180px, 34%);
  margin: 0 auto clamp(52px, 7vw, 92px);
  border-top: 1px solid var(--vp-line-2);
}

/* The intro band deepens from the raised tone back to the
   canvas, held between two faint hairlines, so the change of
   ground reads as a velvet panel rather than a flat stripe. */
.vp-page--careers .vp-section--lift {
  background: var(--vp-canvas-2);
  background: linear-gradient(180deg, var(--vp-canvas-2) 0%, color-mix(in srgb, var(--vp-canvas-2) 55%, var(--vp-canvas)) 100%);
  border-top: 1px solid var(--vp-line);
  border-bottom: 1px solid var(--vp-line);
}
/* AND PANEL IS RESTATED BESIDE IT, 2026-08-25, because the two
   halves of one control must behave alike. "Background: Tint"
   already reached the careers banner - the rule above is two
   classes and beats the banner's own ground by sitting after it
   - while "Background: Panel" is the base sheet's one class and
   lost, so Kate picking Tint saw the velvet replaced and Kate
   picking Panel saw nothing happen. Same value the base rule
   gives it; every other block on this page already had it,
   because nothing on them was outranking it. */
.vp-page--careers .vp-section--panel { background: var(--vp-card); }

/* ---- what we offer: the list worn as cards ------------------ */
/* The rows keep their gold left rule, which is the house's mark
   for a points list and the seam the Edge line control colours;
   no border colour is set here so that control keeps meaning
   what it means. What changes is the ground and the room: sunk
   toward the canvas like the benefit cards, deeper padding, and
   the soft shadow, so four short rows read as four set stones
   rather than four boxes. */
.vp-page--careers .vp-points { gap: clamp(18px, 2.2vw, 28px); }
.vp-page--careers .vp-points li {
  padding: clamp(24px, 2.8vw, 34px) clamp(24px, 2.8vw, 36px);
  background: var(--vp-card);
  background: color-mix(in srgb, var(--vp-card) 58%, var(--vp-canvas));
  box-shadow: var(--lift-shadow, 0 24px 50px -24px rgb(var(--canvas-rgb, 20 0 3) / 0.85));
}

/* ---- the application: the conversion point ------------------ */
/* The panel is the most dressed surface on the page because it
   is the one the whole page exists to walk somebody into. The
   ground falls from lifted wine to the raised canvas tone, under
   the deep soft shadow; measured on the lighter stop of that
   fall (#30060C, the mix below), gold reads 7.73:1 and muted
   7.81:1, and both read higher still on the darker stop. */
.vp-page--careers .vp-apply {
  max-width: 760px;
  padding: clamp(30px, 4.5vw, 62px) clamp(22px, 4vw, 58px);
  background: var(--vp-card);
  background: linear-gradient(180deg, color-mix(in srgb, var(--vp-card) 78%, var(--vp-canvas)) 0%, var(--vp-canvas-2) 100%);
  border: 1px solid var(--vp-line);
  box-shadow: var(--lift-shadow, 0 24px 50px -24px rgb(var(--canvas-rgb, 20 0 3) / 0.85));
}
.vp-page--careers .vp-apply .vp-section__head { margin-bottom: clamp(30px, 3.6vw, 44px); }

/* THE SECTION LEGENDS AS REFINED SMALL CAPS, which is the
   brief's own sentence. The serif legends matched the section
   heads above them and on an eight-section form that meant nine
   headlines; set as letter-spaced capitals over a short gold
   rule they read as a brochure's chapter marks and the one serif
   heading left, How to apply, owns the panel. gold-bright on the
   panel's lighter stop measures 11.14:1. */
.vp-page--careers .vp-form__legend {
  font-family: var(--vp-body);
  font-weight: 600;
  font-size: 12.5px;
  line-height: 1.5;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--vp-gold-soft);
  margin: 0 0 clamp(20px, 2.4vw, 28px);
}
.vp-page--careers .vp-form__legend::before {
  content: "";
  display: block;
  width: 30px;
  margin: 0 0 12px;
  border-top: 1px solid var(--vp-gold);
}

/* Deeper section breaks, so each chapter of the form gets the
   pause the page around it gets. */
.vp-page--careers .vp-form__group + .vp-form__group {
  margin-top: clamp(36px, 4.2vw, 54px);
  padding-top: clamp(32px, 3.8vw, 48px);
}

/* THE FIELD LABELS, unhurried. READ THE NOTE AT .vp-form label
   ABOVE BEFORE TOUCHING ANY OF THIS: that selector outranks any
   bare class, and this scoped copy of it now also ties with the
   opt and check dressings above on specificity and lands later,
   so those two are RE-DRESSED below with the page class carried
   in front, which puts them back above this rule where they have
   to be. Any new label rule on this page must carry
   .vp-page--careers .vp-form label... in front of it too. */
.vp-page--careers .vp-form label {
  margin: 26px 0 9px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.2em;
}
.vp-page--careers .vp-form label:first-child { margin-top: 0; }
.vp-page--careers .vp-form__choice { margin-top: 26px; }
.vp-page--careers .vp-form__choice legend {
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.2em;
}

/* The boxes: softened corners, room to type, and a focus state
   that arrives as a gold ring rather than a blink. The 16px
   floor is load-bearing (mobile Safari zooms below it, see the
   funnel note above) and is restated rather than inherited so
   nobody thins it later. Placeholders were the one pair on this
   page under AA: muted at the old 0.62 ghost measured 3.84:1 on
   the canvas boxes, so the ghost is lifted to 0.8, which
   measures 5.82:1. */
.vp-page--careers .vp-form input,
.vp-page--careers .vp-form textarea {
  padding: 15px 17px;
  border-radius: 10px;
  font-size: 16px;
}
.vp-page--careers .vp-form textarea { min-height: 160px; }
.vp-page--careers .vp-form input::placeholder,
.vp-page--careers .vp-form textarea::placeholder { opacity: 0.8; }
.vp-page--careers .vp-form input:focus,
.vp-page--careers .vp-form textarea:focus {
  border-color: var(--vp-gold);
  box-shadow: 0 0 0 3px var(--vp-line);
}

/* The answers, re-dressed as pills with the page class carried
   in front so they stay above the label rule (see the note
   there). Every value the shipped dressing sets is restated,
   then the pill shape and the tap room: 12px padding on a 15px
   line keeps every option at or above the 44px floor that tests
   18.60 and 18.61 measure. */
.vp-page--careers .vp-form label.vp-form__opt {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  padding: 12px 18px;
  background: var(--vp-canvas);
  border: 1px solid var(--vp-line);
  border-radius: 999px;
  color: var(--vp-ink);
  font-family: var(--vp-body);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
  transition: border-color 0.3s var(--vp-ease-1), background 0.3s var(--vp-ease-1);
}
.vp-page--careers .vp-form label.vp-form__opt:hover { border-color: var(--vp-line-2); }
.vp-page--careers .vp-form label.vp-form__opt:focus-within { border-color: var(--vp-gold); }
.vp-page--careers .vp-form label.vp-form__opt:has(input:checked) {
  border-color: var(--vp-gold);
  background: var(--vp-card);
}

/* The three declarations, re-dressed for the same tie reason.
   The 44px floor is the load-bearing line here (the note on the
   unscoped rule above says why) and it is restated, not
   inherited. */
.vp-page--careers .vp-form label.vp-form__check {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 24px 0 0;
  padding: 10px 0;
  box-sizing: border-box;
  min-height: 44px;
  color: var(--vp-ink);
  font-family: var(--vp-body);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.55;
  text-transform: none;
  cursor: pointer;
}

/* The photograph boxes, kept as real file inputs (replacing one
   costs the keyboard behaviour the browser gives it, see the
   unscoped rule above) and dressed instead: a softer plate, and
   the picker's own button worn as a small gold pill through the
   file-selector pseudo-element, which is the one part of a file
   input every current browser lets a sheet reach. */
.vp-page--careers .vp-form__photo { margin-top: 24px; }
.vp-page--careers .vp-form__photo input[type="file"] {
  padding: 16px;
  background: var(--vp-canvas);
  border: 1px dashed var(--vp-line-2);
  border-radius: 10px;
  color: var(--vp-muted);
  font-size: 14px;
}
.vp-page--careers .vp-form__photo input[type="file"]:focus {
  outline: none;
  border-color: var(--vp-gold);
  box-shadow: 0 0 0 3px var(--vp-line);
}
.vp-page--careers .vp-form__photo input[type="file"]::file-selector-button {
  margin-right: 14px;
  padding: 9px 18px;
  background: transparent;
  border: 1px solid var(--vp-gold);
  border-radius: 999px;
  color: var(--vp-gold-soft);
  font-family: var(--vp-body);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
}

/* The monitoring panel keeps its deliberate apartness and takes
   the page's corner radius through the property it already
   reads; only the room inside it grows. */
.vp-page--careers .vp-form__group--monitoring { padding: clamp(24px, 3vw, 36px); }

/* The one sentence that says what went wrong: unchanged in
   voice, given the page's corners. */
.vp-page--careers .vp-form__error { padding: 14px 17px; border-radius: 10px; }

/* ---- the rose: the call to action, and only that ------------
   One tone for the one thing the page asks somebody to do, worn
   on the hero button and the submit and nowhere else, so it
   stays an invitation rather than a theme. Both directions were
   measured: rose ink and border on the canvas 9.24:1, and the
   canvas ink on a filled rose hover the same 9.24:1.

   WORN THROUGH A PAGE PAIR, NEVER READ DIRECTLY. The first cut
   of this dress read var(--vp-rose) straight off the button,
   which silenced the designer's Accent control on this page: an
   accent lands as an INLINE --gold pair on the section it was
   chosen for (blockColourStyle() in both renderers), and
   site.css .btn--gold follows it by ordinary inheritance - a
   higher-specificity rose read here outranked all of that. So
   the fallback chain is now, nearest wins:
     1. the buttons read --vp-cta and --vp-cta-soft;
     2. the pair is defined as the rose pair on the BODY, above
        every section, so any definition on a section shadows it
        for everything inside that section;
     3. a section carrying a designer Accent - recognisable as
        the literal '--gold:' in its style attribute, an emission
        whose bytes are canonical (colour document section 1.3)
        and pinned by the 9.37 oracle, so neither renderer can
        quietly change them - re-points the pair at --vp-gold /
        --vp-gold-soft, which inside that section resolve to her
        accent;
     4. no accent, no match, and the rose stands.
   The sheen sweep and the hover glow follow the same pair where
   the browser can derive channels from a token; elsewhere the
   gold sheen site.css paints stands, which is a warmth, not a
   fault. */
.vp-page--careers { --vp-cta: var(--vp-rose); --vp-cta-soft: var(--vp-rose-soft); }
.vp-page--careers section[style*="--gold:"] {
  --vp-cta: var(--vp-gold);
  --vp-cta-soft: var(--vp-gold-soft);
}
.vp-page--careers .btn--gold {
  border-color: var(--vp-cta);
  color: var(--vp-cta);
}
.vp-page--careers .btn--gold:hover,
.vp-page--careers .btn--gold:focus-visible {
  background: var(--vp-cta);
  border-color: var(--vp-cta);
  color: var(--vp-canvas);
}
@supports (color: rgb(from white r g b / 1)) {
  .vp-page--careers .btn--gold::before {
    background: linear-gradient(100deg, transparent 0%, rgb(from var(--vp-cta-soft) r g b / 0.4) 50%, transparent 100%);
  }
  .vp-page--careers .btn--gold:hover {
    box-shadow: 0 10px 40px -12px rgb(from var(--vp-cta) r g b / 0.45);
  }
}
.vp-page--careers .vp-form__send {
  margin-top: clamp(30px, 4vw, 44px);
  padding: 17px 44px;
}
@media (max-width: 559px) {
  /* The whole width of the panel under a thumb, on the screens
     where the thumb is how it is pressed. */
  .vp-page--careers .vp-form__send { width: 100%; }
}

/* ---- the confirmation: warm, and worth arriving at ---------- */
.vp-page--careers .vp-form__done { padding: clamp(20px, 3vw, 38px) 0; }
.vp-page--careers .vp-form__tick {
  width: 60px;
  height: 60px;
  box-shadow: var(--glow-gold, 0 10px 40px -12px rgb(var(--canvas-rgb, 20 0 3) / 0.85));
}

/* ---- the no-script way in, dressed like the page ------------
   The served body draws this sentence instead of a form it could
   never send (test 9.35). It had no rule anywhere, so it wore
   the browser's defaults inside a dressed panel. */
.vp-page--careers .vp-form__noscript {
  margin: 0 0 14px;
  max-width: 62ch;
  color: var(--vp-ink);
  font-size: 16px;
  line-height: 1.7;
}
.vp-page--careers .vp-form__noscript-contact { margin: 0; font-size: 16px; }
.vp-page--careers .vp-form__noscript-contact a {
  color: var(--vp-gold-soft);
  text-decoration: none;
  border-bottom: 1px solid var(--vp-line-2);
}
.vp-page--careers .vp-form__noscript-contact a:hover { color: var(--vp-gold); }

/* ---- quiet on request --------------------------------------- */
/* THE CLAIM WAS FALSE AND IS NOW HELD, 2026-08-25. The columns
   did come off this list - they were cards that lifted on hover
   and they are hairline-divided columns now, with nothing on
   them that moves - but the handover then said the banner does
   not animate at all under prefers-reduced-motion, and it did.
   Asked in the browser with the preference on, three things
   inside the banner still had a live transition: the crumb links
   (0.3s on colour), the way-in button (0.4s on five properties)
   and the sheen that sweeps across it (0.7s on left).

   WHY THE OVERRIDE LIVES HERE AND NOT AT SOURCE. Two of the
   three are site.css .btn and .btn--gold::before, and site.css is
   frozen - test 8.4 holds its hash - so the only place that can
   answer for them is this sheet. The third, .vp-crumbs a, IS
   this sheet's own rule and could have been quietened where it
   is written; it is quietened here instead so this round changes
   nothing outside /join-us, which is the law the whole careers
   block is written under. That leaves a real gap worth naming
   rather than hiding: the same crumb links still transition on
   /services and on the city pages, and so do the site shell's
   nav links and the form's own inputs. None of them is this
   banner's, and none of them is fixed here.

   !important is NOT needed for specificity and is not there for
   it: three classes plus an element beats site.css's single .btn
   comfortably. It is there because a transition shorthand set
   later by any sheet on this page would silently put the motion
   back, and because the rule beside it already reads that way -
   inside a reduced-motion block, "none" is the whole point and
   nothing should be allowed to argue with it. */
@media (prefers-reduced-motion: reduce) {
  .vp-page--careers .vp-form label.vp-form__opt { transition: none !important; }
  .vp-page--careers .vp-hero--careers .vp-crumbs a,
  .vp-page--careers .vp-hero--careers .btn,
  .vp-page--careers .vp-hero--careers .btn::before { transition: none !important; }
}

/* ---- the blog list: older-posts pager, browse by year, the archive ----
   Plain, quiet, and only drawn once the blog has more than one page. Placed
   before the heading-size utilities so those stay last, as they must. */
.vp-pager { display: flex; align-items: center; justify-content: center; gap: 22px; margin: 44px 0 0; flex-wrap: wrap; }
.vp-pager a { color: var(--vp-gold-soft); text-decoration: none; border-bottom: 1px solid var(--vp-gold); padding-bottom: 2px; }
.vp-pager a:hover { color: var(--vp-gold); }
.vp-pager__where { opacity: 0.75; font-size: 0.92em; }
.vp-years { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 18px; margin: 34px 0 0; padding-top: 22px; border-top: 1px solid var(--vp-gold); border-top-color: color-mix(in srgb, var(--vp-gold) 35%, transparent); justify-content: center; }
.vp-years__label { text-transform: uppercase; letter-spacing: 0.16em; font-size: 0.74em; opacity: 0.75; }
.vp-years a { color: var(--vp-gold-soft); text-decoration: none; }
.vp-years a:hover { color: var(--vp-gold); text-decoration: underline; }
.vp-years a span { opacity: 0.65; font-size: 0.9em; }
.vp-archive-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 0; }
.vp-archive-list li { display: flex; justify-content: space-between; align-items: baseline; gap: 18px; padding: 14px 0; border-bottom: 1px solid var(--vp-gold); border-bottom-color: color-mix(in srgb, var(--vp-gold) 28%, transparent); }
.vp-archive-list a { color: var(--vp-gold-soft); text-decoration: none; }
.vp-archive-list a:hover { text-decoration: underline; }
.vp-archive-list time, .vp-archive-list li > span { opacity: 0.65; font-size: 0.9em; white-space: nowrap; }
.vp-archive-years li { justify-content: flex-start; }
.vp-archive-back { margin: 30px 0 0; }
.vp-archive-back a { color: var(--vp-gold-soft); }

/* ---- the heading-size utilities -----------------------------
   LAST ON PURPOSE. Each is element-qualified so it ties with the
   component rules above on specificity (0,1,1) rather than
   beating them, and the tie is resolved by source order - which
   means these three lines must stay at the foot of this sheet.
   A block at its own default size emits no class at all, so the
   component rule is what applies and nothing here is reached. */
h2.vp-h--l { font-size: clamp(30px, 4.2vw, 48px); }
h2.vp-h--m { font-size: clamp(28px, 3.4vw, 44px); }
h2.vp-h--s { font-size: clamp(25px, 2.5vw, 33px); }

/* ---- motion ------------------------------------------------ */
/* The one component in this library that moves. Everything else
   is still by construction, so this list is short and honest. */
@media (prefers-reduced-motion: reduce) {
  .vp-card { transition: none !important; }
}

/* ---- the privacy page, and its footer link ---------------- */
.vp-legal h1 { font-family: var(--vp-display); font-weight: 400; font-size: clamp(30px, 4.6vw, 46px); line-height: 1.15; margin: 0 0 28px; }
.vp-legal .vp-prose h2 { font-family: var(--vp-display); font-weight: 500; font-size: clamp(21px, 2.6vw, 26px); margin: 2em 0 0.6em; }
.vp-legal .vp-prose ul { margin: 0 0 1.35em; padding-left: 1.2em; }
.vp-legal .vp-prose li { margin: 0 0 0.6em; color: var(--vp-ink); font-size: 18.5px; line-height: 1.7; }
.vp-legal .vp-prose a { color: var(--vp-gold-soft, var(--gold-soft)); }
@media (max-width: 560px) { .vp-legal .vp-prose li { font-size: 17px; } }
