/* Responsive layer for the design export — handoff.md gap #1.
 *
 * The export was authored at desktop width and carries ZERO @media rules
 * across 68 pages; every layout value is an inline style, which outranks any
 * stylesheet rule. Hence !important throughout, scoped to narrow viewports so
 * desktop rendering is untouched. Selectors match inline-style substrings
 * because the export has almost no classes to hang rules on.
 *
 * Breakpoints are Webflow's 991 / 767 / 479, the ones the live vijil.ai is
 * designed against, so this layer stages the same way the original does
 * rather than inventing its own.
 *
 * Reference measurements taken from the live site at a 390px viewport:
 * nav 75px tall (hamburger, one row), h1 52.76px, headline top 126px.
 */

/* ========================================================================
   Product diagram swap — desktop artwork vs. hand-laid-out mobile variant
   ========================================================================
   Six diagrams draw into a 1096px viewBox. Scaled into 350px their labels
   land near 4px, so they are redrawn rather than shrunk (one module per
   diagram in scripts/mobile-diagrams/). The converter wraps each desktop svg
   together with its variant in .vj-dia-swap; these two rules pick one.

   display:none rather than visibility or opacity, so the hidden one leaves
   the accessibility tree — otherwise a screen reader reads both aria-labels
   for the same figure. */
.vj-dia-swap > .vj-dia-mobile { display: none; }

/* lifecycle-graph is a custom element, so it needs its own half of the swap —
   `svg:not(.vj-dia-mobile)` below cannot match a non-svg sibling. */
.vj-dia-swap > lifecycle-graph { display: block; }

/* ========================================================================
   Hamburger nav — mirrors the live site's Webflow navbar (data-collapse=all)
   ======================================================================== */
.vj-burger { display: none; }

@media (max-width: 991px) {
  .vj-burger {
    display: flex !important;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;               /* 44px is the minimum comfortable tap target */
    height: 44px;
    padding: 0;
    margin-left: auto;
    border: 0;
    background: none;
    cursor: pointer;
    flex: none;
  }
  .vj-burger span {
    display: block;
    height: 2px;
    width: 24px;
    background: currentColor;  /* inherits the nav's ink, dark or light */
    transition: transform .18s ease, opacity .18s ease;
  }
  .vj-burger.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .vj-burger.is-open span:nth-child(2) { opacity: 0; }
  .vj-burger.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* The panel holds the real link row, moved rather than cloned. */
  .vj-mobile-panel {
    padding: 8px 20px 20px;
    border-top: 1px solid currentColor;
    border-color: color-mix(in srgb, currentColor 14%, transparent);
  }
  .vj-mobile-links {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 2px !important;
  }
  .vj-mobile-links > * { width: 100%; }
  .vj-mobile-links a { padding: 11px 0; display: block; }

  /* Desktop dropdowns open on :hover, which never fires on touch. Flatten
     them so every destination is visible in one scrollable list. */
  .vj-mobile-links .vj-dd { display: block !important; }
  .vj-mobile-links .vj-dd-m {
    display: block !important;
    position: static !important;
    padding-top: 0 !important;
  }
  .vj-mobile-links .vj-dd-m > div {
    background: none !important;
    border: 0 !important;
    box-shadow: none !important;
    padding: 0 0 0 14px !important;
    min-width: 0 !important;
  }
  /* The pill CTAs stay pills, sized to the panel. The export sets them
     content-box with 18-20px of horizontal padding, so width:100% became
     390 + 36 = 426px and they ran past the frame. border-box folds the
     padding back inside the width. */
  .vj-mobile-links a[style*="border-radius:999px"] {
    box-sizing: border-box !important;
    width: 100% !important;
    text-align: center;
    margin-top: 8px;
  }
  /* Same trap anywhere else a padded element is stretched to full width. */
  .vj-mobile-panel *, .vj-mobile-links * { box-sizing: border-box; }
}

/* ========================================================================
   Layout — everything below 991px
   ======================================================================== */
@media (max-width: 991px) {

  /* --- Page scrolling ----------------------------------------------------
   * The homepages put every panel inside ONE nested scroll container:
   *   <div data-snap="true" style="height:100vh;overflow-y:auto;
   *        scroll-snap-type:y mandatory">
   * On a phone that breaks scrolling outright: the document never scrolls so
   * the address bar never collapses, 100vh exceeds the visible area while
   * browser chrome shows so panel bottoms are unreachable, and mandatory
   * snapping fights touch momentum. Give the page back to the browser. */
  [data-snap="true"] {
    height: auto !important;
    max-height: none !important;
    overflow-y: visible !important;
    scroll-snap-type: none !important;
  }
  [style*="scroll-snap-align"] { scroll-snap-align: none !important; }

  /* Desktop snaps one full-viewport panel at a time. Mobile scrolls
   * continuously (the snap container is released above), so a panel no longer
   * needs to fill the screen — and pinning it to one leaves the short panels
   * padded out with blank space. Measured on the risk-owner homepage: the
   * Platform panel held 568px of content in an 844px box, and the page
   * carried 796px of slack in total.
   *
   * Panels size to their content instead. svh, not vh, wherever a viewport
   * height is still wanted: vh predates mobile browser chrome and ignores it. */
  [style*="height:100vh"] { height: auto !important; min-height: 0 !important; }
  [style*="min-height:100vh"] { min-height: 0 !important; }

  /* The hero is the exception — it should still fill the first screen. */
  [data-snap] > *:first-child,
  [data-snap] > *:first-child > [style*="scroll-snap-align"] {
    min-height: 100svh !important;
  }

  /* 120px/96px vertical padding is a desktop rhythm; on a phone it reads as a
   * gap rather than as breathing room. */
  [style*="padding:120px"] { padding-top: 56px !important; padding-bottom: 48px !important; }
  [style*="padding:96px"]  { padding-top: 48px !important; }
  [style*="padding-top:120px"] { padding-top: 56px !important; }
  [style*="padding-bottom:96px"] { padding-bottom: 48px !important; }

  /* --- Nav must not overlap the hero -------------------------------------
   * The homepages pin the nav absolute and compensate with padding-top:96px
   * sized for a one-row desktop nav. In flow, that padding double-counts. */
  nav[style*="position:absolute"], nav[style*="position: absolute"] {
    position: static !important;
  }

  /* The panels are `display:grid; align-content:center` inside min-height:100vh.
   * When the content is TALLER than the panel — which happens in portrait,
   * where a narrow column makes everything taller, but not in landscape — a
   * centred grid overflows equally in BOTH directions, so the top of one
   * panel rides up over the bottom of the previous one. That is the terminal
   * image appearing to occlude the subheading above it. Packing to the start
   * lets the panel grow downward instead. */
  [style*="align-content:center"], [style*="align-content: center"] {
    align-content: start !important;
  }
  [style*="scroll-snap-align"][style*="padding:96px"] { padding-top: 28px !important; }

  /* --- Grids -------------------------------------------------------------
   * Track is minmax(0,1fr), NOT 1fr: bare 1fr means minmax(auto,1fr), whose
   * floor is the content's min-content width, so a long code line still
   * forces the page wider than the screen. The 0 floor is what lets it
   * shrink. The second rule catches IMPLICIT grids that declare no columns at
   * all — one of those handed a 326px container a 964px track. */
  [style*="grid-template-columns"],
  [style*="display:grid"], [style*="display: grid"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  [style*="display:grid"] > *, [style*="display: grid"] > *,
  [style*="display:flex"] > *, [style*="display: flex"] > * { min-width: 0 !important; }

  /* --- Flow --------------------------------------------------------------- */
  [style*="display:flex"], [style*="display: flex"] { flex-wrap: wrap !important; }

  /* Terminal comments. The wrap rule above breaks a `$ command    # note` row
     onto two lines, and the orphaned note then reads as a comment on the NEXT
     command — "# in-agent guards" appearing to annotate the line below it. The
     command is the content; the note is decoration. Drop the decoration rather
     than let it lie. Matches only the dimmed, flex:none note spans inside the
     dark terminal blocks; every element this selects starts with "#". */
  [style*="color:rgba(230,237,245,.4)"][style*="flex:none"] { display: none !important; }

  /* Terminal commands. With the note hidden (above) a long command still
     ellipsises, so the reader loses the back half of the one line they came
     for -- "vijil protect my-agent --guards prompt_inj...". A command is not
     decoration; let the row scroll instead of truncating it. Scoped to the
     dark terminal block, not the small inline code chips that share the
     ground token. */
  [style*="border-radius:10px"][style*="--vj-ground-ink"] [style*="white-space:nowrap"] {
    overflow-x: auto !important;
  }
  [style*="border-radius:10px"][style*="--vj-ground-ink"] [style*="text-overflow:ellipsis"] {
    overflow: visible !important;
    text-overflow: clip !important;
  }

  /* Logo marquees are a width:max-content flex track translated by a keyframe
     animation inside an overflow:hidden window. Wrapping it, or capping it at
     max-width:100%, turns the scrolling row into a tall stacked column with
     72px gaps. Animated tracks and their immediate rows keep their own flow. */
  /* div[...] rather than [...] on purpose. The generic
     [style*="width:"] { max-width: 100% } rule below has the same specificity
     and comes later, so it was winning and capping this track at the column
     width — which squashed the two loop copies onto each other and made the
     logos appear to repeat and overlap. The element qualifier outranks it. */
  div[style*="animation:om-marq"], div[style*="animation: om-marq"],
  div[style*="animation:om-marq"] > *, div[style*="animation: om-marq"] > * {
    flex-wrap: nowrap !important;
    max-width: none !important;
    width: max-content !important;
    flex: none !important;
  }
  /* Keep each mark's own inline width. width:auto collapses a background-image
     div to zero (no content to size from), and the flex-children min-width:0
     rule above lets it shrink to nothing; flex:none pins it. */
  div[style*="animation:om-marq"] [role="img"] {
    flex: none !important;
    max-width: none !important;   /* keep the mark's own 130px, not 100% of the row */
  }

  /* Step sequences (Scan -> Fingerprint -> Attest) stack rather than wrap:
     a wrapped connector arrow points nowhere. */
  [style*="display:flex"][style*="align-items:stretch"],
  [style*="display: flex"][style*="align-items: stretch"] {
    flex-direction: column !important;
  }

  [style*="width:"] { max-width: 100% !important; }

  /* The export bakes a measured width AND height onto some elements — this
   * paragraph carries "width:964px; height:29px", the height of ONE line at
   * desktop width. Narrowing it reflows the text to three lines, but the
   * pinned height stays at one, so the text spills over whatever follows.
   * That is the portrait-only occlusion in "Plugs in to": in landscape the
   * column is wide enough that the text still fits its pinned height.
   *
   * Released on text blocks only. Of 56 elements with a pinned width+height,
   * exactly one overflows, and spans are left alone because several use a
   * pinned height as a shape (colour dots, badges) that auto would collapse. */
  p[style*="height:"], h1[style*="height:"], h2[style*="height:"],
  h3[style*="height:"], h4[style*="height:"], li[style*="height:"],
  blockquote[style*="height:"], figcaption[style*="height:"] {
    height: auto !important;
    min-height: 0 !important;
  }
  [style*="min-width:"] { min-width: 0 !important; }
  img, svg, video, canvas, iframe { max-width: 100% !important; height: auto; }

  /* clip, not hidden: hidden makes the element a scroll container and would
     resurrect the nested-scrolling bug that broke the homepage. */
  html, body { overflow-x: clip; }
}

/* ========================================================================
   Type and rhythm — tablet down
   ======================================================================== */
@media (max-width: 767px) {
  /* Scaled to land near the live site's 52.76px h1 at a 390px viewport
     (13.5vw ~= 52.7px there) rather than to an invented ladder. */
  [style*="font-size:72px"], [style*="font-size:76px"], [style*="font-size:80px"] {
    font-size: clamp(36px, 13.5vw, 60px) !important; line-height: 1.06 !important;
  }
  [style*="font-size:64px"], [style*="font-size:68px"] { font-size: clamp(34px, 12vw, 54px) !important; line-height: 1.08 !important; }
  [style*="font-size:56px"], [style*="font-size:60px"] { font-size: clamp(32px, 11vw, 48px) !important; line-height: 1.1 !important; }
  [style*="font-size:44px"], [style*="font-size:48px"] { font-size: clamp(28px, 8.5vw, 38px) !important; line-height: 1.15 !important; }
  [style*="font-size:40px"] { font-size: clamp(26px, 7.5vw, 34px) !important; line-height: 1.18 !important; }

  /* Hard line breaks are set for a desktop measure. On a phone they leave
     orphans mid-sentence ("systems that" alone on a line), so let text
     reflow. display:inline rather than display:none: hiding the <br> also
     removes the whitespace it stood for, which ran words together
     ("systems thatremain reliable"). The ::after restores the space.
     Verified in the browser against all three candidate techniques. */
  .vj-softbr::before { content: none !important; }

  /* nowrap belongs on nav items and pills, not on headings and body, where it
     forces horizontal overflow. */
  h1, h2, h3, h4, p, li, td, th, figcaption, blockquote { white-space: normal !important; }
  nav [style*="white-space:nowrap"], a[style*="border-radius:999px"] { white-space: nowrap !important; }

  [style*="padding:0 32px"] { padding-left: 20px !important; padding-right: 20px !important; }
  [style*="padding:96px 32px"] { padding-left: 20px !important; padding-right: 20px !important; }
}

/* ========================================================================
   Phone
   ======================================================================== */
@media (max-width: 767px) {
  /* ---- Logos: one size for every set ------------------------------------
   * Two different mechanisms carry logos. Customer marks are
   * <div role="img" aria-label="..."> with a background image, sized
   * 176x44 (and NEAR at 112x28, already inconsistent). Analyst and award
   * marks are <img> at 36-48px. Both are normalised to the same 34px so the
   * rows read as one system.
   *
   * The selector keys on the inline opacity every decorative logo carries and
   * the nav wordmark does not, so the wordmark keeps its size. */
  img[style*="opacity:.9"], img[style*="opacity: .9"],
  img[style*="opacity:.85"], img[style*="opacity: .85"],
  img[style*="opacity:.8"], img[style*="opacity: .8"] {
    width: auto !important;
    object-fit: contain;
  }

  /* Equal height is not equal presence. Measured intrinsic ratios:
   *   award badges      0.92 - 0.93  (square)
   *   investor marks    2.89 - 7.72  (wordmarks)
   *   customer marks    3.71 - 9.89  (wordmarks)
   * At a shared 34px the square badges were ~32px wide and the wordmarks
   * 126-336px — roughly four times the visual mass. Heights now move in
   * opposite directions to meet in the middle: wordmarks down to 28, square
   * badges up to 42. Filename is the discriminator because CSS cannot select
   * on aspect ratio, and the award- and inv- prefixes are stable names.
   * (Do not write a star-slash pair inside this comment: it closes it early
   * and silently turns the rules below into garbage.) */
  img[src*="award-"] { height: 48px !important; max-height: 48px !important; }
  img[src*="inv-"] {
    height: 22px !important; max-height: 22px !important;
    align-self: center !important;      /* Mayfield sat high against the others */
    vertical-align: middle !important;
    object-fit: contain;
  }
  /* Gradient's mark is 193x25 — ratio 7.7 against Mayfield's 2.9 — so at an
     equal height it runs 170px wide and dominates the row. Capping width
     brings its visual weight back in line; BrightMind is capped for the same
     reason, being the heaviest of the three by area. */
  img[src*="inv-gradient"]   { max-width: 112px !important; height: auto !important; }
  /* Mayfield's artwork sits high in its own PNG, so at a shared centre
     line its ink reads about 3px above the others. Nudge the box down
     by that much rather than changing its size. */
  img[src*="inv-mayfield"]   { position: relative !important; top: 3px !important; }
  img[src*="inv-brightmind"] { max-width: 100px !important; height: auto !important; }

  /* The row's 22px desktop gap pushed the third mark onto a second line,
     which read as one logo sitting higher than the others rather than as a
     wrap. Tighter gap keeps all three on one row. */
  [style*="display:flex"]:has(> img[src*="inv-"]) {
    gap: 12px !important;
    align-items: center !important;
    flex-wrap: nowrap !important;
  }

  /* Customer logos: a 2 x 3 grid rather than a wrapping flex row, which left
     ragged rows of two-then-one. :has() selects the row by what it contains,
     since the container itself has no class to target. */
  /* [style*="flex-wrap:wrap"] is the discriminator, and it matters: the
     framework marquee is ALSO a flex row of [role=img] marks, but sits inside
     an animated width:max-content track and does not wrap. Without this the
     grid rule collapsed the marquee and width:100% blew each mark up to the
     full row, leaving that section blank. */
  [style*="display:flex"][style*="flex-wrap:wrap"]:has(> [role="img"][aria-label]) {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 18px 16px !important;
    align-items: center !important;
    justify-items: stretch !important;
  }
  [style*="display:flex"][style*="flex-wrap:wrap"]:has(> [role="img"][aria-label]) > [role="img"][aria-label] {
    width: 100% !important;
  }
  /* Justify the grid: left column's artwork to the left margin, right
     column's to the right edge, with the slack falling between them. The box
     already fills its cell, so the alignment is done by where the background
     image sits inside it rather than by resizing anything. */
  [style*="display:flex"][style*="flex-wrap:wrap"]:has(> [role="img"][aria-label]) > [role="img"][aria-label]:nth-child(odd) {
    background-position: left center !important;
  }
  [style*="display:flex"][style*="flex-wrap:wrap"]:has(> [role="img"][aria-label]) > [role="img"][aria-label]:nth-child(even) {
    background-position: right center !important;
  }
  /* Height applies to every mark, marquee included, so both sets read as one
     system. Width is left alone outside the grid. */
  [role="img"][aria-label] {
    height: 22px !important;
    background-size: contain !important;
  }
  /* The NEAR mark is a PNG cropped tight to its glyphs, where the others are
     SVGs carrying their own internal whitespace. At an equal box height it
     therefore reads noticeably larger. Scaling the artwork inside the same box
     gives it matching optical padding without changing the grid. */
  [role="img"][aria-label="NEAR"] {
    background-size: auto 78% !important;
  }

  /* ---- Diagrams ---------------------------------------------------------
   * lorenz-loop and lifecycle-graph draw into a ~1044px viewBox. Scaled into
   * 350px their labels render at about 4px. The wrapper (added by the
   * converter) scrolls instead, so the diagram stays legible and swipeable. */
  .vj-diagram-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    max-width: 100% !important;   /* the 720px host must not widen the page */
    width: 100% !important;
  }
  /* The component sizes its own diagram from viewport HEIGHT:
   *   .vj-loop figure { max-width: min(1080px, max(420px,
   *                     calc((100vh - 400px) * 1.678))) }
   * On a phone that resolves to ~600px however wide the host is, so the stage
   * labels (spec, evolve, evaluate, ...) render at roughly 58% and vanish.
   * That rule is inside a shadow root, so it is fixed in the component's own
   * stylesheet (design/lorenz-loop.js) rather than from here. This wrapper
   * only provides the horizontal scroll. */
  /* No min-width here any more. Forcing the whole component to 1044px took
     its heading and lede off-screen with it, so the section opened scrolled
     past its own title. The component now scrolls only its <figure>
     internally (see design/lorenz-loop.js), leaving the prose at viewport
     width. This wrapper stays for lifecycle-graph, which has no such
     internal treatment. */
  .vj-diagram-scroll > * {
    display: block;
    max-width: 100% !important;
  }

  /* ---- CTA buttons ------------------------------------------------------
   * Centre any row of pill CTAs. Selected by content, as the row has no class. */
  [style*="display:flex"]:has(> a[style*="border-radius:999px"]) {
    justify-content: center !important;
  }

  /* An image set to width:112% deliberately overhangs its column on desktop;
     on a phone that is 2px of horizontal overflow and a crowded figure. */
  img[style*="width:112%"], div[style*="width:112%"] {
    width: 100% !important; max-width: 100% !important;
  }
}

@media (max-width: 479px) {
  /* Stack the CTA pills once a side-by-side pair would be cramped. */
  [style*="display:flex"]:has(> a[style*="border-radius:999px"]) {
    flex-direction: column !important;
    align-items: center !important;
  }
  a[style*="border-radius:999px"] {
    box-sizing: border-box !important;
    width: 100% !important;
    max-width: 320px !important;
    text-align: center !important;
  }

  [style*="padding:0 32px"] { padding-left: 16px !important; padding-right: 16px !important; }
  [style*="gap:88px"] { gap: 48px !important; }
  [style*="gap:64px"] { gap: 36px !important; }
  [style*="gap:56px"] { gap: 32px !important; }
}


/* Desktop behaviour of the converted <br> replacements: ::before injects the
 * newline the original <br> provided. The span itself holds a space, so the
 * mobile rule above (content:none) leaves words correctly separated. */
.vj-softbr::before { content: "\A"; white-space: pre; }

/* ========================================================================
   Product diagram swap — the breakpoint half
   ========================================================================
   Its own block at the end of the file for two reasons. The generic
   `img, svg, ... { max-width:100% !important }` rule in the 991 block would
   otherwise tie with the variant's sizing, and last-wins settles it. And an
   earlier attempt anchored these rules on a nearby comment that turned out to
   sit inside the 767 block, so the swap silently fired a breakpoint late —
   scripts/check-css-breakpoints.mjs now asserts the enclosure. */
@media (max-width: 991px) {
  /* The desktop svg is any direct svg child that is not the variant, so no
     class has to be injected into the export's own markup. */
  .vj-dia-swap > svg:not(.vj-dia-mobile) { display: none !important; }
  .vj-dia-swap > lifecycle-graph { display: none !important; }
  .vj-dia-swap > .vj-dia-mobile {
    display: block !important;
    width: 100% !important;
    max-width: 350px !important;
    height: auto !important;
    margin: 0 auto !important;
  }
}

/* ========================================================================
   Lifecycle variant — the chase around the loop
   ========================================================================
   Six chips sit on a rectangle. Each lights in turn, and the connector leaving
   it travels to the next, so the motion runs clockwise around the perimeter
   once every 7s. That IS the argument: the lifecycle is a loop, not a line.

   Keyframes live here rather than in a <style> inside the svg because the svg
   is injected into a .astro file, and Astro parses `{` as the start of a JS
   expression. Per-step delay rides in an inline style attribute, which carries
   no braces; the longhand properties below leave that delay alone (the
   `animation` shorthand would reset it to zero).

   Every connector carries pathLength="1", so one keyframe drives all six
   whatever their real lengths — there is no dash length to keep in step with
   the geometry, which the previous draft got wrong twice. */
@media (max-width: 991px) {
  .vj-lifecycle .vj-lc-ring,
  .vj-lifecycle .vj-lc-seg {
    animation-duration: 7s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }
  .vj-lifecycle .vj-lc-ring { animation-name: vjLcRing; }
  .vj-lifecycle .vj-lc-seg  { animation-name: vjLcSeg; }
}

/* A chip lights as the pulse reaches it, then hands off. Nothing ever dims:
   the highlight is carried by the ring appearing, not by fading the rest —
   a white chip is held by one #E2DED4 hairline and reads as missing, rather
   than as waiting, once it drops much below full strength. */
@keyframes vjLcRing {
  0%        { opacity: 0; }
  3%        { opacity: 1; }
  15%       { opacity: 0; }
  100%      { opacity: 0; }
}

/* The connector travels from the chip that just lit to the next one. It clears
   before the pulse comes round again, so only one segment is ever lit. */
@keyframes vjLcSeg {
  0%, 1%    { stroke-dashoffset: 1; opacity: 0; }
  3%        { opacity: 1; }
  15%       { stroke-dashoffset: 0; opacity: 1; }
  22%       { stroke-dashoffset: 0; opacity: 0; }
  23%, 100% { stroke-dashoffset: 1; opacity: 0; }
}

/* An animation that repeats forever is exactly what this setting is for. The
   diagram must still read as a loop without it, so the connectors rest fully
   drawn in their static grey — which they already are, underneath. */
@media (prefers-reduced-motion: reduce) {
  .vj-lifecycle .vj-lc-ring,
  .vj-lifecycle .vj-lc-seg { animation: none !important; }
  .vj-lifecycle .vj-lc-seg { opacity: 0 !important; }
  .vj-lifecycle .vj-lc-ring { opacity: 0 !important; }
}

/* ========================================================================
   Hero simulations — desktop only
   ========================================================================
   The simulations occupy a right-anchored wedge that clears the hero copy,
   which on desktop is a 640px column inside a 1096px content area: the copy
   ends near 812px and the wedge starts near 900px.

   On a phone that separation does not exist. The copy is full-width to ~370px
   and the wedge still begins near 210px, so the ant network ran straight
   through the headline, the CTAs and the customer logos. This is not a tuning
   problem — a right-side wedge cannot avoid full-width text.

   Hidden rather than narrowed. A strip thin enough to clear the copy is too
   thin to read as anything, and the modules skip mounting at this width, so
   a phone allocates no canvas and runs no loop. */
.vj-sim-band { display: none; }        /* desktop uses the wedge instead */

/* The wedge's left edge dissolves by construction — cells cannot be born past
   it, and reaction-diffusion additionally ramps its kill rate there. Its TOP
   is a hard horizontal cut where the canvas begins under the nav, so it gets a
   short fade.

   The bottom used to get none, on the grounds that all three simulations reach
   zero ink by the fourth viewport. True, and not enough: the taper shrinks the
   live band's WIDTH, not its brightness, so at the SECOND viewport the band is
   still 16-30% of the page at full strength — which put lit cells straight over
   the third card of the problem section. Zero-by-the-fourth is an endpoint; the
   brief was "more in the hero, less with each section below", which is a
   gradient. The stops below leave the hero (0-25%) exactly as it was and thin
   everything under it. */
/* The page-spanning simulation is absolutely positioned with z-index 0, and the
   hero is the only section that is itself positioned — so on every section BELOW
   the hero the canvas paints over in-flow content, and lit cells landed on the
   card text of the problem section. The sections carry no background, so lifting
   them costs nothing visually: cells still show through the gaps, they just stop
   washing text. The hero is excluded deliberately — the wedge belongs in front
   there, and its copy already sits at z-index 2. */
[data-snap] > section:not([data-label="Hero"]) { position: relative; z-index: 1; }

canvas[data-sim]:not(.vj-sim-band) {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 4%, #000 24%, rgba(0,0,0,.42) 52%, rgba(0,0,0,.16) 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 4%, #000 24%, rgba(0,0,0,.42) 52%, rgba(0,0,0,.16) 78%, transparent 100%);
}

@media (max-width: 991px) {
  /* The wedge is hidden; the band takes its place. */
  canvas[data-sim]:not(.vj-sim-band) { display: none !important; }
  /* Larger, full-bleed, and faded at every edge.
     Full-bleed via the 100vw + translateX trick, so the panel escapes the
     section's own side padding and meets the viewport instead of stopping at a
     ruled margin. Taller to match.
     The fade is a MASK rather than per-simulation alpha maths: two linear
     gradients intersected, which feathers all four edges of whatever is drawn.
     One rule covers Life, the ant colony and reaction-diffusion alike — the
     last of which is a solid field and was reading as a hard blue rectangle. */
  .vj-sim-band {
    display: block !important;
    width: 100vw !important;
    max-width: 100vw !important;
    margin-left: 50% !important;
    transform: translateX(-50%) !important;
    height: 260px !important;
    margin-top: 20px !important;
    margin-bottom: 2px !important;
    -webkit-mask-image:
      linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%),
      linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
    mask-image:
      linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%),
      linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
}

/* Beat containers on the five argument pages (transforms.mjs beatContainers).
 *
 * Vin's teammate asked for containers so the blocks stop "floating on the
 * background"; Vin picked this over full-bleed banding — "cleaner".
 *
 * DESKTOP ONLY, DELIBERATELY. Below 768px the sub-cards inside a beat stack
 * vertically, so this becomes a white box drawn 22px outside other white
 * boxes — a frame hugging a frame. It also eats 44px of a 390px viewport, and
 * <main> is already narrowed here by the rules above, so the bill is bigger
 * than it looks. A phone shows one section at a time, so the vertical gap
 * already says where the argument breaks.
 *
 * Measured on the built page at 390px, not assumed: 278px of text with the
 * container, 358px without. 29% of the measure, on the screen that can least
 * afford it. Breakpoint verified: off at 767px, on at 768px.
 *
 * No shadow, unlike the cta beat, which keeps its own. If every block gets the
 * full treatment the CTA stops being the loudest thing on the page and the page
 * loses its ending.
 */
/* Below the breakpoint the beat keeps whatever vertical padding its export
 * asked for. beatContainers moves an inline `padding` into this property
 * precisely so the container can win above 768px without destroying it: an
 * inline declaration outranks a stylesheet rule, and the proof beat's
 * `padding:56px 0` silently beat the container, painting a card whose content
 * sat flush against both edges. */
.vj-beat { padding: var(--vj-beat-pad, 0); }

@media (min-width: 768px) {
  .vj-beat {
    padding: clamp(22px, 3.2vw, 40px);
    background: var(--vj-surface);
    border: 1px solid rgba(16, 24, 40, .12);
    border-radius: 16px;
  }
}

/* The lifecycle mnemonic beside a product-page heading (transforms.mjs
 * heroLifecycleMini). DESKTOP ONLY, and every part of that is load-bearing:
 *
 *  - Below 992px the same six-step loop ALREADY appears further down the page,
 *    as the mobile variant of the `fits` beat's lifecycle-graph. Showing it in
 *    the hero too would print the same diagram twice on one phone screen.
 *  - The hero's inline grid collapses to one column at this breakpoint (the
 *    rule above, with !important), so a third column has nowhere to go anyway.
 *
 * The loop is LAST in the DOM and placed into column 1 here, so reading and
 * focus order stay breadcrumb -> heading -> copy -> diagram while the visual
 * order puts the mnemonic first. Explicit placement rather than `order`,
 * because the other two children must move deliberately, not by side effect. */
.vj-hero-loop { display: none; }
.vj-hero-loop-cap { margin-top: 10px; text-align: center;
  font: 500 10.5px ui-monospace, Menlo, Consolas, monospace;
  letter-spacing: .1em; text-transform: uppercase; color: var(--vj-ink-muted); }

@media (min-width: 992px) {
  .vj-hero-loop { display: block; grid-column: 1; grid-row: 1; align-self: center; }
  section.vj-hero-mini > :nth-child(1) { grid-column: 2; grid-row: 1; }
  section.vj-hero-mini > :nth-child(2) { grid-column: 3; grid-row: 1; }
}

/* Product tabs on the "where it fits" diagram (transforms.mjs fitsProductTabs,
 * behaviour in public/fits-tabs.js). Replaces the export's row of product
 * links, so it inherits that row's pill shape deliberately — a reader who has
 * seen those pills on another product page should recognise these. */
/* The strip sits ON the diagram, with a hairline between them, so the tabs and
 * the thing they switch read as one component. No enclosing border: the fits
 * beat is already inside a .vj-beat card and a second frame would be a box in a
 * box — the defect that took containers off phones. */
.vj-fits-frame { border-bottom: 1px solid rgba(16, 24, 40, .12);
  margin: 0 0 22px; padding-bottom: 12px; }
.vj-fits-tabs { display: flex; flex-wrap: wrap; gap: 10px;
  justify-content: center; }
.vj-fits-tab { display: inline-flex; align-items: center; gap: 8px;
  font: 400 14.5px Futura, 'Jost', Arial, sans-serif; padding: 7px 16px;
  border-radius: 999px; background: var(--vj-surface);
  border: 1px solid rgba(16, 24, 40, .12); color: var(--vj-ink-body);
  white-space: nowrap; cursor: pointer; text-decoration: none; }
.vj-fits-tab:hover { background: var(--vj-ground-ivory); }
/* The selected tab is marked by weight and a heavier border as well as by the
 * dot's colour. Selection must not rest on hue alone: Discover's #64748b sits
 * in the same family as the rules around it, which is the same reason the hero
 * mnemonic grows its lit chip instead of only tinting it. */
.vj-fits-tab[aria-selected="true"] { font-weight: 600; color: var(--vj-ink);
  border-color: rgba(16, 24, 40, .34); position: relative; }
/* The selected pill grows a foot to the hairline below the strip. That, not the
 * dot's hue, is what says WHICH panel this tab opens — and it survives
 * greyscale, which Discover's slate tint does not. */
.vj-fits-tab[aria-selected="true"]::after { content: ''; position: absolute;
  left: 50%; transform: translateX(-50%); top: 100%; width: 2px; height: 12px;
  background: rgba(16, 24, 40, .34); }
.vj-fits-tab:focus-visible { outline: 2px solid var(--vj-ink); outline-offset: 2px; }
.vj-fits-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.vj-fits-go { display: block; text-align: center; margin-top: 14px;
  font-size: 14.5px; color: var(--vj-brand); }
.vj-fits-go[hidden] { display: none; }

/* An inline SVG that would otherwise shrink its own text illegible
 * (transforms.mjs scrollUnreadableSvgs). Distinct from .vj-diagram-scroll,
 * whose child rule is `max-width:100% !important` — that squeezes the child to
 * fit, which is why CLAUDE.md records that hatch as inert. This one must be
 * allowed to overflow, so the child keeps a computed minimum width and the
 * container scrolls to it. --vj-svg-min is the width at which the diagram's
 * smallest label reaches 9px. */
.vj-svg-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain; max-width: 100%; }
.vj-svg-scroll > svg { min-width: var(--vj-svg-min, 0); display: block; }

/* A labelled pill must wrap, not collapse.
 *
 * Vin, 2026-09-09, with a screenshot: "the partner stack is mangled (on
 * mobile)". Measured on /partners at 390px: five sector pills — Banking,
 * Insurance, Healthcare, Legal, Hiring — were 19px wide each, their 15px
 * nowrap labels spilling straight out of them, while "Government" in the same
 * flex row kept its full 140px.
 *
 * The cause is the `[style*="display:flex"] > * { min-width: 0 !important }`
 * rule above. That exists to stop a long code line forcing the page wider, and
 * it does — but it also removes the min-content floor that makes a flex row
 * WRAP. With `flex:1` and no floor, six items in a 286px row do not wrap onto a
 * second line; five of them shrink to nothing so the sixth can keep its width.
 *
 * So a pill gets its floor back. Selected by the shape the export actually
 * ships — a flex child that is itself a centred flex box with a radius — not by
 * a class, because these carry none. 88px fits the longest label at 15px and
 * lets three sit per line at 390px.
 */
@media (max-width: 991px) {
  [style*="display:flex"] > [style*="flex:1"][style*="border-radius"][style*="justify-content:center"] {
    min-width: 88px !important;
    flex: 1 1 88px !important;
  }
}
