/* The blog's prose, said once.
 *
 * Every rule here replaces an inline style attribute that was repeated across
 * the 43 posts — 1,025 of them, 16% of every inline style in the repo. A
 * paragraph carried its own font, colour, measure and hyphenation on every
 * single <p>, so changing how the blog reads meant editing 770 attributes and
 * hoping none had drifted.
 *
 * WHY THESE FIVE FIRST. The prose container and the figure were held back in
 * this first pass, because responsive.css then keyed 58 of its selectors on
 * inline-style substrings — [style*="display:grid"], [style*="width:"],
 * [style*="padding:0 32px"] — and both styles matched them. Removing such a
 * style unhooked a responsive rule silently: nothing threw, and the page simply
 * stopped reflowing on a phone.
 *
 * That is history now. responsive.css targets classes and has zero such hooks,
 * and .vj-prose-main and .vj-figure are defined further down this file. The
 * paragraph stays because the hazard it describes is the one to check before
 * moving any style into a class, and because a comment that quietly becomes
 * false is worse than one that dates itself.
 *
 * THERE WERE TWO PARAGRAPH SIZES AND NOW THERE IS ONE. 27 posts set 17.5px/1.7
 * and 12 set 19px/1.75, and no post mixed them — two eras of the design rather
 * than one era with mistakes in it. That was left alone here deliberately,
 * because collapsing it is a decision about how the blog reads. Vin made the
 * decision on 2026-09-11 and the type scale carries it out: .vj-p is the single
 * reading measure, and .vj-p-lg is retired.
 */

.vj-p {
  margin: 0 0 18px;
  color: var(--vj-ink-serif);
  text-align: justify;
  hyphens: auto;
  font-family: 'Newsreader', Georgia, serif;
  font-weight: 400;
}
/* ONE reading measure. There were two — 17.5px/1.7 on 27 posts and 19px/1.75
 * on 12 — and no post mixed them, so they were two eras of the design rather
 * than one era with mistakes in it. The larger population wins. */
.vj-p { font-size: 17.5px; line-height: 1.7; }

.vj-h2 {
  margin: 28px 0 14px;
  font: 500 28px/1.3 'Newsreader', Georgia, serif;
  letter-spacing: -0.005em;
  color: var(--vj-ink);
}

.vj-h3 {
  margin: 24px 0 12px;
  font: 500 22px/1.35 'Newsreader', Georgia, serif;
  color: var(--vj-ink);
}

/* The small mono label above a heading — "VERIFIED BY VIJIL" and its kin. */
.vj-eyebrow {
  font: 500 13px ui-monospace, Menlo, monospace;
  letter-spacing: .16em;
  color: var(--vj-brand);
}

/* ---------------------------------------------------------------------------
 * The buttons.
 *
 * The pill was written 87 times across 22 pages in 14 different ways. That is
 * why changing its colour was work: a colour has to be found before it can be
 * changed, and it was in 87 places. Two of those 14 "variants" — 11 uses — were
 * the same eight declarations in a different order, which no amount of care
 * catches by reading.
 *
 * They are four buttons. Sizes are normalised to the majority within each
 * family, so a handful move by 1-2px; the alternative is keeping drift because
 * it is old. Changing the CTA colour is now one line here.
 * ------------------------------------------------------------------------- */

.vj-btn, .vj-btn-ghost, .vj-btn-ghost-brand {
  text-decoration: none;
  font-size: 16px;
  border-radius: 999px;
  white-space: nowrap;
}
.vj-btn {
  background: var(--vj-brand);
  color: var(--vj-surface);
  font-weight: 600;
  padding: 12px 28px;
}
.vj-btn-ghost, .vj-btn-ghost-brand {
  font-weight: 400;
  padding: 12px 26px;
}
.vj-btn-ghost       { color: var(--vj-ink);   border: 1px solid rgba(16,24,40,.35); }
.vj-btn-ghost-brand { color: var(--vj-brand); border: 1px solid rgba(2,71,169,.45); }

/* The small surface chip — a tag, not a call to action. */
/* ---------------------------------------------------------------------------
 * THE `hidden` ATTRIBUTE NEEDS THIS, AND THE REASON IS SPECIFICITY.
 *
 * `[hidden] { display: none }` lives in the browser's UA stylesheet, which any
 * author rule outranks. This site classes almost everything `.vj-grid` or
 * `.vj-flex`, both of which set `display` — so `el.hidden = true` set the
 * attribute, changed nothing on screen, and left the element in the layout.
 *
 * It shipped in /blog's category filter: 39 of 43 cards were marked hidden and
 * all 39 stayed visible. The test that missed it counted `!card.hidden` — the
 * property it had just assigned — instead of the computed display, so it
 * confirmed the assignment rather than the outcome.
 * ------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------
 * A DATED EDITORIAL NOTE at the head of a post that has gone stale.
 *
 * Nine posts sell "Vijil Evaluate", a name retired for Diamond; four quote a
 * latency figure with no percentile and no hardware. Rewriting a published post
 * loses the record of what we said and when. A dated note keeps both: the post
 * stands, and the reader is told what has changed since.
 * ------------------------------------------------------------------------- */
.vj-dated-note {
  margin: 0 0 28px;
  padding: 14px 18px;
  border-left: 3px solid var(--vj-accent);
  background: var(--vj-surface-alt);
  border-radius: 0 8px 8px 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--vj-ink-body);
}
.vj-dated-note b { color: var(--vj-ink); font-weight: 600; }

[hidden] { display: none !important; }

.vj-chip {
  font-size: 16px;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--vj-surface);
  border: 1px solid rgba(16,24,40,.12);
  color: var(--vj-ink-body);
  text-decoration: none;
  white-space: nowrap;
  flex: none;
}

/* The nav size. A CTA in a 65px-tall bar is not the same button as a CTA under
 * a headline, and collapsing the two would have grown every nav on the site.
 * /integrations also uses this pair in its body, which is why it is a class and
 * not two declarations inside SiteHeader. */
.vj-btn-sm, .vj-btn-ghost-sm {
  text-decoration: none;
  font-size: 15px;
  border-radius: 999px;
  white-space: nowrap;
  flex-shrink: 0;
}
.vj-btn-sm {
  background: var(--vj-brand);
  color: var(--vj-surface);
  font-weight: 600;
  padding: 10px 20px;
}
.vj-btn-ghost-sm {
  color: var(--vj-brand);
  border: 1px solid rgba(2,71,169,.45);
  padding: 8px 18px;
}

/* /news sets its chips on the page ground rather than on a raised surface.
 * That is a real difference, not drift, so it gets a name instead of being
 * flattened into .vj-chip. */
.vj-chip-ivory { background: var(--vj-ground-flat); font-size: 16px; }

/* ---------------------------------------------------------------------------
 * The article.
 *
 * A blog post is a document type, not 43 pages that happen to look alike.
 * These are the pieces one is made of. They complete the prose family above:
 * .vj-p sets the paragraph; these set everything around it.
 * ------------------------------------------------------------------------- */

.vj-prose-main { max-width: 880px; margin: 0 auto; padding: 0 32px 80px; display: grid; gap: 44px; }

.vj-post-title {
  margin: 0;
  font: 500 44px/1.15 'Newsreader', Georgia, serif;
  letter-spacing: -0.015em;
  color: var(--vj-ink);
  text-wrap: balance;
}

/* Lists take the measure of the paragraph they sit among, which is why there
 * are two — same 17.5 / 19px split, same 27-post / 12-post cohorts. */
.vj-ul {
  margin: 0 0 18px;
  padding-left: 24px;
  display: grid;
  gap: 8px;
  font-family: 'Newsreader', Georgia, serif;
  font-weight: 400;
  color: var(--vj-ink-serif);
}
.vj-ul { font-size: 17.5px; line-height: 1.7; }

/* A list item in the SANS measure — spec lists and feature rows, not prose. */
.vj-li-body { margin: 0 0 8px; font-size: 16px; line-height: 1.5; color: var(--vj-ink-body); }
/* A list inside a panel: the panel already carries the visual weight, so the
 * list gives up the reading measure and the bullet size that .vj-ul sets. */
.vj-ul-plain { list-style: disc; color: var(--vj-ink-subtle); }
.vj-ul-plain > li:last-child { margin-bottom: 0; }

.vj-pre {
  margin: 6px 0 22px;
  background: var(--vj-ink);
  color: #e6edf6;
  border-radius: 12px;
  padding: 18px 22px;
  font: 400 16px/1.6 ui-monospace, Menlo, monospace;
  overflow-x: auto;
  text-align: left;
}
/* A <pre> already inside a dark panel, so it paints no ground of its own. */
.vj-pre-plain {
  margin: 0;
  padding: 12px 16px;
  font: 500 15px/1.75 ui-monospace, Menlo, monospace;
  color: var(--vj-hairline);
  overflow-x: auto;
}

.vj-code {
  font: 500 0.9em ui-monospace, Menlo, monospace;
  background: rgba(16,24,40,.06);
  padding: 2px 6px;
  border-radius: 5px;
}

/* Images, by the job they do rather than by their pixel size. */
.vj-figure  { max-width: 100%; height: auto; border-radius: 12px; border: 1px solid rgba(16,24,40,.12); }
.vj-avatar  { width: 30px; height: 30px; border-radius: 7px; background: var(--vj-surface);
              border: 1px solid rgba(16,24,40,.12); flex: none; object-fit: contain;
              padding: 2px; box-sizing: border-box; }
.vj-icon-sm { width: 20px; height: 20px; border-radius: 4px; object-fit: contain; flex: none; }
.vj-portrait { width: 100%; aspect-ratio: 1/1.05; object-fit: cover; border-radius: 12px;
               border: 1px solid rgba(16,24,40,.12); filter: grayscale(1); }

/* ---------------------------------------------------------------------------
 * Ink. Four colours said 291 times between them.
 * ------------------------------------------------------------------------- */
.vj-ink        { color: var(--vj-ink); }
.vj-ink-body   { color: var(--vj-ink-body); }
.vj-ink-strong { color: var(--vj-ink); font-weight: 600; }
.vj-link-muted { color: var(--vj-ink-muted); text-decoration: none; }

/* Truncation, composable: a cell that must not blow out its grid track needs
 * both, a plain overflow clip needs only the first. */
.vj-truncate { overflow: hidden; text-overflow: ellipsis; }
.vj-minw0    { min-width: 0; }

/* ---------------------------------------------------------------------------
 * The terminal. A window chrome, three lights, a prompt and a note.
 *
 * Drawn 260 times across the product and integration pages, and it is one
 * component: the same bar, the same #ff5f57 / #febc2e / #28c840, the same
 * dimmed note. `.vj-cli-note` already existed as a class — responsive.css
 * hides the note on a phone so a long command can keep the row to itself.
 * ------------------------------------------------------------------------- */
.vj-term-bar {
  display: flex; align-items: center; gap: 6px; padding: 8px 12px;
  background: rgba(255,255,255,.05); border-bottom: 1px solid rgba(255,255,255,.08);
}
.vj-term-dot { width: 9px; height: 9px; border-radius: 50%; }
.vj-term-dot-red   { background: #ff5f57; }
.vj-term-dot-amber { background: #febc2e; }
.vj-term-dot-green { background: #28c840; }
.vj-term-title { margin-left: 8px; font: 500 13px ui-monospace, Menlo, monospace; color: rgba(230,237,245,.55); }
.vj-term-line {
  display: flex; justify-content: space-between; gap: 16px;
  font: 500 15px ui-monospace, Menlo, monospace; line-height: 1.9;
  color: var(--vj-hairline); white-space: nowrap; overflow: hidden;
}
.vj-term-ok  { color: #5fd77a; }
.vj-cli-note { color: rgba(230,237,245,.4); flex: none; }

/* ---------------------------------------------------------------------------
 * Rows. Each is a row of a specific thing, named for the thing.
 * ------------------------------------------------------------------------- */
.vj-lb-row {
  display: grid; grid-template-columns: 64px minmax(0,1fr) 110px; gap: 20px;
  align-items: center; padding: 18px 0; border-top: 1px solid rgba(16,24,40,.12);
  text-decoration: none;
}
.vj-def-row {
  display: grid; grid-template-columns: 230px minmax(0,1fr); gap: 24px;
  align-items: center; padding: 14px 0; border-top: 1px solid rgba(16,24,40,.12);
}
.vj-spec-row {
  display: flex; gap: 8px; font-size: 15px; line-height: 1.5;
  color: var(--vj-ink-body); white-space: nowrap; min-width: 0;
}
.vj-meta-row {
  display: flex; align-items: center; gap: 10px; font-size: 16px;
  color: var(--vj-ink-muted); font-family: Futura, 'Jost', Arial, sans-serif;
}
.vj-meta-wrap {
  display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap;
  font-family: Futura, 'Jost', Arial, sans-serif; font-size: 16px; color: var(--vj-ink-muted);
}
.vj-row-tight { display: flex; align-items: center; gap: 10px; min-width: 0; }
.vj-foot-row {
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  border-top: 1px solid rgba(16,24,40,.14); padding-top: 22px;
  font-family: Futura, 'Jost', Arial, sans-serif; font-size: 16px;
}

/* ---------------------------------------------------------------------------
 * Stacks. A grid whose only job is to space its children.
 * ------------------------------------------------------------------------- */
.vj-stack-0, .vj-stack-2, .vj-stack-4, .vj-stack-10, .vj-stack-14 { display: grid; }
.vj-stack-0  { gap: 0; }
.vj-stack-2  { gap: 2px; }
.vj-stack-4  { gap: 4px; }
.vj-stack-10 { gap: 10px; }
.vj-align-start { align-content: start; }
.vj-stack-14 { gap: 14px; }

/* ---------------------------------------------------------------------------
 * Type, and an admission. Three of these are named for their size, which is
 * the habit that produced 44 font sizes in the first place. They are named
 * that way because a single role is not visible from the markup, and guessing
 * one would be worse than saying "13px body". Consolidating this into a real
 * scale is a design decision, not a refactor, and it is not made here.
 * ------------------------------------------------------------------------- */
.vj-serif-19     { font: 500 19px/1.3 'Newsreader', Georgia, serif; color: var(--vj-ink); }
.vj-sans-16-muted{ font-size: 16px; line-height: 1.5; color: var(--vj-ink-muted); font-family: Futura, 'Jost', Arial, sans-serif; }
.vj-mono-14-right{ font: 500 14px ui-monospace, Menlo, monospace; color: var(--vj-ink-subtle); text-align: right; }
.vj-mono-13      { font: 500 13px ui-monospace, Menlo, monospace; color: var(--vj-ink-muted); }
.vj-body-15-tight      { font-size: 15px; line-height: 1.5; color: var(--vj-ink-body); }
.vj-body-16      { font-size: 16px; line-height: 1.55; color: var(--vj-ink-body); }
/* Named for what it is: the name cell of a row, clipped to its track. */
.vj-cell-name    { font-size: 16px; font-weight: 600; color: var(--vj-ink);
                   white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vj-link-title   { font-size: 16px; font-weight: 500; color: var(--vj-ink);
                   text-decoration: none; line-height: 1.45; }

.vj-rule        { height: 1px; background: rgba(16,24,40,.14); margin: 8px 0; }
.vj-clamp       { min-width: 0; max-width: 100%; }
.vj-narrow-main { max-width: 760px; margin: 0 auto; display: grid; gap: 22px; padding-top: 53px; }

/* ---------------------------------------------------------------------------
 * Eyebrows, headings, links and panels — and the drift they expose.
 *
 * Three of these pairs differ by ONE declaration and are almost certainly the
 * same thing written twice. They are named separately rather than merged,
 * because merging them changes how a page looks and that is a design decision:
 *
 *   .vj-h-32 / .vj-h-32-plain   identical but for text-wrap:balance  (23 / 21)
 *   .vj-panel-dark / -flat      identical but for the drop shadow    (17 / 12)
 *   .vj-body-16 / -tight        14px at line-height 1.55 vs 1.5      (28 / 16)
 *
 * AND A NAME THAT STATES A SIZE MUST STATE THE RIGHT ONE. The type-scale
 * snap moved values under three of these names and left the names behind:
 * .vj-serif-20 held 19px, .vj-h-16-5 held 16px and had become a byte-for-byte
 * duplicate of .vj-h-16, and .vj-body-13-5 held 14px. Copilot caught all three
 * on PR #27. check-type-scale now fails on a name that disagrees with its own
 * declaration, because a size in a class name is a claim.
 *
 * Three eyebrow variants below are the same story at 10.5px, 11px and 12px.
 * ------------------------------------------------------------------------- */
.vj-eyebrow-mono { font: 500 13px ui-monospace, Menlo, monospace; letter-spacing: .08em; text-transform: uppercase; color: var(--vj-brand); }
.vj-eyebrow-lg   { font-size: 14px; letter-spacing: .1em; text-transform: uppercase; color: var(--vj-brand); font-weight: 600; }
.vj-eyebrow-sm   { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: var(--vj-brand); }

.vj-h-32, .vj-h-32-plain { margin: 0; font-size: 32px; font-weight: 600; letter-spacing: -0.01em; color: var(--vj-ink); }
.vj-h-32 { text-wrap: balance; }
.vj-h-22   { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -0.005em; color: var(--vj-ink); }
.vj-h-16   { font-size: 16px;   font-weight: 600; color: var(--vj-ink); }
.vj-h-16-tight   { font-size: 16px;   font-weight: 600; line-height: 1.35; color: var(--vj-ink); }
.vj-h-row { display: flex; align-items: center; gap: 8px; font-size: 17.5px; font-weight: 600; color: var(--vj-ink); }

.vj-pullquote { margin: 0; font: 400 italic 22px/1.5 'Newsreader', Georgia, serif; color: var(--vj-ink-body); text-wrap: pretty; }

.vj-link-strong { font-size: 16px; font-weight: 600; color: var(--vj-ink);   text-decoration: none; }
.vj-link-brand  { font-size: 15px; color: var(--vj-brand); text-decoration: none; }
.vj-link-med    { font-size: 17.5px; font-weight: 600; line-height: 1.35; color: var(--vj-ink); text-decoration: none; }

.vj-panel-dark, .vj-panel-dark-flat {
  border-radius: 10px; overflow: hidden; background: var(--vj-ground-ink);
  border: 1px solid rgba(255,255,255,.12);
}
.vj-panel-dark { box-shadow: 0 8px 24px rgba(0,0,0,.35); }

.vj-body-15       { font-size: 15px;   line-height: 1.6;  color: var(--vj-ink-body); }
.vj-body-16-flush { margin: 0; font-size: 16px; line-height: 1.55; color: var(--vj-ink-body); }
/* READING MEASURE. Computed against Anthropic, OpenAI, LangChain and Arize at
   1440px: none of them exceeds 68 characters per line, and this site ran to 110.
   Long measure is why the type read as too small — at a fixed letter size the
   eye's return sweep gets harder as the line grows, so the text feels denser
   than it is. These caps are ~70-75 characters at an average glyph width of half
   an em. max-width only ever constrains, so a class used inside a narrow tile is
   unaffected; it bites only where the text would otherwise run the full row. */
.vj-body-16, .vj-body-16-flush, .vj-body-16-tight { max-width: 520px; }
.vj-li-body { max-width: 500px; }          /* a bullet's indent eats into the measure */
/* The PENDING notes are bare <p> with no class of their own, so they inherited
   the full 820px row and ran to 109 characters per line. They are on their way
   out, but they are on the page today. */
.vj-pending p { max-width: 560px; }
.vj-body-15 { max-width: 560px; }

.vj-body-16-tight { font-size: 16px;   line-height: 1.5;  color: var(--vj-ink-body); }
.vj-mono-14-muted { font: 500 14px ui-monospace, Menlo, monospace; color: var(--vj-ink-muted); }
.vj-ink-muted-strong { color: var(--vj-ink-muted); font-weight: 600; }

.vj-label-stack { display: grid; gap: 4px; font-size: 15px; font-weight: 600; color: var(--vj-ink-strong); }
.vj-row-bordered { display: grid; gap: 2px; padding: 12px 0; border-top: 1px solid rgba(16,24,40,.12); }
.vj-marq-track { display: flex; gap: 72px; align-items: center; padding-right: 72px; }
.vj-pad-sm { padding: 8px 14px; }

/* ---------------------------------------------------------------------------
 * The rhetorical arc has a rhythm, and the rhythm is part of the argument.
 *
 * `.vj-beat` paints every section as a white card above 768px. On the legacy
 * arc that is right — those pages enumerate, and an enumeration wants even
 * weight. On the rhetorical arc it is wrong, because the beats do not have even
 * weight: a thesis is not a sidebar and a peroration is not another box.
 *
 * So the arc sets the treatment:
 *
 *   mark, claim     FLUSH. The thesis sits on the page. Putting the loudest
 *                   sentence on the site inside a container is the fastest way
 *                   to make it sound like a feature.
 *   ground          CARD. It is reference: what this is, who it is not for.
 *   problem         FLUSH. Narrative first, then the same failure priced for
 *                   each of the three roles who pay for it.
 *   tension         BAND. The one beat that changes ground under the reader.
 *                   This is the emotional low point of the argument and it is
 *                   the only place on the page where the surface itself moves.
 *                   It ends with the reader's own objection, quoted and
 *                   answered, because an objection you do not stage wins.
 *   concept         FLUSH, with the idea's properties carded.
 *   components      FLUSH, with the four parts carded. Every product names
 *                   four, which is a claim about the products, not a template.
 *   mechanism       FLUSH. The diagram is the object; a card around it would
 *                   be a frame around a frame.
 *   proof           FLUSH between rules. Evidence should look austere.
 *   fits            FLUSH. The lifecycle band and the cross-sell.
 *   pricing         FLUSH, with the three tiers carded. It carries no figure
 *                   and no image: a number here is one Sales has to honour,
 *                   and check-product-arc fails the build on one.
 *   start           FLUSH, with the three steps carded and the command below.
 *   cta             CARD, and the only one carrying a shadow, so the page has
 *                   an ending that is louder than everything before it.
 * ------------------------------------------------------------------------- */
@media (min-width: 768px) {
  /* The tension is the one section whose whole content is a single object, so
   * it takes the wide panel rather than a treatment of its own. Same tint as
   * every other panel on the site: the ground shifts under the reader without
   * the page gaining a fourth kind of box.
   *
   * NOT ON A SNAP PANEL. Both treatments below assume a section sized by its
   * content, which is what /product/{discover,diamond,darwin} have. On
   * /product/dome every section is a 100vh snap panel, so the tint drew a
   * bordered box the full height of the viewport and the proof rules drew
   * hairlines across it. The intent does not survive either: a snap panel
   * already shifts the ground under the reader by being its own screen, so
   * the treatment is redundant there as well as wrong. */
  [data-beat="tension"]:not(:where(.vj-snap, .vj-sec)) {
    padding: clamp(30px, 4vw, 56px) clamp(22px, 3.6vw, 48px);
    border-radius: 16px;
    background: rgba(16,24,40,.03);
    border: 1px solid rgba(16,24,40,.12);
  }
  [data-beat="proof"]:not(:where(.vj-snap, .vj-sec)) {
    padding: clamp(30px, 4vw, 48px) 0;
    border-top: 1px solid rgba(16, 24, 40, .14);
    border-bottom: 1px solid rgba(16, 24, 40, .14);
  }
}

/* ---------------------------------------------------------------------------
 * Figures, and where a product page is allowed to put one.
 *
 * An image on these pages does one of two jobs, and the beat says which:
 *
 *   mechanism    EXPLAIN. How it works, drawn. This is the beat where the
 *                reader is being shown the machinery, so it is the only beat
 *                that may carry more than one figure.
 *   proof        EVIDENCE. The results surface: a dashboard, a report, a
 *                measured plot. One figure, because evidence competes with
 *                itself.
 *
 * NOWHERE ELSE. The tension beat especially: it is the emotional low point and
 * it is carried by prose. An image there gives the reader somewhere to look
 * instead of somewhere to feel.
 *
 * GENEROUS, AND THAT IS A MEASUREMENT. 48px above and below at desktop, which
 * is more than the 28px gap between paragraphs, so a figure reads as a pause
 * rather than as another block. A screenshot sits on the page ground with a
 * hairline and a soft shadow — it is a window onto the product, not a card in
 * our layout.
 * ------------------------------------------------------------------------- */
.vj-fig {
  margin: 0;
  display: grid;
  gap: 12px;
  justify-items: center;
}
@media (min-width: 768px) {
  .vj-fig { margin: clamp(24px, 3.4vw, 48px) 0; }
}
.vj-fig img,
.vj-fig .vj-shot {
  width: 100%;
  max-width: 880px;
  height: auto;
  display: block;
  border-radius: 12px;
  border: 1px solid rgba(16,24,40,.14);
  box-shadow: 0 10px 30px rgba(16,24,40,.10);
  background: var(--vj-surface);
}
/* A caption states what the reader is looking at and where it came from. An
 * uncaptioned screenshot asks the reader to guess whether it is real. */
.vj-fig figcaption {
  max-width: 760px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--vj-ink-muted);
  text-align: center;
}
.vj-fig figcaption b { color: var(--vj-ink-body); font-weight: 600; }

/* ---------------------------------------------------------------------------
 * A PROSE PLACEHOLDER, in the same visual family as .vj-wire.
 *
 * Some claims are blocked on a measurement or a product fact rather than on
 * writing. Vin, 2026-09-12: "Leave everything that's not determined as a
 * placeholder. Fix the rest." Dashed and labelled so a reader sees a gap rather
 * than a claim, and so the gap cannot quietly become copy.
 * ------------------------------------------------------------------------- */
.vj-pending {
  display: grid;
  gap: 8px;
  padding: 18px 20px;
  border: 1px dashed rgba(16,24,40,.24);
  border-radius: 12px;
  background: transparent;
  max-width: 820px;
}
.vj-pending-tag {
  font: 500 13px ui-monospace, Menlo, monospace;
  letter-spacing: .14em;
  color: var(--vj-ink-muted);
}
.vj-pending p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--vj-ink-muted); }

/* A wireframe is a promise, and it should look like one. Dashed, monochrome,
 * labelled WIREFRAME, and deliberately without a single brand colour, so that
 * nobody — a visitor, a screenshot in a deck, us in three weeks — mistakes it
 * for the product. check-page-figures still reports the page as owing a real
 * screenshot; a placeholder satisfies the layout, not the requirement. */
.vj-fig .vj-wire {
  border-style: dashed;
  border-color: rgba(16,24,40,.20);
  box-shadow: none;
  background: transparent;
}

/* The global `a:hover{color:var(--vj-brand-hover)}` in the layout's base style applies to
 * every anchor, including one that is a solid blue button. The page-specific
 * .vjh-*:hover rules only change the background, so on hover a .vj-btn kept its
 * brand-blue ground and took brand-hover-blue TEXT — a label that disappears
 * into its own button at the moment a reader commits to clicking it.
 *
 * These restate the colour at hover so the anchor rule cannot reach them. The
 * ghost buttons are here too: they take their colour from a token that the same
 * global rule was overriding. */
.vj-btn:hover, .vj-btn-sm:hover { color: var(--vj-surface); }
.vj-btn-ghost:hover { color: var(--vj-ink); }
.vj-btn-ghost-brand:hover, .vj-btn-ghost-sm:hover { color: var(--vj-brand); }
.vj-chip:hover { color: var(--vj-ink-body); }
.vj-link-muted:hover { color: var(--vj-ink-muted); }

/* ---------------------------------------------------------------------------
 * TWO LEVELS, AND THE HOME PAGE ALREADY DECIDED THEM.
 *
 * The product pages had three treatments and no rule: some beats were white
 * cards with a shadow, some were flush, the tiles inside them were white with
 * shadows, and the CTA was a fourth thing. Vin, 2026-09-11: "I'm not sure I
 * follow the card structure." Nor could anyone, because there wasn't one.
 *
 * The home page has a rule and has had it all along. Seven sections, none with
 * a background, each a topic sitting on the ivory ground; and inside them, one
 * panel recipe — a 3% ink tint, a hairline, a radius, no shadow — used
 * identically for a three-up tile and for the closing call to action. Zero
 * white cards on the entire page.
 *
 * So the product pages adopt it rather than invent a second grammar. The
 * argument is the same argument at the scope of a product instead of a company,
 * and it should look like it.
 *
 *   SECTION   a topic. No background. The unit of the argument, and the thing
 *             that should ideally hold one screen without being forced to.
 *   PANEL     a group inside a section, or a section's whole content when that
 *             content is one object — the tension, the close. Tint, hairline,
 *             no shadow. A shadow says "floating above the page", which is a
 *             claim none of these blocks is making.
 * ------------------------------------------------------------------------- */
/* SURFACE COMES FROM vijil-ui, 2026-09-22. These were a flat 3% ink tint
 * with a hairline, and the note above argued against a shadow because no
 * block here claims to float. Glass does not claim it either: the shadow
 * is 6% at 24px, which reads as a pane resting ON the ground rather than
 * hovering above it, and the inner highlight is what makes it a pane at
 * all. The values are vijil-ui's own --glass-* tokens, so the site and
 * the console share one surface instead of two that merely agree about
 * color. Geometry - radius, padding, grid - is unchanged. */
.vj-panel-t {
  background: var(--glass-background, rgba(16,24,40,.03));
  backdrop-filter: var(--glass-backdrop-filter);
  -webkit-backdrop-filter: var(--glass-backdrop-filter);
  border: var(--glass-border, 1px solid rgba(16,24,40,.12));
  box-shadow: var(--glass-shadow), var(--glass-inner-shadow);
  border-radius: 12px;
  padding: 24px 22px;
  display: grid;
  gap: 10px;
  align-content: start;
}
/* The wide variant, for a section whose whole content is one object. */
.vj-panel-wide {
  background: var(--glass-background, rgba(16,24,40,.03));
  backdrop-filter: var(--glass-backdrop-filter);
  -webkit-backdrop-filter: var(--glass-backdrop-filter);
  border: var(--glass-border, 1px solid rgba(16,24,40,.12));
  box-shadow: var(--glass-shadow), var(--glass-inner-shadow);
  border-radius: 16px;
  padding: clamp(30px, 4vw, 56px) clamp(22px, 3.6vw, 48px);
}

/* ---------------------------------------------------------------------------
 * STRUCTURES THAT ARE NOT CARDS.
 *
 * /product/discover carried 23 one-sided green-rule cards in eight groups, and
 * those groups held five different KINDS of content: a binary opposition, an
 * audience selector, two accumulating lists, a pipeline, a comparison and an
 * ordered sequence. One costume on five shapes is what made them read as
 * over-present — the colour was the symptom.
 *
 * Each structure below takes its accent from --vj-accent-local, so the same
 * markup works on all four product pages by setting that once on the section.
 * ------------------------------------------------------------------------- */

/* ORDERED SEQUENCE. Three equal cards hide the ordering, which is the whole
 * information in a "1, 2, 3". The rail carries it instead. */
/* An <ol>, so the browser hands it 40px of padding for markers it never draws
 * (a grid <li> has no ::marker box) and 16px of margin above and below. Measured
 * 2026-09-17: the rail sat 40px right of every other structure's left edge. */
.vj-steps { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.vj-step {
  display: grid; grid-template-columns: 34px minmax(0, 1fr);
  gap: 18px; padding-bottom: 26px; position: relative;
}
.vj-step:last-child { padding-bottom: 0; }
.vj-step:not(:last-child)::before {
  content: ""; position: absolute; left: 16px; top: 38px; bottom: 0;
  width: 2px; background: var(--vj-hairline);
}
.vj-step-n {
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  font: 600 14px ui-monospace, Menlo, Consolas, monospace;
  color: var(--vj-accent-local, var(--vj-ink));
  background: var(--vj-surface);
  border: 2px solid var(--vj-accent-local, var(--vj-ink-muted));
}
.vj-step-h {
  margin: 6px 0 6px; font-size: 15px; font-weight: 600;
  letter-spacing: .02em; color: var(--vj-ink);
}

/* PIPELINE. Four parts with arrows between them, because scanners -> card ->
 * identity -> registry is a sequence and four disconnected boxes drop it. */
.vj-pipe { display: grid; gap: 0; }
.vj-pipe-item { display: grid; gap: 6px; padding: 0 0 20px; position: relative; }
.vj-pipe-item:not(:last-child)::after {
  content: ""; position: absolute; left: 7px; top: 22px; bottom: 2px;
  width: 2px; background: var(--vj-hairline);
}
.vj-pipe-h {
  display: flex; align-items: center; gap: 12px;
  font-size: 15px; font-weight: 600; color: var(--vj-ink);
}
.vj-pipe-dot {
  flex: none; width: 16px; height: 16px; border-radius: 50%;
  background: var(--vj-surface);
  border: 2px solid var(--vj-accent-local, var(--vj-ink-muted));
}
.vj-pipe-b { margin: 0 0 0 28px; font-size: 16px; line-height: 1.55; color: var(--vj-ink-body); }

/* ACCUMULATING LIST. Not three choices — three instances of one point. A rule
 * and a short tick weigh less than a card and stop the card fatigue. */
.vj-accrue { display: grid; gap: 22px; }
.vj-accrue-item { display: grid; gap: 6px; padding-top: 14px; border-top: 1px solid var(--vj-hairline); }
.vj-accrue-item::before {
  content: ""; display: block; width: 40px; height: 3px; margin: -14px 0 4px;
  background: var(--vj-accent-local, var(--vj-ink-muted));
}
.vj-accrue-h { font-size: 15px; font-weight: 600; color: var(--vj-ink); }
.vj-accrue-b { margin: 0; font-size: 16px; line-height: 1.55; color: var(--vj-ink-body); }

/* CARD TREATMENTS. What survives as a card on a product page is one of three
 * things, and the rule tells the reader which without being read:
 *
 *   .vj-card-peer   full-width top rule   peers you choose between (roles)
 *   .vj-card-yes / .vj-card-no            the two poles of a binary, blue and
 *                                         red, the same on every such pair so
 *                                         the convention is learned once
 *   .vj-quote-obj   left rule             an objection being quoted back
 *
 *   .vj-card-tier   short top segment    one option among priced options
 *
 * The tier treatment exists because "keep the plain panel" did not work:
 * measured, the role cards and the price cards came out a 3% tint apart and
 * read as the same object. Length is the axis that separates them — a full
 * banner for peers you choose between, a 56px marker for tiers. */
.vj-card-peer { border-top: 3px solid var(--vj-accent-local, var(--vj-ink-muted)); background: var(--vj-surface-alt); }
.vj-card-yes { border-top: 3px solid var(--vj-brand); }
.vj-card-no { border-top: 3px solid var(--vj-danger); }
.vj-card-yes > :first-child { color: var(--vj-brand); }
.vj-card-no > :first-child { color: var(--vj-danger); }

/* A quoted objection. The left rule says "this is someone talking", which is
 * what separates it from the page's own voice. */
.vj-quote-obj {
  border-left: 3px solid var(--vj-ink-subtle);
  padding-left: 18px; margin: 0 0 4px;
}

/* A price tier. Short segment, not a banner: the accent marks the card without
 * claiming the full width that says "these are the choices". */
.vj-card-tier { border-top: 1px solid var(--vj-hairline); position: relative; }
.vj-card-tier::before {
  content: ""; position: absolute; left: 0; top: -1px;
  width: 56px; height: 3px;
  background: var(--vj-accent-local, var(--vj-ink-muted));
  border-radius: 0 0 2px 0;
}

/* A RESULTS TABLE. Rows of findings are data, so they are a <table> and not a
 * drawing: selectable, readable by a screen reader, and responsive without a
 * second mobile variant to keep in sync. An SVG version of this failed
 * check-diagrams for exactly that reason, which was the gate being right. */
.vj-rows { width: 100%; border-collapse: collapse; font-size: 15px; }
.vj-rows th {
  text-align: left; padding: 0 14px 10px 0;
  font: 600 11px Futura, 'Jost', Arial, sans-serif; letter-spacing: .12em;
  text-transform: uppercase; color: var(--vj-ink-muted);
  border-bottom: 1px solid var(--vj-hairline);
}
.vj-rows td { padding: 12px 14px 12px 0; border-bottom: 1px solid #F0EDE6; vertical-align: top; }
.vj-rows tr:last-child td { border-bottom: 0; }
.vj-rows .vj-rows-id { font-family: ui-monospace, Menlo, Consolas, monospace; color: var(--vj-ink); }
.vj-rows .vj-rows-dim { color: var(--vj-ink-muted); }
.vj-rows-tag {
  display: inline-block; padding: 2px 10px; border-radius: 10px;
  font: 600 11px Futura, 'Jost', Arial, sans-serif; letter-spacing: .08em;
  border: 1px solid currentColor; white-space: nowrap;
}
.vj-rows-yes { color: var(--vj-brand); }
.vj-rows-no { color: var(--vj-danger); }
.vj-rows-gap { color: var(--vj-ink-subtle); border-style: dashed; }
.vj-rows-wrap { overflow-x: auto; }

/* A CITED OUTSIDE FACT. Distinct from every internal card treatment on purpose:
 * a full hairline box rather than a one-sided rule, because this is not one of
 * the page's own claims. The source line is part of the component, not
 * optional — a number without its method is the thing this page argues against.
 *
 * `.vj-sidenote-stat` carries the figure at 40px, which is on the type scale.
 * The review date is not decoration: an annual study ages into a wrong claim,
 * which is how VIJIL-1362's customer numbers got stale. */
.vj-sidenote {
  display: grid; gap: 10px; align-self: start;
  /* The page's own measure, standing alone. Beside prose the grid column caps
   * it and this does nothing.
   *
   * BORDER-BOX MATTERS HERE. The site does not set it globally, so max-width
   * was content-box and the card rendered 640 + 44 padding + 2 border = 686
   * outer — a width nothing else on the page has, which is what made it look
   * wrong below the tiers rather than beside them. Found by Fable's review. */
  box-sizing: border-box;
  max-width: 640px;
  padding: 22px; border: 1px solid var(--vj-hairline); border-radius: 12px;
  background: var(--vj-surface);
}
.vj-sidenote-eyebrow {
  font: 600 11px Futura, 'Jost', Arial, sans-serif; letter-spacing: .12em;
  text-transform: uppercase; color: var(--vj-ink-muted);
}
.vj-sidenote-stat {
  font: 500 40px/1.05 'Newsreader', Georgia, serif;
  letter-spacing: -0.015em; color: var(--vj-discover);
}
.vj-sidenote-b { margin: 0; font-size: 15px; line-height: 1.55; color: var(--vj-ink-body); }
.vj-sidenote-src {
  margin: 2px 0 0; padding-top: 10px; border-top: 1px solid var(--vj-hairline);
  font-size: 13px; line-height: 1.5; color: var(--vj-ink-muted);
}
/* The citation's link was var(--vj-ink-muted) — the SAME colour as the text
 * around it, with no underline, so the one actionable thing in the card was
 * invisible until hover. Colour alone was never going to carry it; the
 * underline is the cue that does not depend on seeing a hue. */
.vj-sidenote-src a { color: var(--vj-ink-body); text-decoration: underline; text-underline-offset: 2px; }
.vj-sidenote-src a:hover { color: var(--vj-brand); }

/* THE READING MEASURE APPLIES TO THESE TOO. Built 2026-09-16 with no max-width,
 * these ran the full 1160px section while every paragraph beside them capped at
 * 640 — measured at 1062 and 1160 against prose at 640, which is what Vin saw
 * as "misaligned with the rest of the page".
 *
 * The container takes 640 so headings share the left edge and the right edge of
 * the prose. The body takes 600, because check-measure caps a 16px line at 600
 * and a container of 640 would put a 16px paragraph over it. */
.vj-accrue, .vj-pipe, .vj-steps { max-width: 640px; }
.vj-accrue-b, .vj-pipe-b { max-width: 600px; }

/* THE NAV IS A BAND. Every page band is capped at 1160px and widens to
   min(1800px, 78vw) above 1600px (responsive.css), and its content therefore
   begins at the band's 32px padding edge. The nav row is 1160px centred with
   the same 32px padding, so below 1600px it already lines up — and above it,
   without this rule, the bands widened and the nav did not. At 2560px that left
   the logo 288px right of every headline on the page. The footer had exactly
   the same defect, from exactly the same cause, so both carry this class:
   the nav and the footer are the chrome, and the chrome agrees with itself.

   !important because the 1160px is an inline style, the same reason the other
   overrides in responsive.css carry it. Change the band width, change this. */
@media (min-width: 1600px) {
  .vj-chrome-row { max-width: min(1800px, 78vw) !important; }
}

/* And the same at the other end. Below 768px `.vj-band-gutter` narrows the
   band's side padding from 32px to 20px, which moved the content in and left
   the logo 12px out on its own. The nav row is not a band-gutter, so it needs
   the rule by name. */
@media (max-width: 767px) {
  .vj-chrome-row { padding-left: 20px !important; padding-right: 20px !important; }
}

/* The homepage puts its <footer> INSIDE the CTA band rather than after it, and
   that band already supplies the 32px gutter — so the chrome row's own padding
   double-counts and the footer lands 32px right of everything above it. Every
   other page has the footer as a sibling of the bands, where the padding is the
   only gutter there is and must stay. */
[data-label] > footer > .vj-chrome-row,
[data-beat] > footer > .vj-chrome-row {
  padding-left: 0 !important;
  padding-right: 0 !important;
}

/* THE SNAP CUE — see public/snap-cue.js for why only seven pages get one.
 *
 * Bottom right, over the snap container. Fixed rather than absolute because the
 * container scrolls beneath it and an absolutely-positioned cue would scroll
 * away with the first section, which is the one place it must not.
 *
 * The blink is the whole affordance: a static caret in a corner reads as an
 * icon, and an icon in a corner is furniture a reader stops seeing. Movement is
 * what says "you are not finished here". */
.vj-snap-cue {
  position: fixed;
  right: 28px;
  bottom: 24px;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 10px;
  border: 0;
  background: none;
  cursor: pointer;
  font: 500 15px/1 ui-monospace, Menlo, monospace;
  color: var(--vj-ink-muted);
  opacity: .85;
  transition: opacity .25s ease, transform .25s ease;
}
.vj-snap-cue:hover { opacity: 1; transform: translateY(2px); }
/* The caret carries the direction and the cursor carries the attention, so the
 * caret has to be the bigger of the two or the pair reads as a stray block with
 * a speck beside it. At the shared 15px the glyph rendered about half the
 * cursor's height. */
.vj-snap-cue-x { font-size: 21px; line-height: 1; }
.vj-snap-cue:focus-visible { outline: 2px solid var(--vj-brand); outline-offset: 2px; border-radius: 6px; }

/* The block cursor. Sized off the caret beside it rather than to a round
 * number, so the pair reads as one glyph and one prompt. */
.vj-snap-cue-b {
  /* Square, not a terminal caret's slab. At 8x15 it read as a thin bar beside
     the 21px chevron rather than as a cursor. */
  width: 15px;
  height: 15px;
  background: currentColor;
  animation: vjCue 1.15s step-end infinite;
}
@keyframes vjCue { 0%, 50% { opacity: 1 } 50.01%, 100% { opacity: 0 } }

/* Past the last section the cue would point at nothing. Kept in the DOM and
 * made inert rather than removed, because it comes back on scroll up. */
.vj-snap-cue-off { opacity: 0; pointer-events: none; }

/* A blinking element is a known migraine and vestibular trigger, and this one
 * blinks forever. Honour the setting: the cue still reads, it just holds. */
@media (prefers-reduced-motion: reduce) {
  .vj-snap-cue-b { animation: none }
  .vj-snap-cue:hover { transform: none }
}

/* The phone has a real scrollbar and far less dead space, so the cue is
 * solving a problem that is not there — and 28px from the right edge is inside
 * the thumb's reach on a control nobody asked for. */
@media (max-width: 767px) { .vj-snap-cue { display: none } }

/* The crumb row the layout renders above <main> on ordinary pages. It matches
 * the site's column so the crumb starts on the same left edge as the headline
 * below it — the alignment complaint that produced .vj-chrome-row, one row up. */
.vj-crumb-row {
  max-width: 1160px;
  margin: 0 auto;
  padding: 28px 32px 0;
  box-sizing: border-box;
}
@media (min-width: 1600px) { .vj-crumb-row { max-width: min(1800px, 78vw) } }
@media (max-width: 767px)  { .vj-crumb-row { padding: 20px 20px 0 } }

/* THE CRUMB'S LINKS, which do not inherit the nav's colour because
 * .vj-link-muted sets its own. Muted is var(--vj-ink-muted), and against the product
 * pages' ground that is 4.31:1 — under WCAG AA. check-rendered failed the
 * build on the word "Home" alone after the container was already corrected.
 *
 * Scoped to .vj-crumbs rather than fixing .vj-link-muted, because that class
 * is used elsewhere on lighter grounds where it passes, and widening a colour
 * change to those is a separate decision from making this crumb readable.
 * (0,1,1) beats the class's (0,1,0), so source order does not matter here. */
.vj-crumbs a { color: var(--vj-ink-body); }
.vj-crumbs a:hover { color: var(--vj-brand); }

/* PINNED CRUMB, for a section that centres its content vertically. pricing and
 * services do (align-content:safe center / align-items:center), so a crumb in
 * that flow rides down with the block and sat 250px+ below the navbar while the
 * product pages — which use align-items:flex-start — sat at 53. Taking it out
 * of flow gives the same 53 without abandoning the centring.
 *
 * left:32px matches those sections' own padding-left, so it lands on the same
 * edge as the headline below it.
 *
 * top is 122px, NOT 53px, and the difference is the nav. On these frames the
 * nav is position:absolute and the section starts at viewport 0, so an offset
 * measured from the section is 69px higher than the same offset on a page
 * where the section sits below the nav in flow. 122 = the nav's 69 + the 53
 * the product pages use. Measured at -16 before this, i.e. the crumb was
 * sitting above the navbar's lower edge. */
.vj-crumbs-pin { position: absolute; top: 122px; left: 32px; }
@media (max-width: 767px) { .vj-crumbs-pin { left: 20px; top: 104px; } }

/* THE CONTENT COLUMN, FOR PAGES THAT MUST TRACK THE CHROME.
 *
 * The nav and footer are 1160px and widen to min(1800px, 78vw) above 1600
 * (.vj-chrome-row, and .vj-band gives content sections the same rule). A page
 * whose column is a bare 1160 therefore agrees with the nav on a laptop and
 * drifts right of it on a wide monitor: /research measured 137px right of the
 * logo at 1920 and 288px at 2560, because its <main> never widens.
 *
 * This is the chrome's own rule under a name a content column can wear without
 * also inheriting .vj-band's padding, which would undo the 53px first-line
 * offset locked in elsewhere.
 *
 * NOT for /news, /blog or /whitepapers: those are deliberately narrow reading
 * measures, Vin's stated exception. */
/* box-sizing FIRST, and it is the load-bearing half. .vj-chrome-row is
 * border-box, so its 1160 INCLUDES the 32px gutter and its content starts at
 * (viewport-1160)/2 + 32. A content-box column with the same 1160 and the same
 * gutter is 1224 wide and starts its content 32px further left — measured on
 * /research at every width, laptop to 2560. Same defect .vj-imported's own
 * comment records two hundred lines up. */
.vj-col-chrome { box-sizing: border-box; }
@media (min-width: 1600px) {
  .vj-col-chrome { max-width: min(1800px, 78vw) !important; }
}

/* THE TEAM ROSTER, COLLAPSED BY DEFAULT. Fourteen people ran longer than the
 * six-person leadership block above it and pushed Investors and Advisors off
 * the screen.
 *
 * <details> rather than a script: it is the native disclosure control, so it
 * is keyboard operable and announced as expanded/collapsed without any ARIA of
 * ours, and it works before JavaScript loads — or if it never does. */
.vj-team-fold { margin: 0; }
.vj-team-summary {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  list-style: none;
  font: 500 16px Futura, 'Jost', Arial, sans-serif;
  color: var(--vj-brand);
  padding: 9px 0;
  user-select: none;
}
.vj-team-summary::-webkit-details-marker { display: none; }   /* Safari's own triangle */
/* Our own marker, so it can sit AFTER the label and rotate on open. */
.vj-team-summary::after {
  content: '';
  width: 7px; height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .2s ease;
}
.vj-team-fold[open] .vj-team-summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.vj-team-summary span {
  font: 500 13px ui-monospace, Menlo, monospace;
  color: var(--vj-ink-muted);
  border: 1px solid rgba(16, 24, 40, .16);
  border-radius: 999px;
  padding: 1px 8px;
}
.vj-team-summary:focus-visible { outline: 2px solid var(--vj-brand); outline-offset: 3px; border-radius: 4px; }
.vj-team-fold[open] > div { margin-top: 18px; }
@media (prefers-reduced-motion: reduce) { .vj-team-summary::after { transition: none } }

/* ===========================================================================
   SECTION ARCHETYPES

   WHY THESE EXIST. Measured across twenty beats on /product/diamond and
   /product/dome: the heading was 36px on NINETEEN of them, nineteen painted no
   background of their own, and eleven contained no image, SVG or canvas at all.
   Column count was the only thing that varied. So a product page was nine
   rectangles of identical type on identical ground, half of them with nothing to
   look at — which is the mechanism behind "the pages aren't interesting enough",
   and it is not something more words fix.

   A beat is assigned an archetype, and the archetype supplies the ground, the
   vertical rhythm and the column rule. Rhythm then comes from the assignment
   rather than from whoever styled the section last.

   THE FIVE:

     .vj-sec-open     the opening. Dark ground, display type. One per page.
     .vj-sec-turn     a pivot in the argument. Page ground, the larger heading
                      step. Three or four per page at most — a turn that happens
                      every section is not a turn.
     .vj-sec-cards    a grid of parallel things. Tinted ground.
     .vj-sec-split    a figure beside the prose that explains it. Page ground.
     .vj-sec-figure   one artifact or one number, given the width. Tinted ground.

   GROUND ALTERNATION FALLS OUT OF THE ASSIGNMENT. open is dark, cards and
   figure are tinted, turn and split sit on the page ground, so a page reads
   dark → light → tinted → light → tinted without anyone choosing per section.

   These replace `min-height:100vh` + `scroll-snap-align:start`. A beat with
   320px of argument was being given 1080px whether it wanted it or not, and no
   amount of styling fixes a panel that is mostly air. Sections now size to what
   they hold. Below 991px nothing here applies that did not apply before: the
   snap container was already released there, which is why the phone never had
   this problem.
   =========================================================================== */

/* FULL-BLEED BOX, CAPPED CONTENT. The archetypes paint grounds, so the section
   has to reach the viewport edges while its content stays on the measure. The
   usual way is an inner wrapper on every section; this does it with padding
   instead, so no markup changes and there is one box per beat rather than two.

   The inner width tracks responsive.css:917 deliberately — 1160px up to 1487,
   then 78vw, then a 1800px ceiling — so the band does not change when a page
   moves onto these classes. max(32px, ...) keeps the gutter when the viewport is
   narrower than the measure. */
.vj-sec {
  box-sizing: border-box;
  display: grid;
  align-content: start;
  gap: 24px;
  width: 100%;
  padding-block: 96px;
  padding-inline: max(32px, calc((100% - min(1800px, max(1160px, 78vw))) / 2));
}

/* The opening. Dark, because nineteen sections on one ground is the problem and
   the first screen is where a page earns the next one. --vj-ground-navy and
   --vj-ink-on-navy are an existing pair; the Lorenz components already ship a
   full palette against both. */
.vj-sec-open {
  background: var(--vj-ground-navy);
  color: var(--vj-ink-on-navy);
  padding-block: 148px 112px;
}
/* !important, for the reason responsive.css states about its own 132: an inline
   style outranks every selector, and these pages carry a colour on nearly every
   text node. Without it the heading renders --vj-ink on --vj-ground-navy, which
   is dark on dark and all but invisible — which is exactly what happened on the
   first build of this archetype. */
.vj-sec-open h1,
.vj-sec-open h2,
.vj-sec-open h3 { color: var(--vj-ink-on-navy) !important; }
.vj-sec-open p,
.vj-sec-open li,
.vj-sec-open dd { color: var(--vj-ink-on-navy) !important; opacity: .84; }
.vj-sec-open .vj-eyebrow,
.vj-sec-open .vj-sec-eyebrow { color: var(--vj-accent) !important; }
/* The crumb, the product name and any other loose label. Scoped to leaf-ish
   elements so a card inside the opening keeps its own colours. */
.vj-sec-open nav,
.vj-sec-open nav a,
.vj-sec-open nav span { color: var(--vj-ink-on-navy) !important; }
/* MATCHED ON THE INLINE VALUE, at any depth, because chasing nesting with
   `> div > div` missed the product name and would miss the next one too. The
   closing paren is part of the match, so --vj-ink-muted) and --vj-ink-body) do
   NOT collide with --vj-ink). CLAUDE.md warns that [style*=] hooks go silently
   dead when the style they match is deleted; here that failure is benign — the
   element then simply inherits .vj-sec-open's own colour, which is the right
   one. */
.vj-sec-open [style*="color:var(--vj-ink)"] { color: var(--vj-ink-on-navy) !important; }
.vj-sec-open [style*="color:var(--vj-ink-body)"],
.vj-sec-open [style*="color:var(--vj-ink-muted)"],
.vj-sec-open [style*="color:var(--vj-ink-subtle)"] { color: var(--vj-ink-on-navy) !important; opacity: .74; }
.vj-sec-open a:not(.vj-btn):not(.vj-btn-ghost) { color: var(--vj-accent) !important; }
/* A hairline drawn for a light ground disappears or glares on a dark one. */
.vj-sec-open [style*="border-top"],
.vj-sec-open [style*="border-bottom"] { border-color: rgba(255,255,255,.14) !important; }

/* THREE MORE WAYS A COLOUR REACHES THIS SECTION, all found by measuring the six
   pages converted onto these archetypes on 2026-10-04 rather than by reading
   them. Diamond was the only page on .vj-sec-open until then, and it happened to
   use none of the three.

   An eyebrow written inline instead of with .vj-sec-eyebrow. --vj-brand is
   #0247a9 and lands at 2.14:1 on this ground: "PRICING" on /pricing and "SIX
   WEEKS · ONE AGENT" on /services were both effectively invisible. Three
   elements across the seven pages, all eyebrows, so the flip is unambiguous —
   there is no reading on which brand blue on navy was intended.

   .vj-ink as a CLASS. The p/li/dd rule above sets ink-on-navy, but `b.vj-ink`
   inside an li carries more specificity and wins, so "Discover", "Diamond" and
   "Dome" rendered at 1.02:1 inside a sentence whose other words were legible.
   Ten elements.

   The ghost CTA. Line 1123 deliberately excludes .vj-btn-ghost from the link
   colour because a button keeps its own ground — but nothing then gave it one
   for navy, so "Talk to us" on the Adaptive Red Teaming opener was dark ink
   inside a dark-blue border. Same gap, and the same fix, as the nav carries for
   .vj-sec-open pages. The SOLID button is untouched on purpose: it paints
   --vj-brand under --vj-surface text and reads at 8.48:1 already. */
.vj-sec-open [style*="color:var(--vj-brand)"] { color: var(--vj-accent) !important; }
.vj-sec-open .vj-ink { color: var(--vj-ink-on-navy) !important; }
.vj-sec-open .vj-btn-ghost,
.vj-sec-open .vj-btn-ghost-sm {
  border-color: rgba(255, 255, 255, .34) !important;
  color: var(--vj-ink-on-navy) !important;
}

/* A CHILD THAT PAINTS ITS OWN LIGHT SURFACE KEEPS ITS OWN INK. Everything above
   assumes the navy ground reaches the text, and /services breaks that
   assumption: its opener carries a summary card on background:var(--vj-surface),
   so the rules flipped that card's text to #ebeef5 and left it on white. 1.1:1,
   invisible, and the measurement that missed it was comparing against the
   section's ground rather than the card's.

   Exactly one element across the seven pages does this — enumerated in the
   built output, not assumed — which is also why this is keyed on the
   declaration the card actually makes rather than on a new class. The
   descendant selectors carry one more class than the rules they undo, so they
   win without !important escalation beyond what is already here.

   This is the same defect as the nav dropdown on a .vj-sec-open page: a blanket
   colour inside a section reaching into a card that paints its own ground. */
.vj-sec-open [style*="background:var(--vj-surface)"] h1,
.vj-sec-open [style*="background:var(--vj-surface)"] h2,
.vj-sec-open [style*="background:var(--vj-surface)"] h3,
.vj-sec-open [style*="background:var(--vj-surface)"] b,
.vj-sec-open [style*="background:var(--vj-surface)"] .vj-ink { color: var(--vj-ink) !important; opacity: 1 !important; }
.vj-sec-open [style*="background:var(--vj-surface)"] p,
.vj-sec-open [style*="background:var(--vj-surface)"] li,
.vj-sec-open [style*="background:var(--vj-surface)"] dd,
.vj-sec-open [style*="background:var(--vj-surface)"] span,
.vj-sec-open [style*="background:var(--vj-surface)"] div { color: var(--vj-ink-body) !important; opacity: 1 !important; }
.vj-sec-open [style*="background:var(--vj-surface)"] [style*="color:var(--vj-brand)"] { color: var(--vj-brand) !important; }

/* A pivot. Nothing but the heading step and the room around it — a turn is
   signalled by scale, not by decoration. */
.vj-sec-turn { padding-block: 112px; }

/* Layout only. Ground is .vj-sec-tint, below, because two `cards` sections in a
   row should not automatically be two tinted bands. */
.vj-sec-cards { }
.vj-sec-split { }
.vj-sec-figure { }

/* The alternating ground, applied independently of the archetype. Alternation is
   a property of the SEQUENCE, not of any one section.

   WHITE, NOT IVORY. --vj-surface-alt is #f3f1ea, a warm ivory, and the page
   ground is a cool blue-grey gradient — so alternating between them read as two
   different colour schemes fighting rather than one page breathing. White is the
   same hue family as the ground, so the change registers as light and shade
   instead of as a second palette. Vin, 2026-10-03. */
.vj-sec-tint { background: var(--vj-surface); }

/* The eyebrow is the one piece of furniture every archetype shares, so it is
   defined once rather than inline on every section. */
.vj-sec-eyebrow {
  font: 500 13px ui-monospace, Menlo, Consolas, monospace;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* A section's own band. .vj-band carries the width curve (responsive.css:917,
   min(1800px,78vw) above 1600px); this is the inner column for archetypes whose
   content should not run the full band. */
.vj-sec > * { max-width: 100%; }

/* Two columns, figure beside prose, collapsing with everything else at 991px
   via the .vj-grid rule that already governs inline grid-template-columns. */
.vj-sec-split .vj-split-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px;
  align-items: center;
}

@media (max-width: 991px) {
  .vj-sec { padding-block: 64px; padding-inline: 20px; }
  .vj-sec-open { padding-block: 96px 72px; }
  .vj-sec-turn { padding-block: 72px; }
  .vj-sec-split .vj-split-pair { grid-template-columns: minmax(0, 1fr); gap: 32px; }
}

/* ---------------------------------------------------------------------------
   A PAGE THAT OPENS DARK OPENS ITS NAV DARK TOO

   The seam, before this: a 69px nav painting the page's light gradient, with a
   10%-black hairline under it, sitting directly on a navy hero. Light bar, hard
   edge, dark ground — and the hairline is drawn for the light side, so it reads
   on one half of the boundary and vanishes on the other.

   Moving the edge does not help. The nav's links are --vj-brand, which is 1.5:1
   on --vj-ground-navy, so any arrangement that puts the nav over the dark hero
   needs light nav content anyway. That is the same work /product/diamond's dark
   mode needs (WEB-18), which is why it is done here rather than deferred.

   SCOPED BY :has(), on the convention the archetype states: .vj-sec-open is the
   OPENING section and there is one per page. A page that put an open section in
   the middle would get a dark nav over a light first screen — so if that ever
   becomes a real layout, this needs an explicit opt-in on the frame instead.

   !important throughout, for the reason responsive.css gives about its own 132:
   the nav's ground, and every colour in it, is an inline style.
   --------------------------------------------------------------------------- */

:root:has(.vj-sec-open) nav:not([aria-label]) {
  background: var(--vj-ground-navy) !important;
  border-bottom-color: rgba(255, 255, 255, .10) !important;
}
:root:has(.vj-sec-open) nav:not([aria-label]) a,
:root:has(.vj-sec-open) nav:not([aria-label]) button,
:root:has(.vj-sec-open) nav:not([aria-label]) span,
:root:has(.vj-sec-open) nav:not([aria-label]) div { color: var(--vj-ink-on-navy) !important; }

/* THE DESKTOP DROPDOWN IS A LIGHT CARD AND KEEPS ITS OWN INK. The override above
   is a blanket on every a/button/span/div in the nav, and it reached inside the
   Product/Resources/Company panels, whose background is still var(--vj-surface).
   Measured on /product/diamond: menu items at 1.16:1, near-white on white, until
   hovered. Copilot, #248.

   Restored to the two tokens the panel uses on a light page — the link at
   --vj-ink-body and its label at --vj-ink-subtle — rather than to one flat
   colour, so the card reads identically whichever page opens it.

   THE MOBILE PANEL IS THE EXCEPTION AND IT IS GENUINELY DARK: responsive.css
   nests .vj-dd-m inside .vj-mobile-links there. That rule carries one more class
   than this one, so it wins on specificity and the light ink survives where it
   belongs. No :not() gymnastics, and nothing here depends on source order. */
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-dd-m a,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-dd-m button,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-dd-m div { color: var(--vj-ink-body) !important; }
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-dd-m span { color: var(--vj-ink-subtle) !important; }

:root:has(.vj-sec-open) nav:not([aria-label]) .vj-mobile-links .vj-dd-m a,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-mobile-links .vj-dd-m button,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-mobile-links .vj-dd-m span,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-mobile-links .vj-dd-m div { color: var(--vj-ink-on-navy) !important; }

/* The two CTAs keep their own grounds — they are buttons, not nav text. The
   ghost one needs a light edge to exist on navy at all.
   THE -sm VARIANTS ARE WHAT THE HEADER ACTUALLY RENDERS: the built page carries
   `vj-btn-sm` and `vj-btn-ghost-sm`, so naming only the base classes gave "Talk
   to us" none of this and left it with its low-contrast blue border on navy.
   Copilot, #248. */
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-btn,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-btn-sm { color: var(--vj-surface) !important; }
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-btn-ghost,
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-btn-ghost-sm {
  border-color: rgba(255, 255, 255, .34) !important;
  color: var(--vj-ink-on-navy) !important;
}

/* The mark swap. Both are in the markup; this chooses. */
.vj-logo-light { display: none; }
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-logo-dark { display: none !important; }
:root:has(.vj-sec-open) nav:not([aria-label]) .vj-logo-light { display: block !important; }

/* The mobile panel needs nothing here. It inherits the nav's dark ground and
   reads correctly at 390px — verified, not assumed. An earlier version of this
   block forced --vj-ink onto it, which would have put dark text on a dark panel
   had it won; it did not win, which is the only reason that was invisible. */
