/* ==========================================================================
   aiRA — /product (20 Sep): the depth the homepage compresses. Hero with the
   real product capture, then the mechanism, the seven capabilities as their
   real /lp question strings, the category board, governance, and the book
   box. The mechanism, comparison and book-box sections are the shared blocks
   in components.css; this file carries the hero, the capabilities grid, and
   self-contained copies of the feat-icon and sec anatomies (those rules live
   fused with homepage-only selectors in home.css, so they are restated here
   the way industry.css restates the caption icons).

   Loads after tokens -> global -> components.
   ========================================================================== */

/* ---- Hero: ranged left, the capture bleeding off the right edge --------
   3 Oct, from databricks.com's own hero (user: "left aligned and shorter
   hero and subheader text, and product image on right but not fully
   visible"). Copy in the left column at a short H1 and a one-line sub, the
   product capture in the right column running PAST the page's right edge so
   it is cut rather than framed.

   WHAT THE REFERENCE ACTUALLY DOES, and what was taken: a terse headline
   with the explanation demoted to the sub, a panel anchored to the right
   that the window cuts, and a dot texture in the gap between the two. What
   was NOT taken is its two-button row: this site has one conversion button
   and /product already books from the hero and from the card at the foot.

   IT WAS CENTRED FROM 22 SEP (sendr.ai/platform/sequencer) with the capture
   full width underneath, and a left/right split for a few hours before that.
   Three things from the centred version are GONE rather than parked, because
   every one of them was solving a problem only a centred block has:
     - the H1's and the sub's fluid measures. A centred block pinches when
       its middle line is the shortest, and a flat px measure gives the H1 a
       band where it falls to two lines with the second longer than the
       first. Ranged left in a column of its own, the column IS the measure
       and a rag that is not centred has nothing to be symmetrical about.
     - text-wrap: balance on the sub, for the same reason. It is `pretty`
       now, which works on the last line rather than on the shape.
     - the word "still" in the headline, which existed to fix that rag.
   -------------------------------------------------------------------------- */

/* TWO COLUMNS, and the glyph field spans BOTH. The copy is in column one,
   the capture in column two, and the field runs under the pair: see the
   note on .phero__matrix for why it cannot live in the copy's column alone
   any more. align-items: center hangs the copy off the capture's middle,
   which is what the reference does and what stops a 380px copy block
   floating at the top of a 660px row. */
.phero .container {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1fr);
  column-gap: var(--sp-10);
  align-items: center;
}

/* THE HERO GIVES UP ITS BOTTOM PADDING at the widths where the split runs
   (user, 3 Oct: "cut 30% from bottom and start new section from right
   there"). The capture's cut edge IS the section's bottom edge now, so the
   joint and the section below begin exactly where the picture stops. The TOP
   padding stays: the hero still has to clear the floating header.
   It is scoped to the split, because below 1200 the capture is full width
   and uncropped and there is no cut for a boundary to land on. */
@media (min-width: 1200px) {
  .phero { padding-bottom: 0; }
}

/* THE HERO KEEPS BOTH RAILS. It lost its right one for part of 3 Oct, while
   the capture overshot the viewport and crossed it: the rail was hidden for
   the capture's height and visible above and below, which reads as a broken
   line rather than as bleed. The capture stops ON that rail now and has
   faded out before it reaches it, so there is nothing to hide and the rail
   runs the whole page again. Do not re-add the override. */

/* ---- The glyph field (22 Sep, user reference: sv-animations GlyphMatrix) --
   A canvas of small mono glyphs behind the copy, a few of which change every
   150ms: two thirds of the characters are middots and three cells in a
   hundred move per tick, so it registers as paper texture and only resolves
   into marks up close. See the colour note on the rule itself for how dark it
   ended up and why it had to.

   IT IS ANCHORED TO THE COPY, NOT THE SECTION, and that is what keeps it off
   the capture. The host is a child of .phero__copy and its box runs from the
   section's own top padding down to exactly the figure's top margin, both
   written as the same tokens those two rules use, so the field still lands on
   the figure's edge if either changes.

   The pitch is 16px because --cell is 80: see js/glyph-matrix.js, and the
   20 Sep hero lattice for what a pitch that does not divide the cell does to
   the page frame.

   Two masks, one per element, so neither has to composite: the host fades the
   field out downward before it reaches the capture, and the canvas clears an
   ellipse around the text. That ellipse is the same shape the home hero's
   lattice uses, which is what makes the two grounds read as one language. */
.phero__copy {
  grid-area: 1 / 1;
  /* NO max-width and NO auto margins. The grid column is the measure now:
     at 1440 it is 568px against the 800px cap this used to carry, so every
     one of those numbers was inert the moment the hero went to two columns,
     and an inert cap is a number the next person has to disprove. */
  width: 100%;
  /* Paints over the field: same cell, later in the DOM, and positioned. */
  position: relative;
}

.phero__matrix {
  /* SPANS BOTH COLUMNS since 3 Oct, where it used to share the copy's single
     cell. That is forced by the geometry, not a preference: the clear
     ellipse is 0.44 of 0.64 of the host's width, which is 349px on the old
     1240px full-width cell and matched the centred H1's own 350px half
     almost exactly. In a 568px left column the same arithmetic clears only
     160px against a headline 540px wide, so the glyphs would run straight
     through the words, and widening the clear to cover them leaves no field
     at all. Spanning the pair keeps the box at the container's width, so
     the ratio still resolves to a real number.
     IT RUNS UNDER THE CAPTURE and that costs nothing: the capture is opaque
     and positioned later in the DOM, so it covers the right half outright.
     What is left visible is the gap between the two columns and the air
     above and below the copy, which is where the reference puts its own
     dot texture. */
  grid-area: 1 / 1 / 2 / -1;
  /* Stretched to the cell, then pushed out top and bottom by exactly the two
     tokens the neighbouring rules use: up through the section's own top
     padding, and down to where .phero__fig's margin-top begins. Written as
     margins rather than as top/bottom insets because the field is a grid item
     now, not an absolutely positioned layer. It still lands on the figure's
     edge if either of those rules changes. */
  /* Up through the section's own top padding, and NOTHING at the foot: the
     hero's bottom padding is 0 at these widths, so a negative bottom margin
     would hang the field over the joint and into the section below. It used
     to be -sp-16, measured against the gap the capture then had under it. */
  margin-block: calc(-1 * var(--section-pad)) 0;
  /* No arithmetic: the frame's rails ride .container's outer edges and its
     20px inner padding is what keeps content clear of them, so a layer that
     simply fills .phero__copy already runs the width of the frame with that
     same 20px of air either side.

     It was a 50vw bleed with padding-inline: var(--frame-x) first, which put
     the field 18px OUTSIDE each rail: a percentage inside that token resolves
     against the containing block, .phero__copy, never against the bled box,
     so its 5% branch measured the container and its calc branch went
     negative. Do not reach for --frame-x from inside the container. */
  /* The INK itself, at 0.55. It started at --ink-3 and 0.34 and the user
     could not see it, twice (23 Sep, the second time asking in terms for a
     dark colour). The arithmetic is why: the script applies a per-cell alpha
     jitter on top of this, so the opacity here is not what lands. At --ink-3
     and 0.34 the average glyph was arriving near 21% of a mid grey on a
     near-white ground, which is under the threshold where a 12px stroke reads
     at all. Going dark rather than more opaque is what actually fixed it: a
     faint near-black glyph still has edges, a faint grey one has none. */
  color: var(--ink);
  /* The three hues of the site's own palette, in the client's own names:
     Tertiary, Secondary and Primary are one colour at three hues, 30 degrees
     either side of the brand (user, 23 Sep: "some colors teal and purple and
     brand/primary"). js/glyph-matrix.js reads these and gives a minority of
     cells one of them, so the field is ink with colour scattered through it
     rather than three fields of colour. They are the SATURATED mid steps on
     purpose: this layer already sits at 0.55 with a per-cell alpha jitter on
     top, and a pale accent under that arrives as nothing, which is the same
     lesson the field's own colour taught on 23 Sep. */
  --gm-c1: var(--tertiary-600);
  --gm-c2: var(--secondary-500);
  --gm-c3: var(--brand);
  opacity: 0.55;
  pointer-events: none;
  /* Fades in under the header and out again before the capture. Without the
     first stop the field starts on a hard horizontal edge level with the top
     of the nav card. */
  /* TWO MASK LAYERS, INTERSECTED, since 3 Oct. The vertical one is the
     original: it fades the field in under the header so it does not start
     on a hard horizontal edge, and out again before the section ends.
     THE HORIZONTAL ONE IS NEW AND IS NOT OPTIONAL. The capture now DISSOLVES
     at its right edge instead of being cut by the viewport, and the field
     spans both columns and sits behind it, so the moment the capture went
     translucent the glyphs started showing through it. A field read through
     a fading screenshot does not look like texture, it looks like dirt on
     the picture. The field now dies at 48%, which is where the capture's
     left edge falls (at 1440 the copy runs 0..575 of 1240 and the capture
     starts at 615, so 49.6%), and the ramp from 32% gives it somewhere to
     go rather than stopping on a line.
     THEY MUST INTERSECT, NOT ADD. mask-composite defaults to `add`, which
     is a UNION: the two layers would cover each other's gaps and the mask
     would be very nearly opaque everywhere, which is the same as having no
     mask at all. `intersect` is the one that means "both". */
  -webkit-mask-image:
    linear-gradient(to bottom, transparent 0, #000 10%, #000 84%, transparent 100%),
    linear-gradient(to right, #000 0, #000 32%, transparent 48%);
  mask-image:
    linear-gradient(to bottom, transparent 0, #000 10%, #000 84%, transparent 100%),
    linear-gradient(to right, #000 0, #000 32%, transparent 48%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
.phero__matrix .glyphs__canvas {
  /* THE CLEAR MOVED OFF CENTRE WITH THE COPY (3 Oct). It was an ellipse at
     50% 50% when the copy was centred in a full-width cell; the copy is in
     the left column now, so the hole has to be too, or it clears the gap
     and leaves the glyphs sitting on the headline.
     The numbers: the gradient's 100% is 46% of the host's width, so 570px
     on a 1240px container, and the clear runs to 0.62 of that, 354px,
     centred at 23% (x=285). That covers x=-69 to 639 against a copy column
     ending at 568, so the headline sits on open ground with the field
     starting in the gap. Vertically it clears 0.62 of 50%, which leaves
     texture above and below the copy rather than only beside it. */
  /* THE VERTICAL RADIUS IS AN ABSOLUTE LENGTH, NOT A PERCENTAGE, and that
     is the point of this rule rather than an implementation detail.
     The clear has to cover the COPY, whose height barely moves (about
     322px, so ±161 from its centre). As a percentage it was instead sized
     by the HOST, whose height is driven by the capture and has now changed
     three times in two days: 634px, then 805 when the frame took the left
     half at full height, then 644 when 30% came off the bottom. Every one
     of those moves re-broke the clear in one direction or the other, once
     leaving the field as a patch above the headline and once putting
     glyphs through the words.
     At a flat 320px the clear is 0.62 x 320 = 198px at EVERY width, which
     covers the copy's 161 with about two glyph cells to spare and does not
     move when the capture does. What scales instead is how much field is
     left above and below, which is the thing that SHOULD scale: 62px at
     1200 and 156px at 1920.
     The horizontal radius stays a percentage, because there the thing
     being cleared really is a fraction of the host. */
  --gm-clear: 0.62;
  -webkit-mask-image: radial-gradient(46% 320px at 23% 50%,
    transparent 0 calc(var(--gm-clear) * 100%), rgba(0, 0, 0, 0.45) 84%, #000 100%);
  mask-image: radial-gradient(46% 320px at 23% 50%,
    transparent 0 calc(var(--gm-clear) * 100%), rgba(0, 0, 0, 0.45) 84%, #000 100%);
}

/* The copy paints over the field by DOM order: the host is the first child of
   .phero__copy and these are positioned siblings after it, so they win
   without a z-index. A negative one on the host would have been the other
   way, and it escapes .phero__copy the moment .reveal leaves a stacking
   context behind. */
.phero__t,
.phero__sub,
.phero__cta { position: relative; }

.phero__t {
  font-family: var(--font-display);
  /* --t-display-0, the one hero size since 28 Sep. It was --t-display-1 (60)
     while the homepage sat at 70; with every non-utility hero on 64 there is
     one token and no argument about which page gets which. */
  font-size: var(--t-display-0);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: var(--tr-display);
  color: var(--ink);
  /* 800px at full size (user, 28 Sep, raised from the 700 set on 23 Sep), but
     FLUID BELOW IT, which is the part that cannot be dropped.
     A flat 800 puts the headline on TWO lines with the second longer than the
     first everywhere from about 768 to 1050: measured, 992 gives 614 / 768 and
     900 gives 585 / 732. That is an inverted taper in a centred block, and the
     band is wide enough to cover most laptops and every tablet. The long note
     at the top of this file describes exactly this failure at the old 700px
     measure, where the bad band was 640 to 880, and describes this fix: cap a
     measure that moves WITH the type rather than replacing it. It was not
     applied then only because a flat 700 was what had been asked for.
     12.5 is 800 / 64, so the cap resolves to exactly 800 at the clamp's 64px
     ceiling and the box shrinks in step with the font below it. */
  /* NO MEASURE AND NO AUTO MARGIN. The grid column is the measure, and the
     long apparatus this replaced (min(800px, --t-display-0 * 12.5), solved
     so the box shrank with the clamp) was fixing an inverted taper that only
     a CENTRED block can have. Ranged left there is no taper: a second line
     longer than the first is just a rag. */
  margin-bottom: var(--sp-5);
  text-wrap: balance;
}
/* The brand highlight, same drawing as .section-heading em: restated because
   this h1 is not a section heading. */
.phero__t em {
  font-style: normal;
  color: var(--primary-400);
  text-underline-offset: 0.16em;
}

.phero__sub {
  font-size: var(--t-body-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
  /* Capped a little inside the column so the sub is visibly the second
     object rather than a block as wide as the headline, which is what the
     reference does with its own one-liner. It replaces a 600px measure and
     an auto margin, both of which were centring apparatus.
     `pretty` rather than `balance`: balance evens the lines of a CENTRED
     block, and what a ranged-left sub needs is only not to end on an
     orphan. */
  max-width: 30rem;
  text-wrap: pretty;
}

.phero__cta {
  justify-content: flex-start;
  margin-top: var(--sp-8);
}

/* ---- The capture: right column, stopping on the 80rem rail -------------
   3 Oct, second pass (user: "the idea of hero product image is to show 50%
   of left side image and also show till 80rem max width of page. after that
   nothing is shown and towards the end of image show fade out white in to
   bg color"). The first pass ran the frame off the viewport entirely; this
   one ends it on the page's own right rail and dissolves it before it gets
   there.

   THE CROP IS EXACTLY HALF, and the arithmetic is the point rather than a
   tuned number. The frame takes aspect-ratio 1280/1598, which is the LEFT
   HALF of workspace.png (2560x1598); the <img> inside takes width: 200% and
   height: 100%, so its element box is 2W wide by W*1598/1280 tall, a ratio
   of 1.602, which IS the source's own ratio to three decimals. object-fit
   therefore neither crops nor scales anything: the image is drawn whole at
   its natural proportions in a box twice the frame's width, and the frame
   clips the right half of it. Change either number and they must change
   together, or the picture distorts.
   WHAT THAT BUYS, beyond doing as asked: the capture renders at about 0.50
   scale against the 0.39 the bleeding version managed, so the thread's UI
   type comes out near 12px instead of 9. A defined crop is sharper than an
   overshoot, because the overshoot sized itself off whatever was left.

   IT ENDS ON THE RAIL, not 20px inside it. width: calc(100% + --sp-5) is
   .container's own gutter given back, which is the same giveback the bento
   and the rollout tabs use and exists for exactly one reason: that 20px is
   there to clear the rails, so handing it back puts an edge ON one. 80rem
   is --container, and the container's border box is where the rail rides.
   THE HERO'S RIGHT RAIL IS BACK ON because of this. It was switched off in
   the first pass, where the capture crossed it and left a line that was
   hidden for the capture's height and visible above and below it. Nothing
   crosses it now: the image stops there and has faded out before it does,
   so the rail is continuous down the whole page again.

   NO BLEED MEANS NO --phero-bleed AND NO 1800 STEP. Both are gone, and so
   is the flat height they forced: the frame carries a ratio again, which is
   what the <iframe> swap wants anyway, since an iframe has no intrinsic
   size and would collapse a height:auto box. The note that used to sit here
   arguing for a height over a ratio was true only while the bleed existed.
   -------------------------------------------------------------------------- */
.phero__fig {
  grid-area: 1 / 2;
  position: relative;
  /* Above the frame's rails, which ride at z-index 1. The joint below the
     section is z-index 2 and still crosses in front, which is correct: it
     is the boundary line, not a rail. */
  z-index: 1;
  justify-self: start;
  width: calc(100% + var(--sp-5));
  /* 70% OF THE LEFT HALF'S HEIGHT, not all of it (user, 3 Oct: "databricks
     hero is very tight, we are showing full height of product image. lets
     only show top and cut 30% from bottom"). 1598 x 0.7 = 1118.6, so the
     frame is 1280 x 1118.6 where it was 1280 x 1598.
     THE TWO CROPS ARE INDEPENDENT AND BOTH STILL EXACT, which is the part
     that makes this a one-number change. The <img> is width: 200% of the
     frame, so its element box is now 2W x 0.874W, a ratio of 2.29 against
     the source's 1.602. object-fit: cover therefore scales by WIDTH, the
     horizontal crop stays exactly 50%, and the surplus height is taken off
     the BOTTOM because object-position is top left. Change the ratio and
     the crop percentage follows; the 200% is what holds the half. */
  aspect-ratio: 1280 / 1118.6;
  overflow: hidden;
  /* NO BOTTOM BORDER AND NO BOTTOM RADIUS. The frame's cut edge sits on the
     section boundary now, and the .joint under the section already draws a
     hairline there: a border as well would put two 1px lines in adjacent
     pixels, which is the same doubling the gapless-grid note warns about.
     Squaring the corners is the same argument made in shape, a rounded
     corner meeting a full-width rule reads as an object that failed to
     reach it. */
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--surface);
  /* THE DISSOLVE IS A MASK NOW, not the section overlay the first pass
     used. That overlay existed only because the frame overshot the viewport
     by a different amount at every width, so nothing measured from the
     frame's own box landed in the same place twice. The frame ends on the
     rail at every width now, so its own right edge is a fixed target and
     the mask can simply be a percentage of it. A mask is the better tool
     wherever it can reach: it needs no knowledge of the ground it fades
     into, where a gradient overlay has to be told, and would have to be
     retold if this section ever took a wash.
     THE RAMP IS LATE AND STEEP. Linear from 0 reads as a grey veil laid
     over the whole picture; holding full opacity to 62% and dropping from
     there reads as the image running out. The last 38% is the dissolve, so
     the solidly-visible crop is about the left 31% of the capture with the
     rest handing over to the page.
     IT TAKES THE BOX-SHADOW WITH IT, which is why there is no longer one.
     mask-clip defaults to border-box and an outer box-shadow paints outside
     that box, so the two cannot coexist: masking the figure would have
     clipped the shadow away entirely and left the lift half-drawn. It is
     the right answer anyway. An object dissolving into the page cannot cast
     a shadow onto it, because the shadow asserts exactly the edge the fade
     is there to remove. The hairline and the radius carry the frame on the
     three sides that still have one, which is how every other ruled object
     on this page is drawn. */
  -webkit-mask-image: linear-gradient(to right,
    #000 0%, #000 62%, rgba(0, 0, 0, 0.55) 82%, transparent 100%);
  mask-image: linear-gradient(to right,
    #000 0%, #000 62%, rgba(0, 0, 0, 0.55) 82%, transparent 100%);
}

/* THE CAPTURE SETTLES INTO PLACE ON SCROLL (user, 28 Sep). It starts tilted
   back and slightly small, and stands up to its resting position as it comes
   into view.

   IT IS A SCROLL-DRIVEN CSS ANIMATION, not a scroll listener. animation-timeline
   is bound to the element's own position in the viewport, so it runs on the
   compositor, cannot fall behind the scroll the way a rAF handler can, and
   costs no JavaScript at all.

   THE RESTING STATE IS THE ELEMENT'S NORMAL STATE and the animation only ever
   moves AWAY from it. That is what makes the degradation free: a browser
   without scroll-driven animations (Firefox still ships it behind a flag)
   simply shows the capture where it belongs, with nothing missing and nothing
   half-applied. The @supports guard is what stops the keyframes running as an
   ordinary time-based animation on load in those browsers.

   IT TRANSFORMS .phero__fig, NOT THE IMG. The img is inside overflow:hidden, so
   scaling it would only move the picture inside a frame that stayed put; the
   frame, its border, its radius and its two shadows all have to travel
   together for this to read as one object.

   PERSPECTIVE IS A TRANSFORM FUNCTION here rather than a property on the
   parent: .phero__fig's parent is the hero's grid, which also holds the copy
   and the glyph field, and giving that a 3D context would drag both into it.
   It has to appear in BOTH keyframes or the two transform lists do not
   interpolate and the rotation snaps.

   THE ORIGIN IS THE TOP EDGE, so the card hinges at the top and its lower half
   swings up. Origin centre reads as a card being pushed, top reads as one
   standing up, which is the gesture the reference sites use. */
/* THE SETTLE IS SCOPED TO THE STACKED WIDTHS (max-width: 1199px) since
   3 Oct, and it is not a demotion, it is where the effect still exists. A
   view() timeline runs on the element's travel through the viewport, and in
   the split layout the capture sits inside the first screen: it is already
   fully entered at load, so the animation holds progress 1 forever and never
   plays a frame (verified 3 Oct: getComputedTiming().progress was 1 at
   load). Below 1200 the capture is a full-width block lower on the page, it
   really does enter through the bottom edge, and the settle still earns its
   place. The LOAD entrance below owns the split widths instead, and the
   scoping is also what keeps the two off the same element at the same time:
   two animations on one `transform` do not add, the later one simply wins,
   and a `both` fill from either would permanently override the other. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (max-width: 1199px) {
    .phero__fig {
      /* TOP LEFT, not top centre. The card's left edge is the only one on
         screen, so a centred origin would slide that edge 35px inward
         during the scale and read as the picture drifting; hinged at its
         own left corner it only stands up. */
      transform-origin: 0 0;
      animation: phero-settle linear both;
      animation-timeline: view();
      /* Starts a little after the top edge appears and finishes well before
         the capture is fully covered, so the last of the movement happens
         while it is comfortably on screen rather than at the edge. */
      animation-range: entry 8% cover 32%;
    }
    @keyframes phero-settle {
      from {
        opacity: 0.5;
        transform: perspective(1600px) translateY(28px) rotateX(9deg) scale(0.93);
      }
      to {
        opacity: 1;
        transform: perspective(1600px) translateY(0) rotateX(0deg) scale(1);
      }
    }
  }
}

/* ---- The load entrance (3 Oct, user: "when product page is loaded,
   slide from bottom hero content and image") ------------------------------
   The copy and the capture rise out of the bottom as the page opens: 28px
   up with a fade, the capture a tenth of a second behind the copy so the
   two read as a sequence rather than a block-paste.

   IT IS A TIME-BASED CSS ANIMATION ON LOAD, not an observer, because an
   observer answers "when does this scroll into view" and the hero is in
   view the moment the document paints; and not a script, because unlike the
   removed headline sweep this effect's resting state needs no property a
   missing animation would strand (the keyframes only move AWAY from rest,
   `both` fill, same contract as every entrance on this site). No @supports
   guard is needed for the same reason the settle needs one: plain
   time-based animations run everywhere, it is only a scroll TIMELINE that
   can fall back to time and play on load by accident.

   THE CAPTURE'S RISE RUNS AT THE SPLIT WIDTHS ONLY (min-width: 1200px),
   because below that the scroll settle owns the capture and the two cannot
   share the element: both animate `transform` and `opacity`, animations do
   not compose per property, and whichever fills last would pin the other's
   properties forever. The COPY rises at every width, there being nothing
   else animating it anywhere.

   transform, not margin or top: it composites, costs no layout, and the
   copy's position: relative (which paints it over the glyph field) is
   untouched by it. The matrix host deliberately does NOT rise: a texture
   that arrives with the copy reads as part of the copy, where ground should
   already be there when the furniture lands on it. */
@media (prefers-reduced-motion: no-preference) {
  .phero__copy {
    animation: phero-rise 0.65s var(--ease) both;
  }
  @keyframes phero-rise {
    from { opacity: 0; transform: translateY(28px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}
@media (prefers-reduced-motion: no-preference) and (min-width: 1200px) {
  .phero__fig {
    animation: phero-rise 0.65s var(--ease) 0.12s both;
  }
}

/* width: 200% IS THE CROP. The element box comes out 2W x W*1598/1280, a
   ratio of 1.602, which is workspace.png's own, so object-fit has nothing
   to do: the picture is drawn whole and undistorted at twice the frame's
   width and the frame clips the left half of it. `cover` and `contain`
   would give the same result here, which is the tell that the box and the
   asset agree. This pairs with .phero__fig's aspect-ratio: 1280/1598 and
   neither can be changed alone. */
/* The capture sits in a <picture> (AVIF/WebP, 22 Oct), which takes no box
   of its own, so these rules reach the img through it. */
.phero__fig picture { display: contents; }
.phero__fig img,
.phero__fig > iframe {
  display: block;
  width: 200%;
  height: 100%;
  max-width: none;
  border: 0;
  object-fit: cover;
  object-position: top left;
}

/* ---- Shared anatomies restated (see the head comment) --------------------
   The feat icon-in-title grammar and the mini grid, exactly the home.css
   drawing, for the mechanism's three points. */

.feat__icon {
  display: block;
  margin-bottom: var(--sp-3);
  color: var(--brand);
}
.feat__icon svg { width: 20px; height: 20px; }
.feat__mini h4:has(> .feat__icon) {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.feat__mini h4 > .feat__icon { display: inline-flex; margin-bottom: 0; }
.section--inv .feat__icon { color: var(--primary-200); }

.feat__mini {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-10) var(--sp-8);
  border-top: 1px solid var(--border);
  padding-top: var(--sp-10);
}
.feat__mini > li { display: flex; flex-direction: column; }
.feat__mini h4 {
  font-size: var(--t-title-sm);
  font-weight: 600;
  letter-spacing: var(--tr-title);
  color: var(--ink);
  margin-bottom: var(--sp-2);
}
.feat__mini p {
  font-size: var(--t-body-sm);
  line-height: 1.65;
  color: var(--ink-3);
}

/* ==========================================================================
   THE DARK RUN: #how and #governance share one light
   --------------------------------------------------------------------------
   The two dark sections are meant to read as one block, so the light that
   lifts the mechanism's ground has to CROSS the boundary instead of dying on
   it (user, 23 Sep: "the how it fits and security sections color should
   connect or flow through"). It was dying on it, and the reason is worth
   keeping: .mech__glow is centred at 112% of #how's OWN height, which puts
   the bright middle of the ellipse below the seam where #how cannot draw it.
   So #how ramped up to its lightest exactly at the joint and #governance
   started flat, which is the one place a step reads as two panels.

   Both halves are written from the SEAM now, in the same lengths, so they
   are one ellipse that happens to be drawn twice: its centre sits --glow-y
   below the joint, #how draws everything above that point and #governance
   everything below it. Percentages cannot do this, because they resolve
   against each section's own height and the two are never equal (889 and 900
   here, and either moves the moment a line of copy wraps).

   This is a PAGE override, not a change to .mech__glow: that component also
   runs on index.html, index-v2 and index-v3, where the mechanism is followed
   by a LIGHT section and has nothing to flow into.
   -------------------------------------------------------------------------- */
.mech,
.sec {
  /* How far below the seam the light's centre sits. */
  --glow-y: 120px;
  --glow-h: 560px;
  /* The second, smaller ellipse: off to the left and a little lower, which
     is what keeps the wash from reading as one symmetrical blob. */
  --glow2-y: 160px;
  --glow2-h: 360px;
  --glow-1: color-mix(in srgb, var(--brand) 14%, transparent);
  --glow-2: color-mix(in srgb, var(--primary-300) 8%, transparent);
}
.mech .mech__glow {
  background:
    radial-gradient(80% var(--glow-h) at 50% calc(100% + var(--glow-y)),
      var(--glow-1), transparent 72%),
    radial-gradient(46% var(--glow2-h) at 16% calc(100% + var(--glow2-y)),
      var(--glow-2), transparent 75%);
}
/* The lower half rides #governance's own BACKGROUND rather than a decor
   element. #how can use an absolutely placed layer because .mech__diagram is
   positioned and paints over it; nothing in #governance is positioned, so the
   same layer would sit on top of the copy. A background-image cannot. */
.sec {
  background-image:
    radial-gradient(80% var(--glow-h) at 50% var(--glow-y),
      var(--glow-1), transparent 72%),
    radial-gradient(46% var(--glow2-h) at 16% var(--glow2-y),
      var(--glow-2), transparent 75%);
}

/* ==========================================================================
   SECURITY (#governance)
   --------------------------------------------------------------------------
   Rebuilt 23 Sep from a laravel.com/cloud reference: the claim with a checked
   list, then a row of certification marks drawn as emblems rather than set as
   a sentence.

   DARK, and continuous with #how by design. The cert row's dividers are the
   only structure; there are no cards, which is what lets the two dark
   sections read as one block rather than as two panels.

   THE HEAD IS THE SITE'S OWN, not the reference's. The reference opens on a
   ruled strip with its label on the left and a line of engineering detail
   opposite; that was built here and came straight back out, because every
   other section on the site is eyebrow over heading and a section that
   invents its own head reads as imported. The mono line it carried is the
   first checked item below almost verbatim, so nothing went with it.

   The cert row BLEEDS to the rails, the same 20px giveback the bento uses:
   .container pads that much purely to clear the frame, so handing it back
   puts the row's rules on the frame's own verticals.
   ========================================================================== */

.sec__list {
  list-style: none;
  padding: 0;
  /* .section-head owns the gap above; the list starts at the body's top. */
  margin: 0;
  display: grid;
  gap: var(--sp-5);
  max-width: 54rem;
}
.sec__list li {
  display: flex;
  gap: var(--sp-3);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink-inv-2);
}
.sec__list svg {
  flex: none;
  width: 20px;
  height: 20px;
  /* Optically on the first line rather than its box: the 20px glyph is taller
     than the 14px text's cap height. */
  margin-top: 3px;
  color: var(--primary-200);
}

/* Primary is --black, which disappears on this ground. The inverse of "the
   primary button is ink" is a light fill with ink on it, and it is the one
   element in the section that should carry full contrast. */
.section--inv .btn--primary {
  background: var(--ink-inv);
  color: var(--black);
  box-shadow: none;
}
.section--inv .btn--primary:hover {
  background: color-mix(in srgb, var(--ink-inv) 88%, var(--black));
}

/* ---- The certification row ----------------------------------------------
   Emblems, not a sentence. Six cells divided by hairlines, running rail to
   rail under the copy, each one a mark plus a wordmark stack.

   WHAT IS IN HERE IS A SUBSET ON PURPOSE. Only the claims held as
   certifications or compliance get an emblem; HIPAA-capable-under-a-BAA and
   the two AI-framework ALIGNMENTS keep their qualifier in .sec__note, because
   a badge strips one and would overstate all three.
   -------------------------------------------------------------------------- */
.sec__certs {
  list-style: none;
  padding: 0;
  margin: var(--sp-16) 0 0;
  margin-inline: calc(-1 * var(--sp-5));
  display: grid;
  /* THREE, not six, since 23 Sep: only the marks Capillary supplied are drawn,
     and the rest are carried in .sec__note. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-block: 1px solid var(--frame-line-inv);
}
.cert {
  position: relative;
  display: grid;
  place-items: center;
  /* Mark over label, with the pair centred as one object. */
  gap: var(--sp-4);
  align-content: center;
  min-height: 140px;
  padding: var(--sp-6) var(--sp-3);
  color: var(--ink-inv-3);
}
.cert + .cert { border-left: 1px solid var(--frame-line-inv); }

/* A SUPPLIED MARK takes the whole cell: the artwork carries its own wordmark,
   so the drawn emblem and the .cert__stack under it would both be saying it
   twice.

   SIZED BY HEIGHT, not width, and not to a box. The three are 200x200, 182x177
   and 158x200, so a common width would render the tall shield small and the
   circles large. A common height gets them closer, and the shield still takes
   a step down because a shape that is taller than it is wide reads bigger at
   the same height than a circle does; 84 against 92 is that correction, and it
   is optical, so do not "fix" it back to one number.

   They are FULL-COLOUR marks on a dark ground, which is the one place this
   site lets a logo keep its own colour: the rule that customer logos render in
   full colour applies here too, and a compliance mark recoloured to fit a
   palette stops being the mark. */
.cert__logo {
  display: block;
  width: auto;
  height: 76px;
}
.cert:last-child .cert__logo { height: 70px; }
/* The mark says what it is, the label says what it MEANS: two of the three
   are graphics rather than wordmarks, and the AICPA circle names the auditor
   rather than the standard. It is the eyebrow's own spec, so the row's type
   is the section's type. */
.cert__name {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--ink-inv-3);
  text-align: center;
}


.sec__note {
  margin-top: var(--sp-6);
  font-size: var(--t-caption);
  line-height: var(--lh-body);
  color: var(--ink-inv-3);
  max-width: 44rem;
}

/* ==========================================================================
   THE WORKSPACE (#workspace): five cells, 2 / 1 / 2
   --------------------------------------------------------------------------
   Built 23 Sep from the firecrawl.dev/search reference the user supplied. It
   replaces two full-width sections, #ask and the old #features, clubbed on
   the user's call: they were the same subject read twice, once as what
   happens when you ask and once as what lands when it answers.

   EVERY ILLUSTRATION WENT with them, also the user's call, and that is most
   of what this block used to be: the drawn .thread, the .pbook registry
   panel, the four .work__fig surfaces, the .mark annotation, the accordion
   and its synced well. The reference carries no pictures in its cells either
   beyond a small glyph, and five claims that each say a specific checkable
   thing do not need illustrating.

   IT IS DRAWN IN LINES, NOT CARDS (user, 23 Sep, second pass on the same
   reference: "no padding between grid, needs to follow our lines structure").
   There is no gap, no fill, no radius and no shadow. The cells simply share
   hairlines, and the grid is pulled back out to the RAILS so its own edges
   sit on the frame's verticals rather than 20px inside them. That is the
   whole reason .container carries a 20px gutter, so undoing it is what makes
   this grid part of the page frame instead of a thing floating within it.

   The MIDDLE CELL IS WIDE because it is the load-bearing one: "it builds you
   something" is the section's argument and the four artifact kinds are its
   evidence. The two above are what happens when you ask, the two below are
   how you check what came back, so the block reads ask / build / verify.
   ========================================================================== */

/* The head shares the GRID'S internal edge, not .container's. Once the grid
   bleeds out to the rails, the section's outer edge is the frame, and the
   24px the cells pad in from it is the section's real left margin. Left on
   .container's 20px the head's text sat at x=100 against the first cell's
   104, which is an almost-alignment and reads worse than a frank inset. */
/* ONE padding for the block, so the head and the cells cannot drift apart.
   They had: the head was on --sp-6 and the cells on --sp-12, which put the
   heading's text 24px inside the rail and the cell copy 48px inside it. The
   head is given the same giveback as the grid and then pushed back in by the
   cells' own pad, so the two read as one left edge. */
/* WHITE GROUND (user, 3 Oct: "give section what it does #fffffff bg
   colour"). --surface is #FFFFFF in tokens.css, which is the only file
   allowed a raw colour value, so the hex is never written here.
   IT IS A REAL DIFFERENCE AND NOT A NO-OP, which is worth saying because
   the page ground is already near-white: --bg is #FBFBFD, so this lifts the
   section by about 4/255. That is the same separation .book__card--split
   relies on and it reads, because the bento is drawn entirely in hairlines
   with no fills of its own, so the one thing marking the band is its ground.
   THIS IS NOT .section--wash. That modifier came OFF this section on 23 Sep,
   when the cells lost their card fills: a TINTED band fights a hairline grid.
   White does not; it reads as paper under the lines rather than as a panel
   behind them.
   It follows the dark theme on its own, --surface being #10141F there. */
.bento {
  /* 20px since 3 Oct (was 32): petavue's outcomes cards run 20-22px of
     copy inset, and the head-alignment formula below reads this token, so
     one change moves both and they cannot drift. */
  --cell-pad: var(--sp-5);
  background: var(--surface);
}

.bento__head {
  margin-inline: calc(-1 * var(--sp-5));
  padding-inline: var(--cell-pad);
  max-width: calc(46rem + var(--sp-5) + var(--cell-pad));
  margin-bottom: var(--sp-12);
}
.bento__sub { margin-top: var(--sp-5); }

/* ---- The grid: petavue's outcomes bento (3 Oct) --------------------------
   (user: "check outcomes section of dev.petavue-website.pages.dev, i need
   that bento grid for our what it does section"). Petavue is the BASE EXPORT
   this architecture came from, and this is the first time a piece of its
   VISUAL language has been taken on purpose: read from its live CSS, the
   grid is six columns with a 10px gap, flat filled square cards (radius 0),
   the ART ON TOP over the copy, and (until 3 Oct) a border that lit on
   hover, which the user took off. That
   REVERSES the 23 Sep firecrawl pass deliberately: the gapless hairline
   grid, the subgrid band-alignment, the copy-first order and the
   bleed-to-close-the-cell are all gone with the user's call, and are in git.
   PETAVUE'S OWN DEAL since the second 3 Oct pass (user: "2 column and 3
   column grid"): two halves over three thirds. The move no longer needs a
   full row because it is the aiRA path alone now, one straight run that
   fits a half card.
   The grid keeps the 20px rail giveback, so with the cards' own 20px copy
   inset the card text sits at exactly the head's x. */
/* A 10-COLUMN GRID since 4 Oct (user, from a reference measured at 4+6
   over 3+4+3): One thread 4, The move 6 (the wide card), then business
   context 3, its working 4, domain 3. The 4-wide card in the second row is
   exactly as wide as One thread above it, so the rows interlock rather than
   lining up on halves and thirds. Spans are classes in the markup
   (--4, --6; 3 is the default), so the deal reads off the HTML. */
.bento__grid {
  display: grid;
  grid-template-columns: repeat(10, minmax(0, 1fr));
  gap: 10px;
  margin-inline: calc(-1 * var(--sp-5));
}

.bento__cell {
  grid-column: span 3;
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  border: 1px solid var(--border);
  /* Square, like the rollout tabs: radius is for buttons and pills here. */
  border-radius: 0;
  padding: 0 var(--cell-pad) var(--sp-6);
}
.bento__cell--4 { grid-column: span 4; }
.bento__cell--6 { grid-column: span 6; }
/* SUBGRID ROWS (4 Oct): each card takes two of the grid's rows, art and
   copy, so every card in a row shares the same two tracks. With unequal
   widths the narrow cards wrap their line to three where the wide one takes
   two, and as flex columns each card placed its own copy, so the titles sat
   up to 24px apart across a row. Sharing the tracks puts every title on one
   line. Where subgrid is unsupported the declaration is dropped and each
   card simply rows itself, which is the old behaviour. */
@supports (grid-template-rows: subgrid) {
  .bento__cell {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
    row-gap: 0;
  }
}

/* ICON + LABEL IS THE TITLE since 3 Oct (user: "only keep like icon + h3,
   so use label copy on bento__t and remove label ... H3 to use Primary
   color"). The mono label and the sentence-long claim it sat over are gone;
   the h3 carries the label's words with its icon in front, in the brand,
   and one lean line under it does the explaining. 16/500 overrides both
   halves of the universal h3 (28/600), which that rule's note allows. */
.bento__t {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-size: var(--t-title-sm);
  font-weight: 500;
  line-height: var(--lh-title);
  letter-spacing: var(--tr-title);
  color: var(--brand);
}
.bento__t svg { width: 20px; height: 20px; flex: none; }
.bento__p {
  margin: var(--sp-2) 0 0;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
  max-width: 48ch;
}

/* ---- The figure slot -----------------------------------------------------
   Where the illustration goes, drawn as LINES ONLY (user, 23 Sep, with a
   crop of the reference): one line inset from each of the well's four edges,
   each running the full width or height of the well so they cross at the
   corners and carry on to meet the cell. That is
   the "double lines top bottom and left right" the reference shows, and it
   is the page frame's own grammar rather than a box drawn inside a box.

   The insets are 80 and 40, which are --cell and half of it: the frame's
   graph paper is pitched at 80, so these lines land where that grid would
   have put them instead of cutting across it. The lines take --frame-grid,
   not --frame-line: the cell's own borders are structure and these are
   texture, and at the same weight the well reads as four nested boxes.

   It bleeds out of the cell's padding on three sides, so its own edges meet
   the cell's borders. It carries NO RULE along its top (user, 23 Sep:
   "remove the separator line between text and image and also reduce that
   gap"): the copy and the well are one cell, and a hairline between them
   split it into two stacked blocks inside a grid already drawn in hairlines.
   With the line gone the gap is the only separation left, so it dropped from
   40 to 24, under the cell's own 32px inset.

   IT IS A RESERVED SLOT, not a decoration: it carries the site's own
   placeholder glyph (the Phosphor duotone Image at --ink-faint, the same one
   /solutions' seat shots and the industry hero use), and dropping an <img>
   in beside that glyph fills the well and stands the glyph down, with the
   lines and the well's size unchanged.

   ITS SHAPE IS A RATIO, NOT A HEIGHT (user, 23 Sep: "make it a bit square in
   height"). It was a flat 200px, which on a 640px cell is a 3.2:1 letterbox
   that no real screenshot would fit. 3/2 tracks the cell as the grid narrows
   instead of going flatter and flatter, and the cap is there for the WIDE
   cell, which is twice as wide and would otherwise want an 850px well.
   -------------------------------------------------------------------------- */
.bento__fig {
  /* 80 and 40: --cell and half of it. */
  --fig-x: 80px;
  --fig-y: 40px;
  /* The bleed is one variable feeding both the margin and the width, and it
     has to feed the width: a grid item that is stretched in BOTH axes and
     carries an aspect-ratio lets the ratio win, so the well was sizing its
     own width back off the capped height and every one of them came out 630
     wide whatever its cell. Stating the inline size makes it definite, the
     ratio then only drives the height, and max-height clamps that height
     without touching the width. */
  --fig-bleed: var(--cell-pad);
  display: grid;
  place-items: center;
  width: calc(100% + 2 * var(--fig-bleed));
  /* ART ON TOP, COPY UNDER IT since 3 Oct, petavue's card anatomy; order
     rather than moved markup, so a screen reader still gets the claim
     before the picture. A FIXED 272px, petavue's own art height, instead of
     the old 3/2 ratio capped at 420. LARGER since the lean-copy pass (user:
     "reduce content and make illustration larger"): 300 in a third, 360 in
     a half, the space the cut copy gave back. The drawings' zoom steps are
     keyed to the well's height and width, so they pick their step on
     their own. */
  order: -1;
  /* HEIGHT HUGS THE DRAWING since 3 Oct (user: "empty space below ...
     lot noticeable on mobile"). Fixed 300/360 panes held drawings whose
     zoom moves with width, which left 5 to 135px of dead ground under them.
     It grows (flex) to the taller partner in a desktop row, so copy still
     starts on one line across a row; alone in a column it is exact. */
  height: auto;
  flex: 1 0 auto;
  margin: 0 calc(-1 * var(--fig-bleed)) var(--sp-5);
  background-image:
    linear-gradient(var(--frame-grid) 0 0),
    linear-gradient(var(--frame-grid) 0 0),
    linear-gradient(var(--frame-grid) 0 0),
    linear-gradient(var(--frame-grid) 0 0);
  background-size: 100% 1px, 100% 1px, 1px 100%, 1px 100%;
  background-position:
    0 var(--fig-y),          0 calc(100% - var(--fig-y)),
    var(--fig-x) 0,          calc(100% - var(--fig-x)) 0;
  background-repeat: no-repeat;
}

/* Once a real shot lands it fills the well and the drawn lines stand down
   with the glyph: they are the empty state, not a frame around the art.
   DIRECT CHILD ONLY, all three rules, since 3 Oct: the branch diagram's
   pills carry brand <img>s (the aiRA mark and wordmark), and the old
   descendant selectors read those as "a real shot landed", stretched a 16px
   mark to fill the well and stood the dot ground down. A drawn illustration
   is allowed to contain images; only an image dropped DIRECTLY in the well
   is the media this rule is about. */
.bento__fig:has(> img) {
  background-image: none;
}

.bento__fig > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- Drawn art in a well ------------------------------------------------
   From contexts/UI examples (user, 23 Sep): a product fragment floating on a
   dot ground, soft shadow, sometimes a second card stacked behind. Art stands
   the drawn inset lines down exactly as a screenshot does, since they are the
   empty state, and lays a dot field in their place. The dots are pitched at
   16px, which divides --cell (80px), so they register with the frame. */
.bento__fig--art {
  position: relative;
  overflow: hidden;
  background-image: none;
  /* INLINE-SIZE, not size: the pane is sized BY its drawing now, and size
     containment would collapse it to zero. So every zoom step below is
     keyed to WIDTH; height queries would never match. */
  container-type: inline-size;
  padding-bottom: var(--sp-8);
  /* EVERY ILLUSTRATION FADES OUT DOWNWARD since 3 Oct (user, from a
     workflow-card reference: "each bento image illustration needs fade out
     gradient on them from bottom"). A MASK, not a gradient overlay: it
     needs no knowledge of the card's ground, and it fades the dot field
     with the drawing so the two dissolve together. Full strength to 55%,
     gone at the pane's bottom edge, where the copy starts. */
  -webkit-mask-image: linear-gradient(to bottom, #000 66%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 66%, transparent 100%);
  /* ANCHORED TO THE TOP, as the reference's card is: what fades is each
     drawing's tail, never its middle. Centred, the fade ate the move's
     approval state and the agenda's payoff row. */
  place-items: start center;
  padding-top: var(--sp-6);
}
/* Drawings sit ABOVE the dot layer: it is absolutely placed, so it would
   otherwise paint over any drawing that is not positioned itself. */
.bento__fig--art > * { position: relative; }
.bento__fig--art::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, var(--border-strong) 1px, transparent 1.4px);
  background-size: 16px 16px;
  background-position: 8px 8px;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, #000 30%, transparent 100%);
  mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, #000 30%, transparent 100%);
}

/* ---- One thread: the question and its answer, nothing between ---------- */
.oth {
  position: relative;
  width: 380px;
  margin: 84px 0 0 48px;      /* room for the ticket peeking out above-left */
  font-family: var(--font-product);
}
/* The ticket the same question becomes without aiRA: faded, tilted, and
   behind, so it reads as the route not taken rather than as a second
   feature. */
.oth__ticket {
  position: absolute;
  top: -76px;
  left: -64px;
  width: 272px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  opacity: 0.6;
  transform: rotate(-4deg);
}
.oth__ticket p { margin: 0; }
.oth__ticket-h,
.oth__ticket-f {
  display: flex;
  align-items: center;
  white-space: nowrap;
  justify-content: space-between;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--ink-faint);
}
.oth__ticket-t {
  margin: 6px 0 var(--sp-3) !important;
  font-size: var(--t-caption);
  font-weight: 500;
  color: var(--ink-3);
}
.oth__queued {
  padding: 0 8px;
  border-radius: var(--r-pill);
  color: var(--hold);
  background: var(--hold-bg);
}

/* NO CARD since 3 Oct (user: "show this message bubble as floaty, not in a
   box", with a chat reference): the exchange floats on the dot ground as two
   bubbles, each lifted on its own soft shadow. The wrapper only stacks them. */
.oth__card {
  position: relative;
  display: grid;
  gap: var(--sp-4);
  text-align: left;
}
/* The person: a WHITE bubble off the right edge (user, 3 Oct: "user reply
   is always in white"); it was brand-filled for a turn.
   The sides are set by which edge each bubble sits on, so neither carries a
   "You" chip or an aiRA mark (both came off 23 Sep). */
.oth__q {
  display: flex;
  justify-content: flex-end;
}
.oth__q .chat__q {
  margin: 0;
  max-width: 80%;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  color: var(--ink);
  box-shadow: var(--shadow-md);
}
/* aiRA: a white bubble off the left edge, answering with the work and the
   artifact rather than a sentence. */
.oth__a {
  display: grid;
  gap: var(--sp-3);
  width: 88%;
  padding: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}
.oth__a .chat__tool { margin: 0; }
.oth__kpi {
  flex: 1;
  display: grid;
  gap: 4px;
  /* No border of its own: inside a bubble a second box is a box in a box. */
  padding: var(--sp-1) var(--sp-2) var(--sp-1);
}
.oth__kpi-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
}
.oth__spark { width: 120px; height: 32px; flex: none; }
.oth__spark-a { fill: var(--brand); fill-opacity: 0.1; }
.oth__spark-l {
  fill: none;
  stroke: var(--brand);
  stroke-width: 2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.oth__kpi-s { font-size: var(--t-caption); color: var(--ink-3); }

/* ---- Your business context: an agenda card (3 Oct) ---------------------
   From a checklist reference (user: "do something like this for Your
   business context"): a pale shell with the title and a + in its head, a
   white panel inset inside it, done rows on a grey pill, ticked green and
   struck through, open rows on an empty ring, the day set right. Product
   face, like the One thread bubbles beside it. The old dated mono log and
   the tilted semantic-layer card behind it are in git. */
.odata {
  position: relative;
  width: 380px;
  padding: 6px;
  /* The shell is one slate step DARKER than the bento card it sits on
     (slate-200 on slate-100): at the card's own grey it vanished into it.
     The done rows take the card's grey, a step lighter, on the white
     panel. */
  background: var(--slate-200);
  border: 1px solid var(--border-strong);
  border-radius: 22px;
  box-shadow: var(--shadow-md);
  font-family: var(--font-product);
  text-align: left;
}
:where(.odata) p, :where(.odata) ol { margin: 0; padding: 0; list-style: none; }
.odata__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px 14px;
  font-size: var(--t-body);
  font-weight: 600;
  color: var(--ink);
}
.odata__plus {
  width: 16px;
  height: 16px;
  stroke: var(--ink-3);
  stroke-width: 1.5;
  stroke-linecap: round;
  fill: none;
}
.odata__rows {
  display: grid;
  gap: 4px;
  padding: 8px;
  background: var(--surface);
  border-radius: 16px;
}
/* Every row is a toggle since 3 Oct (user: "these need to be interactive,
   so i can check uncheck any i like, with smooth animation"). The button
   carries the row; js/checklist.js flips .is-done on the li and
   aria-checked on the button. Each part of the state animates on its own:
   the ground fades in, the ring fills green, the tick DRAWS, the strike
   RUNS left to right across the words, and the ring pops once. */
.odata__item {
  width: 100%;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  font: inherit;
  font-size: var(--t-body-sm);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background-color 0.25s var(--ease);
}
.odata__item:hover { background: color-mix(in srgb, var(--surface-2) 55%, transparent); }
.odata__item:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.odata__d { font-size: var(--t-caption); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.odata__ck { width: 18px; height: 18px; overflow: visible; }
.odata__ck circle {
  fill: transparent;
  stroke: var(--border-strong);
  stroke-width: 1.2;
  transition: fill 0.25s var(--ease), stroke 0.25s var(--ease);
}
.odata__ck path {
  fill: none;
  stroke: var(--approve);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 12;
  stroke-dashoffset: 12;
  transition: stroke-dashoffset 0.3s var(--ease) 0.08s;
}
/* The strike is a 1px background line that grows, not text-decoration,
   which cannot animate its extent. */
.odata__t {
  justify-self: start;
  background: linear-gradient(var(--ink-faint), var(--ink-faint)) no-repeat 0 55% / 0% 1px;
  transition: background-size 0.35s var(--ease), color 0.25s var(--ease);
}
/* Done: a grey ground, the ring green, the tick drawn, the words struck. */
.odata__rows .is-done .odata__item { background: var(--surface-2); }
.odata__rows .is-done .odata__ck circle { stroke: var(--approve); fill: var(--approve-bg); }
.odata__rows .is-done .odata__ck path { stroke-dashoffset: 0; }
.odata__rows .is-done .odata__t { color: var(--ink-3); background-size: 100% 1px; }
/* The pop plays only on a click (the script adds .is-pop), never on load. */
.odata__rows .is-pop .odata__ck { animation: odata-pop 0.36s var(--ease); }
@keyframes odata-pop {
  40% { transform: scale(1.22); }
  to  { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .odata__item, .odata__ck circle, .odata__ck path, .odata__t { transition: none; }
  .odata__rows .is-pop .odata__ck { animation: none; }
}

/* ---- The move: a fan-out (3 Oct) ----------------------------------------
   From a primary-to-replicas sync diagram (user: "design these like image
   attached"). The question card feeds the aiRA hub on three parallel wires,
   and the hub branches on curves to the three things it builds: Answer,
   Segment, Journey, each in one of the palette's three anchors (Tertiary,
   Primary, Secondary), carried by its icon tile and by the dashes running
   its wire. No approval badge since 3 Oct (user); the card's copy says it.
   LAID OUT AT ONE SIZE, 560x280, and zoomed to the well, the pattern the
   other drawings use, so the SVG wires and the HTML cards share one set of
   pixel coordinates and cannot drift. Below 470 of container the zoom would
   take the type under 10px, so it stands up as a plain list instead.
   NO MONO ANYWHERE IN THIS DIAGRAM (user, 3 Oct). */
.omove {
  position: relative;
  width: 560px;
  height: 280px;
  text-align: left;
}
:where(.omove) p, :where(.omove) ol { margin: 0; padding: 0; list-style: none; }

.omove__step--t { --acc: var(--tertiary-600);  --acc-bg: var(--tertiary-50); }
.omove__step--p { --acc: var(--brand);         --acc-bg: var(--primary-50); }
.omove__step--s { --acc: var(--secondary-500); --acc-bg: var(--secondary-50); }

/* Wires: a grey base, then each branch's dashes in its accent. */
.omove__wires { position: absolute; inset: 0; width: 100%; height: 100%; }
.omove__w { stroke: var(--border-strong); stroke-width: 1.5; }
.omove__dash { stroke-width: 1.5; stroke-dasharray: 6 26; stroke-linecap: round; }
.omove__dash--t { stroke: var(--tertiary-600); }
.omove__dash--p { stroke: var(--brand); }
.omove__dash--s { stroke: var(--secondary-500); }
/* The dashes travel toward the outputs. At rest (reduced motion) they
   simply stand on the wire, which is how the reference draws them anyway. */
@media (prefers-reduced-motion: no-preference) {
  .omove__dash { animation: omove-flow 1.4s linear infinite; }
  .omove__dash--p { animation-delay: -0.45s; }
  .omove__dash--s { animation-delay: -0.9s; }
}
@keyframes omove-flow { to { stroke-dashoffset: -32; } }

/* Shared card skin: white, hairline, 16px corners, soft lift. */
.omove__src,
.omove__step,
.omove__hub {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-md);
}

/* The source: the question, the reference's "Primary · Write" card. */
.omove__src {
  position: absolute;
  left: 0;
  top: 50%;
  translate: 0 -50%;
  width: 172px;
  display: grid;
  gap: 4px;
  padding: 14px 16px 16px;
  border-radius: var(--r-lg);
}
.omove__src b { font-size: var(--t-body); font-weight: 600; line-height: 1.35; color: var(--ink); }

/* The hub: aiRA, a white disc on the junction. */
.omove__hub {
  position: absolute;
  left: 210px;
  top: 110px;
  width: 60px;
  height: 60px;
  display: grid;
  place-items: center;
  border-radius: 50%;
}
.omove__hub img { width: 26px; height: 26px; }

/* The three outputs, stacked on the right edge where the wires land. */
.omove__steps {
  position: absolute;
  left: 360px;
  right: 0;
  top: 0;
  height: 280px;
}
.omove__step {
  position: absolute;
  left: 0;
  right: 0;
  height: 60px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px;
  border-radius: var(--r-lg);
}
.omove__step:nth-child(1) { top: 20px; }
.omove__step:nth-child(2) { top: 110px; }
.omove__step:nth-child(3) { top: 200px; }
.omove__ic {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 10px;
  background: var(--acc-bg);
  border: 1px solid color-mix(in srgb, var(--acc) 22%, transparent);
  color: var(--acc);
}
.omove__ic svg { width: 20px; height: 20px; }
.omove__tx { display: grid; gap: 1px; min-width: 0; }
.omove__tx b { font-size: var(--t-body); font-weight: 500; color: var(--ink); }
.omove__d { font-size: var(--t-caption); color: var(--ink-3); white-space: nowrap; }


/* Zoomed to the well by width; the drawing never re-lays itself out. */
@container (min-width: 600px) { .omove { zoom: 1.02; } }
@container (max-width: 599px) { .omove { zoom: 0.92; } }
@container (max-width: 519px) { .omove { zoom: 0.82; } }

/* Below 470 a zoom would take the type under 10px, so the fan-out stands
   up as a list: question, the three outputs, the approval. */
@container (max-width: 469px) {
  .omove {
    zoom: 1;
    width: min(320px, 100% - var(--sp-8));
    height: auto;
    display: grid;
    gap: var(--sp-2);
  }
  .omove__wires, .omove__hub { display: none; }
  .omove__src, .omove__steps, .omove__step {
    position: static;
    translate: none;
  }
  .omove__src { width: auto; padding: 10px 14px; margin-bottom: var(--sp-1); }
  .omove__src b { font-size: var(--t-body-sm); }
  .omove__steps { display: grid; gap: var(--sp-2); height: auto; }
  /* One-line rows, label left and output right, so the list fits the
     well's fixed height: two-line cards ran 45px past it at 390. */
  .omove__step { height: 44px; gap: 10px; padding: 0 12px; }
  .omove__ic { width: 26px; height: 26px; border-radius: 8px; }
  .omove__ic svg { width: 16px; height: 16px; }
  .omove__tx { flex: 1; display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
  .omove__tx b { font-size: var(--t-body-sm); }
}

/* ---- The method: a dark query ---------------------------------------
   The one dark surface in the block. The two lines carrying the source and
   the assumption are lit. Its floating notes came off 3 Oct (user: "remove
   ometh__notes and make ometh larger"), and the panel took their room: 440
   wide with no corner margin, against 360 plus 184 of notes. */
.ometh {
  position: relative;
  width: 440px;
  text-align: left;
}
.ometh p, .ometh ol { margin: 0; }
.ometh__code {
  border-radius: var(--r-md);
  background: var(--surface-inv);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  overflow: hidden;
}
.ometh__tabs {
  display: flex;
  gap: var(--sp-5);
  padding: var(--sp-3) var(--sp-4) 0;
  border-bottom: 1px solid var(--ink-inv-3);
  color: var(--ink-inv-3);
}
.ometh__tabs span { padding-bottom: var(--sp-3); }
.ometh__tabs .is-on {
  color: var(--accent-on-dark);
  box-shadow: inset 0 -1px 0 var(--accent-on-dark);
}
.ometh__lines {
  display: grid;
  gap: 6px;
  padding: var(--sp-4);
  list-style: none;
  counter-reset: ln;
  color: var(--ink-inv-2);
  white-space: pre;
}
.ometh__lines li {
  position: relative;
  counter-increment: ln;
  padding-left: 28px;
}
.ometh__lines li::before {
  content: counter(ln);
  position: absolute;
  left: 0;
  color: var(--ink-inv-3);
}
.ometh__lines b { font-weight: 500; color: var(--primary-200); }
.ometh__lines i { font-style: normal; color: var(--accent-on-dark); }
.ometh__lines .is-mark { color: var(--ink-inv); }

/* ---- Your domain: a term tree (3 Oct) ---------------------------------
   From a "Workflow completed" reference (user): one parent card, the
   children hung off it on dashed elbows. The parent says the terms are
   understood; each child is one term in its accent, with what aiRA reads
   it as set grey after it, the reference's "12s ago" slot. The glossary
   card and the points chart before it are in git. */
.odom {
  position: relative;
  width: 360px;
  font-family: var(--font-product);
  text-align: left;
}
:where(.odom) p, :where(.odom) ol { margin: 0; padding: 0; list-style: none; }
.odom__top,
.odom__row {
  display: flex;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-md);
}
.odom__top {
  gap: 12px;
  padding: 14px 16px;
  border-radius: 16px;
  font-size: var(--t-title-sm);
  font-weight: 500;
  color: var(--ink);
}
.odom__ok { width: 20px; height: 20px; flex: none; }
.odom__ok circle { fill: var(--approve-bg); stroke: var(--approve); stroke-width: 1.4; }
.odom__ok path { fill: none; stroke: var(--approve); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* The children sit indented under the parent's icon. One dashed spine runs
   down from that icon; each row takes a dashed elbow off it. */
.odom__rows {
  position: relative;
  display: grid;
  gap: 10px;
  padding: 12px 0 0 52px;
}
.odom__rows::before {
  content: '';
  position: absolute;
  left: 26px;
  top: 0;
  bottom: 37px;       /* stops where the last elbow turns */
  border-left: 1.5px dashed var(--ink-faint);
}
.odom__row {
  --acc: var(--brand);
  position: relative;
  gap: 10px;
  height: 54px;
  padding: 0 14px;
  border-radius: 14px;
  box-shadow: var(--shadow-sm);
  font-size: var(--t-body-sm);
  white-space: nowrap;
}
.odom__row::before {
  content: '';
  position: absolute;
  left: -27px;
  top: calc(50% - 10px);
  width: 26px;
  height: 10px;
  border-left: 1.5px dashed var(--ink-faint);
  border-bottom: 1.5px dashed var(--ink-faint);
  border-bottom-left-radius: 10px;
}
.odom__row--t { --acc: var(--tertiary-600); }
.odom__row--p { --acc: var(--brand); }
.odom__row--s { --acc: var(--secondary-500); }
.odom__ic { display: grid; flex: none; color: var(--acc); }
.odom__ic svg { width: 20px; height: 20px; }
.odom__tx { display: grid; gap: 1px; }
.odom__row b { font-weight: 500; color: var(--ink); line-height: 1.3; }
/* The reading sits UNDER the term, not trailing it (user, 3 Oct: "text
   after main text not looking good"): inline it read as a run-on. */
.odom__m { font-size: var(--t-caption); color: var(--ink-3); line-height: 1.3; }

/* The drawing is laid out at one size and scaled to its well, so the card's
   proportions and its 12px type survive together; laid out fluidly it would
   rewrap into a different picture at every width.
   ZOOM, not transform: scale. A transform shrinks the picture but not the
   box it occupies, so the centred card kept its full height and the well
   clipped its footer, which is the one row carrying the claim. Keyed to the
   well's HEIGHT, since that is the tight axis at 3/2. */
/* And keyed to WIDTH since the cards went to thirds. Later in source, so
   the narrower answer wins. */
/* The agenda card is short (about 260 tall at full size), so it takes its
   own, gentler steps: the height ladder above would shrink it to a
   thumbnail in a pane it fits at nearly full size. */
/* Width-only ladders since the panes hug their drawings. Defaults first,
   narrower steps after, so the narrowest match wins. */
.oth { zoom: 0.9; }
.odata { zoom: 0.86; }
.odom { zoom: 0.86; }
@container (max-width: 560px) { .oth { zoom: 0.8; } }
/* The query panel is short and wide, so WIDTH is its only binding axis:
   440 at full size, stepped down so it keeps about 16px either side. */
@container (max-width: 480px) { .ometh { zoom: 0.88; } }
@container (max-width: 440px) { .odom { zoom: 0.8; } }
@container (max-width: 380px) { .odom { zoom: 0.72; } }
@container (max-width: 330px) { .odom { zoom: 0.64; } }
@container (max-width: 420px) { .ometh { zoom: 0.78; } }
@container (max-width: 370px) { .ometh { zoom: 0.72; } }
@container (max-width: 330px) { .ometh { zoom: 0.64; } }
@container (max-width: 400px) { .oth { zoom: 0.72; } .odata { zoom: 0.84; } }
@container (max-width: 360px) { .oth { zoom: 0.68; } .odata { zoom: 0.78; } }
@container (max-width: 300px) { .oth { zoom: 0.58; } .odata { zoom: 0.68; } }

/* ---- Getting started (rebuilt 22 Sep, rebuilt again 23 Sep) -------------
   FOUR CLICKABLE BOXES OVER ONE LINE GRAPH, from a tryplayground.com
   reference the user supplied: "you got the 4 phase blocks, clean them and
   make their boxes clickable, below them is a big line graph, start from 0,
   then based on click it increases."

   One deliberate departure from the reference. It marks the chosen column
   with a pale blue FILL; this site signals a selected item with colour on
   its label and its mark, never a background (22 Sep, user, on the
   accordion: "dont do bg fill on hover, rather subtle color", "the icon
   should stay gray if not active"). So the chosen phase takes a brand
   border, a brand phase number and a lit node, and the four boxes keep one
   surface between them.

   The rail this replaced is gone with it. It was one hairline with four
   brand squares on it, and it existed to carry the sequence; the graph does
   that now, and a rail above a curve is the same statement twice. What the
   rail got right is kept: all four nodes were lit at once, which left the
   current phase with no mark of its own, and the nodes are grey until
   chosen now.
   -------------------------------------------------------------------------- */

/* FOUR ROWS, one per part of a phase, and each box spans them and takes them
   back with subgrid: meta, title, description, output. That is what keeps the
   rule above the output landing at ONE height across the row.

   Two simpler things were tried and both fail the same way. margin-top:auto
   on the output, and flex:1 on the description, both FLOOR the output, which
   aligns the four outputs' bottoms; one of them runs to two lines and another
   to one, so their rules then sit 21px apart across boxes of equal height.
   The rule is at the TOP of that block, so the top is what has to align. */
/* With the tabs on the rails the section's real left margin is their 24px,
   not .container's 20, so the head takes the same giveback and pad the bento
   head does, or its text sits 4px off the tabs' and reads as a mistake. */
.rollout .section-head {
  margin-inline: calc(-1 * var(--sp-5));
  padding-inline: var(--sp-6);
}

.rollout__track {
  /* The positioning context for .rollout__tab, which is measured in px off
     each step's offset and so has to share the track's own origin. */
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: repeat(4, auto);   /* meta, title, description, output */
  gap: 0;
  margin-top: var(--sp-12);
  /* Out to the rails, the same 20px giveback the bento uses, so the tabs'
     outer edges sit on the frame's verticals and their rules run rail to
     rail. Unwound below 992 with the rails. */
  margin-inline: calc(-1 * var(--sp-5));
  padding: 0;
  list-style: none;
  border: 1px solid var(--frame-line);
}
/* TABS, not cards (user, 23 Sep: "smaller tabs, no gap, more boxy shape and
   more connected to left right border"). No gap, no radius, no shadow, no
   lift: the four share hairlines and read as one control, and the board
   under them shares their bottom edge. The chosen tab is marked by a brand
   bar along its top, which is a tab's own grammar and is still not a
   background fill (22 Sep: "dont do bg fill on hover, rather subtle color"). */
.rollout__step {
  position: relative;
  display: grid;
  grid-row: span 4;
  grid-template-rows: subgrid;
  row-gap: 0;
  padding: var(--sp-5) var(--sp-6);
  background: var(--surface);
  cursor: pointer;
}
.rollout__step + .rollout__step { border-left: 1px solid var(--frame-line); }
/* ONE rule that SLIDES between the tabs (user, 23 Sep: "the top horizontal
   line should move and not regrow in each tab. same tab line to move
   across"). It was a bar per box, scaled up from zero on the chosen one and
   back down on the last: four marks taking turns, which reads as four
   separate events rather than as one thing moving.

   Its geometry comes from the chosen step's OWN offset within the track,
   measured by js/rollout.js, not from a column fraction. That is what lets
   the same element follow the 4-up, the 2x2 below 1099 and the stack below
   640 without the script knowing which of them is on, and it is why it
   carries a --tab-y as well: in a 2x2 the rule has to change rows.

   The transition is held until .is-live so the first placement lands rather
   than sliding in from the corner. */
.rollout__tab {
  position: absolute;
  top: -1px;
  left: 0;
  height: 2px;
  width: var(--tab-w, 25%);
  background: var(--brand);
  transform: translate(var(--tab-x, 0px), var(--tab-y, 0px));
  pointer-events: none;
}
.rollout.is-live .rollout__tab {
  transition: transform var(--dur-slow) var(--ease),
              width var(--dur-slow) var(--ease);
}
/* One row fewer once the output is out of flow: it is absolutely placed by
   .sr-only, so it stops claiming a track. */
.rollout.is-live .rollout__track { grid-template-rows: repeat(3, auto); }
.rollout.is-live .rollout__step { grid-row: span 3; }

/* The state mark: an empty ring for a phase still ahead, a filled ring for
   the chosen one, a check for the ones already behind it. So the row reads
   as progress, not as four equal options. js/rollout.js adds it. */
.rollout__mark {
  position: absolute;
  top: calc(var(--sp-5) + 2px);
  right: var(--sp-6);
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--brand-ink);
}
.rollout__mark::before {
  content: '';
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-pill);
  transition: border-color var(--dur) var(--ease), border-width var(--dur) var(--ease);
}
.rollout__mark svg { display: none; width: 20px; height: 20px; }
.rollout__step:hover .rollout__mark::before,
.rollout__step.is-hover .rollout__mark::before { border-color: var(--brand-line); }
.rollout__step.is-on .rollout__mark::before { border: 5px solid var(--brand); }
.rollout__step.is-done .rollout__mark::before { display: none; }
.rollout__step.is-done .rollout__mark svg { display: block; }

/* The whole box is the target, and this is how it becomes one without the
   markup putting a <button> around an <h3>, which is invalid: the button
   wraps the TITLE, which gives it a sensible accessible name, and a
   pseudo-element stretches it over the box. js/rollout.js builds it. */
.rollout__btn {
  appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.rollout__btn::after {
  content: '';
  position: absolute;
  inset: 0;
}
.rollout__btn:focus-visible { outline: none; }
.rollout__btn:focus-visible::after {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

/* One item since 23 Sep, when the WHO chips came off (user: "also remove the
   needs you, capillary runs it, etc."). Nothing was lost: the section's own
   sub already says "your team is needed in the first and the third", so the
   chips were repeating a sentence four times, once per box. */
.rollout__meta {
  display: flex;
  align-items: center;
  margin-bottom: var(--sp-3);
  padding-right: var(--sp-8);   /* clears the state mark */
}
/* Grey until chosen: four lit numbers mark nothing. */
.rollout__n {
  font-family: var(--font-display);
  font-size: var(--t-caption);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  transition: color var(--dur) var(--ease);
}
.rollout__step.is-on .rollout__n { color: var(--brand-ink); }

/* Who the phase needs. The two that want the customer's own people are the
   ones a buyer is scheduling around, so those carry colour; the two Capillary
   runs stay quiet. This is a LABEL and not a state, which is why it keeps a
   fill where the selection does not. */

.rollout__t {
  font-size: var(--t-title-sm);
  font-weight: 500;
  letter-spacing: var(--tr-title);
  line-height: var(--lh-title);
  color: var(--ink);
  margin-bottom: var(--sp-2);
  transition: color var(--dur) var(--ease);
}
.rollout__step:hover .rollout__t,
.rollout__step.is-hover .rollout__t { color: var(--brand-ink); }
.rollout__d {
  font-size: var(--t-body-sm);
  line-height: 1.65;
  color: var(--ink-3);
  margin-bottom: var(--sp-5);
}
/* With the output gone the paragraph closes the box, so the box's own bottom
   padding is the only gap wanted under it. */
.rollout.is-live .rollout__d { margin-bottom: 0; }

/* The output MOVED TO THE GRAPH on 23 Sep (user: "try to show the output on
   the graph, rather than on card"). This rule is what a reader sees with the
   script off; once it takes over, .rollout.is-live sends the block to
   .sr-only. HIDDEN RATHER THAN REMOVED: the pill on the graph is aria-hidden
   decoration, so display:none would drop four facts from the page as markup,
   and the section's own sub promises "four phases with a named output each". */
.rollout__out {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  font-size: var(--t-body-sm);
  line-height: 1.5;
  color: var(--ink-2);
}
.rollout.is-live .rollout__out {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.rollout__out-k {
  display: block;
  margin-bottom: 2px;
  font-family: var(--font-display);
  font-size: var(--t-caption);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---- The board: graph over parity gate -----------------------------------
   The chart and the gate are ONE object since 23 Sep. The gate is the
   condition the curve has to clear before go live, so it is the chart's own
   footer and it is drawn ON the chart as the line between phase three and
   phase four, rather than standing as a separate block under the section.

   The chart is four PHASE COLUMNS (js/rollout.js), each a click target with
   its number in an axis strip, so the graph says which phase is chosen and
   which are behind it without the reader looking back up at the boxes. The
   columns are equal on purpose: Capillary publishes two to four weeks in
   total and no per-phase split. There is NO scale on either axis: a y axis
   would be a measurement the site does not have.

   The ruled lines are pitched at 40px, which divides --cell (80px), so they
   register with the frame's own graph paper instead of cutting across it.
   -------------------------------------------------------------------------- */
/* Joined to the tabs and bled to the rails like them: the tabs' bottom rule
   is the chart's top edge, and nothing on the board is rounded. */
.rollout__board { margin-inline: calc(-1 * var(--sp-5)); }

.rollout__chart {
  --p: 1;
  --axis: 0px;
  position: relative;
  /* Not overflow:hidden: the marker sits ON the right edge at the last phase
     and should hang over it rather than be sliced in half. The <svg> clips
     itself to its own viewport. */
  padding-bottom: var(--axis);
  border-inline: 1px solid var(--frame-line);
  background: var(--surface);
}
.rollout__chart.is-live { --axis: 44px; }

.rollout__plot {
  position: relative;
  z-index: 1;
  height: 320px;
  /* The columns underneath take the clicks; everything drawn up here is
     decoration over them. */
  pointer-events: none;
  background-image: repeating-linear-gradient(to bottom,
    transparent 0 39px, var(--frame-grid) 39px 40px);
}
.rollout__svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Stops rather than raw hexes, so the fill stays on the token. */
.rollout__stop--hi { stop-color: var(--brand); stop-opacity: 0.18; }
.rollout__stop--lo { stop-color: var(--brand); stop-opacity: 0; }

/* The route ahead. Dashed, so it reads as pending. */
.rollout__ghost {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1.5;
  stroke-dasharray: 2 6;
  stroke-linecap: round;
}
.rollout__area { fill: url(#rollout-fill); }
/* A wide, faint stroke under the line, so the travelled route has some
   weight against the dashed one ahead of it. */
.rollout__halo {
  fill: none;
  stroke: var(--brand);
  stroke-opacity: 0.12;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.rollout__line {
  fill: none;
  stroke: var(--brand);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The curve is clipped from the RIGHT back to --p. Percentages in a clip-path
   on an SVG element resolve against its own fill box, and every path spans
   the full width, so they clip identically and the fill never runs ahead of
   the line. The transition is only live once the script has taken over, or
   the collapse to zero on load would animate in reverse. */
.rollout__area,
.rollout__halo,
.rollout__line { clip-path: inset(0 calc((1 - var(--p)) * 100%) 0 0); }
.rollout__chart.is-live .rollout__area,
.rollout__chart.is-live .rollout__halo,
.rollout__chart.is-live .rollout__line {
  transition: clip-path var(--dur-slow) var(--ease);
}

/* ---- Phase columns and the axis strip ---- */
.rollout__cols {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.rollout__col {
  position: relative;
  border-left: 1px solid var(--border);
  cursor: pointer;
}
.rollout__col:first-child { border-left: 0; }
/* The parity gate is the boundary into go live: dashed while it is ahead,
   brand once the chosen phase has reached it. */
.rollout__col--gate { border-left: 1px dashed var(--border-strong); }
.rollout__chart.is-gated .rollout__col--gate { border-left-color: var(--brand); }

.rollout__col-n {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--axis);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  border-top: 1px solid var(--border);
  font-family: var(--font-display);
  font-size: var(--t-caption);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  transition: color var(--dur) var(--ease);
}
/* The frame's own crossing mark, grey until its phase is reached. */
.rollout__col-n::before {
  content: '';
  width: 6px;
  height: 6px;
  background: var(--border-strong);
  transition: background var(--dur) var(--ease);
}
.rollout__col.is-done .rollout__col-n { color: var(--ink-2); }
.rollout__col.is-done .rollout__col-n::before { background: var(--ink-3); }
.rollout__col.is-on .rollout__col-n,
.rollout__col.is-hover .rollout__col-n { color: var(--brand-ink); }
.rollout__col.is-on .rollout__col-n::before { background: var(--brand); }

/* ---- What rides the curve ----
   HTML, not SVG. The chart is drawn with preserveAspectRatio="none" so it
   fills any box, which means a <circle> inside it would render as an ellipse
   at every width but one. */
.rollout__dot,
.rollout__drop,
.rollout__node,
.rollout__gate-tag,
.rollout__pill { display: none; }
.rollout__chart.is-live .rollout__dot,
.rollout__chart.is-live .rollout__drop,
.rollout__chart.is-live .rollout__node,
.rollout__chart.is-live .rollout__pill { display: block; }
.rollout__chart.is-live .rollout__gate-tag { display: inline-flex; }

/* A dashed drop from the marker to the floor of the plot, so the chosen
   point reads against its column rather than floating in it. */
.rollout__drop {
  position: absolute;
  left: var(--px, 0%);
  top: var(--py, 100%);
  bottom: 0;
  width: 0;
  border-left: 1px dashed var(--brand);
  opacity: 0.45;
  transition: left var(--dur-slow) var(--ease), top var(--dur-slow) var(--ease);
}

/* The other three phases' points: filled once behind the chosen one, hollow
   ahead of it, and ringed when their box or column is hovered. */
.rollout__node {
  position: absolute;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--border-strong);
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.rollout__node.is-done {
  background: var(--brand);
  box-shadow: 0 0 0 3px var(--surface);
}
.rollout__node.is-hover { box-shadow: 0 0 0 4px var(--brand-subtle), inset 0 0 0 1.5px var(--brand); }
.rollout__node.is-on { opacity: 0; }

.rollout__dot {
  position: absolute;
  z-index: 1;
  left: var(--px, 0%);
  top: var(--py, 100%);
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 3px solid var(--surface);
  border-radius: var(--r-pill);
  background: var(--brand);
  box-shadow: 0 0 0 1px var(--brand-line), var(--shadow-sm);
  transition: left var(--dur-slow) var(--ease), top var(--dur-slow) var(--ease);
}
/* A slow ring off the marker: the one moving thing on a still chart, and the
   answer to "where am I" before anything is read. */
.rollout__dot::after {
  content: '';
  position: absolute;
  inset: -3px;
  border: 2px solid var(--brand);
  border-radius: var(--r-pill);
  opacity: 0;
  animation: rollout-pulse 2.4s var(--ease) infinite;
}
@keyframes rollout-pulse {
  0%   { transform: scale(1);   opacity: 0.5; }
  100% { transform: scale(2.4); opacity: 0; }
}

/* The gate on the chart, sat at the foot of its line. */
.rollout__gate-tag {
  position: absolute;
  z-index: 1;
  left: 75%;
  bottom: var(--sp-3);
  transform: translateX(-50%);
  align-items: center;
  gap: 6px;
  padding: 4px 12px 4px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  font-size: var(--t-caption);
  font-weight: 600;
  line-height: 20px;
  white-space: nowrap;
  color: var(--ink-3);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.rollout__gate-tag svg { width: 20px; height: 20px; flex: none; }
.rollout__chart.is-gated .rollout__gate-tag {
  border-color: var(--brand-line);
  color: var(--brand-ink);
}

/* Two lines, a label and the output, so it is a chart annotation rather than
   a pill: the site's own surface with a hairline and a float shadow. */
.rollout__pill {
  position: absolute;
  z-index: 2;
  left: var(--px, 0%);
  top: var(--py, 100%);
  transform: translate(var(--pull, -50%), var(--push, calc(-100% - 18px)));
  /* Sized by its text, not by what is left of the box. An absolutely placed
     element with left:100% and no right has ZERO available width, so at the
     last phase it shrink-wrapped to its longest word and stacked four lines
     tall. */
  width: max-content;
  max-width: 24rem;
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  /* The 2px brand rule is on the TOP, and it is the SAME MARK the chosen
     phase box wears (.rollout__step.is-on::before). Same white ground, same
     hairline, same bar: the annotation is that box appearing again at its
     point on the curve, rather than a second thing that happens to be near
     it. It was a brand bar down the LEFT with the radius zeroed, which is the
     stock callout every template ships and reads as one (user, 23 Sep: "looks
     very AI made with left border").
     No radius, because nothing else in this section has one: the boxes and
     the chart are drawn in hairlines. The shadow is the small one and not
     --shadow-float, which is the nav's; this only has to lift off a grid. */
  border: 1px solid var(--border);
  border-top: 2px solid var(--brand);
  border-radius: 0;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: left var(--dur-slow) var(--ease), top var(--dur-slow) var(--ease);
}
.rollout__pill-k {
  display: block;
  margin-bottom: 2px;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--brand-ink);
}
.rollout__pill-v {
  display: block;
  font-size: var(--t-body-sm);
  font-weight: 550;
  line-height: 1.45;
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .rollout__chart.is-live .rollout__area,
  .rollout__chart.is-live .rollout__halo,
  .rollout__chart.is-live .rollout__line,
  .rollout__dot,
  .rollout__drop,
  .rollout__node,
  .rollout__pill,
  .rollout.is-live .rollout__tab,
  .rollout__gate-tag,
  .rollout__col-n,
  .rollout__col-n::before,
  .rollout__step::before,
  .rollout__mark::before,
  .rollout__n,
  .rollout__t { transition: none; }
  .rollout__dot::after { animation: none; }
}

/* ---- The parity gate, as the board's footer ----------------------------
   Still the one loud block in the section: a brand-tinted ground, a labelled
   mark and the figure at display size. It sits UNDER the chart now, sharing
   its border, because it is the chart's condition: the line marked on the
   graph between phase three and four is this.
   -------------------------------------------------------------------------- */
.rollout__gate {
  position: relative;
  display: grid;
  /* ONE ROW since 23 Sep (user: "this can be made a bit compact"): label,
     figure, claim and capture side by side, the figure down a step to the
     H2 size and the claim to body. It was two rows with a 60px figure, which
     made the footer nearly as tall as the chart's own axis-to-curve span. */
  --gate-pad: var(--sp-4);
  grid-template-columns: auto auto minmax(0, 1fr) clamp(120px, 11%, 150px);
  grid-template-areas: 'label fig body shot';
  align-items: center;
  column-gap: var(--sp-5);
  padding: var(--gate-pad) var(--sp-6);
  border: 1px solid var(--frame-line);
  border-top-color: var(--border);
  background: var(--wash-primary);
  overflow: hidden;
}
/* The capture bleeds to the card's own edges, so it gets the padding back on
   three sides. It is a COLUMN and not an absolutely placed layer: the body
   copy has to be laid out around it, and a layer would let the sentence run
   underneath at the widths where it is longest. */
.rollout__gate-shot {
  grid-area: shot;
  position: relative;
  align-self: stretch;
  margin: calc(-1 * var(--gate-pad)) calc(-1 * var(--sp-6))
          calc(-1 * var(--gate-pad)) 0;
  overflow: hidden;
}
.rollout__gate-shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The dashboard side of the capture, which is the half carrying numbers.
     Cropping in CSS rather than shipping a second file: it is the same asset
     the hero loads, so it costs no extra request. */
  object-fit: cover;
  object-position: 100% 30%;
  opacity: 0.5;
  /* Dissolved into the card rather than cut off at a hard edge, the same way
     the hero's glyph field and the category pages' mark row are handled. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 62%);
  mask-image: linear-gradient(to right, transparent 0, #000 62%);
}
.rollout__gate-k {
  grid-area: label;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: var(--tr-mono);
  text-transform: uppercase;
  color: var(--brand-ink);
}
.rollout__gate-k svg { width: 20px; height: 20px; flex: none; }

.rollout__gate-fig {
  grid-area: fig;
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin: 0;
  padding-left: var(--sp-5);
  border-left: 1px solid var(--brand-line);
}
/* At the H3 size since 23 Sep (user: "by compact i mean smaller"): the
   footer is a slim strip under the chart now, not a second headline. Sans
   with tabular numerals, never the mono, which gives a comma its own cell. */
.rollout__gate-n {
  font-family: var(--font-display);
  font-size: var(--t-display-3);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tr-display);
  font-variant-numeric: tabular-nums;
  color: var(--brand-ink);
}
.rollout__gate-u {
  font-size: var(--t-caption);
  color: var(--ink-2);
  white-space: nowrap;
}
.rollout__gate-d {
  grid-area: body;
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink-2);
  margin: 0;
  max-width: 88ch;
  padding-left: var(--sp-5);
  border-left: 1px solid var(--brand-line);
}

/* Two across from 1099, not 1299. The switch was that high because the
   section wore .section--band, whose 136px inset squeezed the four boxes to
   178px tracks at a 1200 viewport; the band came off on 23 Sep (user: "remove
   the left right dotted padding border") and they have the whole container
   now, so four hold down to about 1100.

   It went VERTICAL here until 23 Sep, on the reasoning that four phases in
   two rows stop reading as a sequence. That reasoning is spent: the graph
   below them now carries the sequence, and each box says which phase it is,
   so a 2x2 costs the section nothing and saves a phone about 400px of
   scrolling past boxes it has already read. */
@media (max-width: 1099px) {
  /* Two bands of four rows. */
  .rollout__track {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(8, auto);
  }
  .rollout.is-live .rollout__track { grid-template-rows: repeat(6, auto); }
  .rollout__step:nth-child(odd) { border-left: 0; }
  .rollout__step:nth-child(-n+2) { border-bottom: 1px solid var(--frame-line); }
  .rollout__plot { height: 280px; }
  /* The capture goes and the label rides above figure and claim, so the
     claim keeps a readable measure in two-thirds of the width. */
  .rollout__gate-shot { display: none; }
  .rollout__gate {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: 'label label' 'fig body';
    row-gap: var(--sp-3);
  }
  .rollout__gate-fig { padding-left: 0; border-left: 0; }
}
@media (max-width: 640px) {
  /* One column, so there is nothing left to align across and the subgrid
     comes off: each box simply rows itself. */
  .rollout__track {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
  }
  .rollout__step,
  .rollout.is-live .rollout__step {
    grid-row: auto;
    grid-template-rows: none;
    display: flex;
    flex-direction: column;
  }
  .rollout.is-live .rollout__track { grid-template-rows: none; }
  .rollout__step + .rollout__step { border-left: 0; }
  .rollout__step:not(:last-child) { border-bottom: 1px solid var(--frame-line); }
  .rollout__plot { height: 240px; }
  /* A quarter of a phone is about 85px, so the axis keeps the number only. */
  .rollout__col-w { display: none; }
  /* The pill is most of the chart's width at this size, so it wraps and is
     held to a measure; js/rollout.js stops centring it on the marker below
     520px and anchors it to whichever side has the room. */
  /* It stops pointing at the marker here and becomes the chart's CAPTION,
     pinned to the top-left. At this width the annotation is most of the box,
     so there is no anchoring that keeps it inside: left-anchored it runs off
     the right at the middle phases, right-anchored it runs off the left, and
     centred it does both. The marker still moves; this just stops pretending
     to be a tooltip in a box too small for one. */
  .rollout__pill {
    left: var(--sp-3);
    top: var(--sp-3);
    transform: none;
    padding: var(--sp-2) var(--sp-3);
    max-width: calc(100% - 2 * var(--sp-3));
  }
}
@media (max-width: 640px) {
  /* One column, and the capture goes: a 200px slice of a screenshot beside
     stacked copy is noise rather than texture. */
  .rollout__gate-shot { display: none; }
  .rollout__gate {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'label' 'fig' 'body';
    row-gap: var(--sp-3);
    padding: var(--sp-5);
  }
  .rollout__gate-d {
    padding-left: 0;
    padding-top: var(--sp-3);
    border-left: 0;
    border-top: 1px solid var(--brand-line);
  }
  .rollout__gate-u { margin-top: 0; }
}


/* ---- Responsive ----------------------------------------------------------
   LAST IN THE FILE, and it has to stay last. Media queries add no specificity,
   so an override here only beats a base rule by coming after it. This block
   used to sit in the middle, which was invisible for a while because a
   duplicated copy of the file tail happened to repeat it below the sections it
   overrides; when that duplicate was removed on 22 Sep the page started
   overflowing on a phone. Add new section styles ABOVE this comment, never
   below it.
   -------------------------------------------------------------------------- */

/* THE SPLIT STOPS AT 1200, which is EARLIER than the 992 everything else on
   this page unwinds at, and the headline is why. Ranged left in the split's
   left column the H1 runs to two lines down to 1200 and to THREE from 1199
   down, and at three lines the break lands inside the <em>: "the move" ends
   one line and "it implies" starts the next, so the dashed underline draws
   as two separate marks instead of one. That is the same failure /about's
   mission line is held to, and it is not a thing to accept on a hero.
   Widening the copy column was tried on paper and only half works: 1.15fr
   buys two lines back at 1100 and still gives three at 992.
   There is PRECEDENT on this page for stacking early. The pre-22-Sep split
   hero stacked at 1100 on the same kind of reasoning, that the capture
   stops being readable before the layout stops fitting.
   Below this line the hero is the stacked block it was before 3 Oct: copy
   full width, capture full width under it at its own ratio. The copy stays
   RANGED LEFT, since that was asked for at every width. */
@media (max-width: 1199px) {
  /* Everything the bleed needs has to be undone BY NAME: the second column,
     the start alignment, the overshoot width, and the height that replaced
     the ratio. Leave the height on and the frame is a 420px-tall letterbox
     of the capture's own top-left corner on a phone. */
  .phero .container { grid-template-columns: minmax(0, 1fr); }
  .phero__fig {
    grid-area: 2 / 1;
    justify-self: stretch;
    /* THE BOTTOM EDGE COMES BACK. Above 1200 the frame's cut sits on the
       section boundary, so it drops its bottom border and squares those
       corners to avoid doubling the joint's hairline. Stacked, the capture
       is full width, uncropped and has the section's own padding under it,
       so an open-bottomed card would just look unfinished. */
    border-bottom: 1px solid var(--border);
    border-radius: var(--r-lg);
    /* 100% and not the giveback: the rails come off below 992 and
       .container drops its gutter with them, so there is nothing to give
       back and a wider box would hang outside .padding-global. Between
       1199 and 992 the rails are still drawn and the capture sits the
       normal 20px inside them, like every other block on the page. */
    width: 100%;
    /* The capture's OWN ratio, so the whole window fits and nothing is
       cropped. See the phone note at the foot of this file for why any
       other ratio here is a crop rather than a resize. */
    aspect-ratio: 16 / 10;
    margin-top: var(--sp-12);
  }
  /* Back to the whole window, centred: at full width nothing is cut, so
     anchoring the crop left would only shift a picture that already fits. */
  /* THE CROP COMES OFF WITH THE SPLIT. Back to the whole window at 100%,
     centred: at full width the frame is the capture's own 16/10 and nothing
     is cut, so a 200% element would show the left half of a picture that
     had room for all of it. */
  .phero__fig img,
  .phero__fig > iframe { width: 100%; object-position: top center; }
  /* No dissolve either: there is no edge being cut to soften, and a mask
     here would just fade the right of a picture that fits. */
  .phero__fig { -webkit-mask-image: none; mask-image: none; }
  /* The field goes with the split, not with the rails, which is a change
     from the 992 this used to hide at. Its clear ellipse is placed over the
     LEFT COLUMN's copy; once the copy is full width there is no column for
     the hole to sit in and the glyphs end up behind the headline rather
     than around it. The script reads a zero box, drops its grid and kills
     its timer, so nothing ticks behind a hidden canvas. */
  .phero__matrix { display: none; }
}

@media (max-width: 991px) {

  .feat__mini { grid-template-columns: 1fr 1fr; }

  /* One column, and the grid gives back its rail bleed: the rails come off at
     this width and .container drops the 20px gutter with them, so a negative
     margin here would hang the grid outside .padding-global. */
  /* Two columns of CARDS, petavue's own mid step; they stay filled and
     bordered now rather than unwinding into the old ruled list, because a
     gapped card survives a narrow screen in a way a gapless hairline band
     did not. The rail giveback still comes off with the rails. */
  .bento__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-inline: 0; }
  .bento__cell, .bento__cell--4, .bento__cell--6 { grid-column: span 1; }
  /* Five cards in two columns leave the last one alone: it takes the row. */
  .bento__cell:last-child { grid-column: 1 / -1; }
  /* Same giveback, same reason, for /product's rollout tabs and board. */
  .rollout .section-head,
  .rollout__track,
  .rollout__board { margin-inline: 0; }
  .rollout .section-head { padding-inline: 0; }
  .bento__head { margin-inline: 0; padding-inline: 0; max-width: 46rem; }
  .bento__head { margin-bottom: var(--sp-10); }
}
@media (max-width: 991px) {
  /* The cert row gives back its rail bleed: the rails come off here and
     .container drops the gutter with them, so a negative margin would hang
     the row outside .padding-global. */
  .sec__certs { margin-inline: 0; }
  /* Six across becomes three by two. The marks are the content, so they keep
     their size and the cell simply gets shorter. */
  /* Already three across; only the cell height comes down. */
  .cert { min-height: 116px; }
}
@media (max-width: 640px) {
  /* Three marks stay on one row on a phone: at 390 that is a 116px track
     each, which still carries an 84px mark. Stacking them would cost 230px
     for no gain. */
  .cert { min-height: 96px; padding-inline: var(--sp-2); gap: var(--sp-2); }
  .cert__logo { height: 56px; }
  .cert:last-child .cert__logo { height: 52px; }
  /* "SOC 2 TYPE II" does not fit a 117px track on one line, and a wrapped
     mono label reads as two labels, so the row drops to the caption size and
     loses the tracking that was pushing it over. */
  .cert__name { font-size: var(--t-caption); letter-spacing: 0.04em; }
}
@media (max-width: 640px) {
  /* One column of cards, petavue's own last step: at two columns a 390
     phone was dealing 165px cards with 272px art panes inside them. */
  .bento__grid { grid-template-columns: minmax(0, 1fr); }
  .bento__cell, .bento__cell--4, .bento__cell--6, .bento__cell:last-child { grid-column: auto; }

  .phero__cta { margin-top: var(--sp-6); }
  /* The phone override for .phero__sub is GONE because the base rule already
     says `pretty`. It existed to switch off `balance`, which was there for a
     centred rag and went with the centring on 3 Oct, and a phone rule that
     restates its own base is the kind of line that outlives its reason.
     What it was solving still holds: balance is ignored past six lines, this
     sub ran to seven here and ended on a 14px orphan, and `pretty` has no
     line limit and works on the last line. It is two lines at 640 now. */
  .phero__fig { margin-top: var(--sp-10); }
  /* THE FRAME KEEPS THE CAPTURE'S OWN RATIO ON A PHONE, so the whole window
     fits and the box stays short (user, 28 Sep: "why the image if fill, fit
     it and change the frame size on mobile ... height is too long thats the
     issue").
     THE CROP WAS NEVER object-fit's FAULT, which is the part worth keeping.
     workspace.png is 2560x1600, exactly 16/10, and .phero__fig's base ratio is
     16/10, so at that ratio `cover` and `contain` do the same thing and
     nothing is cut. Every crop this rule has produced came from CHANGING the
     ratio: 5/4 cut the right pane, and 1/1 cut more of it and stood 350px tall
     on a 350px-wide screen. Matching the source ratio fixes the fit and the
     height in one move, 219px instead of 350.
     SO THERE IS NO MOBILE OVERRIDE LEFT and that is deliberate: the base rule
     was already right and the phone rules were working around a problem they
     had created. Do not reintroduce a ratio here without changing the asset.
     WHAT IT COSTS, stated plainly: at 350px wide the window's own UI type
     renders around 1.5px, so this is the product as a recognisable OBJECT, a
     two-pane app with a thread and a dashboard, and not something a phone
     reader can read. That is the trade the user chose over the zoomed crop
     .book__shot uses on the same asset. If legibility is ever wanted back
     here, it needs a zoom, and a zoom needs the height back. */


  .feat__mini { grid-template-columns: 1fr; }
}