/* ============================================================
   RMS Automation — products page
   Loaded only by /products/. Deliberately a file of its own
   rather than another thousand lines on home.css: nothing here
   is reused anywhere else, and home.css is already carried by
   four pages that would all pay to download a layout only one
   of them draws.

   THE ONE IDEA THIS PAGE IS BUILT ON

   Every product block is coloured by the plan that first
   includes it — cyan for Basic, amber for Premium, violet for
   Unlimited. Those are not new colours picked for this page;
   they are the exact three the pricing cards already orbit in
   (.plan[data-i] in home.css), so a reader who has seen the
   pricing page arrives here already knowing what the hues mean
   and a reader who does the reverse trip is not taught a second
   code. If the pricing colours ever change, change them here in
   the same commit or the two pages start lying to each other.
   ============================================================ */

/* ===================== HERO ===================== */
.pr-hero{ padding:calc(64px + clamp(56px,9vw,120px)) 0 clamp(40px,5vw,64px); }
.pr-hero h1{
  font-family:var(--display); font-variation-settings:"wdth" 118,"wght" 800;
  font-size:clamp(34px,6vw,66px); line-height:1.02; letter-spacing:-.015em;
  margin:0 0 20px; max-width:16ch;
}
.pr-hero h1 .thin{ display:block; font-variation-settings:"wdth" 92,"wght" 300; color:var(--ash); }
.pr-hero > .shell > p{ color:var(--ash); font-size:16.5px; line-height:1.65; max-width:56ch; margin:0; }

/* --- the legend ---------------------------------------------------------
   A colour code has to be stated before it is used, once, in the place the
   reader hits first. Without this the page is six blocks in three unrelated
   colours and the reader either works it out three blocks in or never does.

   It is a <dl> and not a row of divs because that is what it is: three
   terms and their definitions. The visual arrangement is the grid's job. */
.pr-key{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  margin:clamp(38px,5vw,56px) 0 0; padding:0;
  border-top:1px solid var(--edge);
}
.pr-key > div{ padding:20px 0 0; }
.pr-key > div + div{ border-left:1px solid var(--edge); padding-left:clamp(18px,2.4vw,30px); }
.pr-key > div:not(:last-child){ padding-right:clamp(18px,2.4vw,30px); }
.pr-key dt{
  display:flex; align-items:center; gap:9px; margin:0 0 8px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.18em; text-transform:uppercase;
  color:color-mix(in srgb, var(--g) 66%, var(--ash));
}
/* The swatch is the sample of the thing being defined, so it is a real
   painted square rather than a bullet tinted by inheritance. */
.pr-key dt::before{ content:""; width:9px; height:9px; flex:none; background:var(--g); }
.pr-key dd{ margin:0; font-size:13px; line-height:1.6; color:var(--ash); }

/* Same three assignments as .pr-b below, keyed off the same attribute
   name. Kept as CSS rather than inline `style="--g:…"` on each entry, so
   there is exactly one place in the codebase where a plan name is bound to
   a colour per file, and a fourth plan is one line in two rules rather
   than a search for hex values in markup. */
.pr-key > [data-tier="basic"]    { --g:var(--plasma); }
.pr-key > [data-tier="premium"]  { --g:var(--arc);    }
.pr-key > [data-tier="unlimited"]{ --g:var(--ion);    }

/* ===================== THE BLOCKS ===================== */
/* The grid is on the shell, not on the section: the shell is what carries
   the max-width and the gutters, so it is what the blocks have to be laid
   out inside. Putting the grid on the section would stretch every block to
   the viewport. */
.pr-set{ padding:0 0 clamp(70px,9vw,110px); }
.pr-set > .shell{ display:grid; gap:clamp(18px,2.4vw,30px); }

/* Square corners, like every other frame on this site. Morpho's version of
   this layout is heavily rounded; ours is not, and matching the geometry
   rather than the grammar would have made one page on the site look like it
   came from somewhere else. */
.pr-b{
  display:grid; grid-template-columns:1fr 1fr;
  border:1px solid var(--edge); background:var(--graphite);
  overflow:hidden;
  /* Each block has an id so an article can link back to the one it is
     about. Without this the fixed bar lands on top of the block's first
     rows every time one of those links is followed. */
  scroll-margin-top:88px;
}

/* Alternation by nth-child rather than by a modifier class on every other
   block. A class is more legible in the markup right up until somebody
   inserts a block in the middle, at which point every class below it is
   wrong and the page silently develops two media panels side by side.
   nth-child re-derives it. */
.pr-b:nth-child(even) .pr-media{ order:2; }

/* --- the media panel ---------------------------------------------------
   The tinted half. Two layers: a soft off-centre bloom in the plan hue,
   over a near-black base carrying a much weaker wash of the same hue, so
   the panel reads as lit from one corner rather than as a flat colour
   field.

   color-mix into an opaque base, never into `transparent` — mixing toward
   transparent in sRGB darkens as it fades and leaves a grey halo where the
   stop lands. The bloom's stops are the exception: those are alpha ramps,
   which is the one thing a gradient stop genuinely wants. */
.pr-media{
  position:relative; display:grid; place-items:center;
  padding:clamp(26px,3.4vw,54px); min-height:clamp(300px,34vw,430px);
  background:
    radial-gradient(90% 78% at 22% 8%,
      color-mix(in srgb, var(--g) 30%, #0B0E13),
      color-mix(in srgb, var(--g) 6%, #0B0E13) 72%),
    #0B0E13;
}

/* The screenshot itself, framed the way the enterprise page frames its
   captures — hairline border, inset highlight along the top edge, and a
   drop that lifts it off the wash underneath. */
.pr-shot{
  width:100%; position:relative; z-index:1;
  border:1px solid rgba(255,255,255,.10); background:var(--void); overflow:hidden;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07), 0 30px 62px -30px rgba(0,0,0,.95);
}
.pr-shot img{ width:100%; height:auto; display:block; }

/* --- the shot stack ----------------------------------------------------
   Every sub-feature's capture ships in the block and they are stacked one
   on top of another; js/products.js moves [data-on] between them as the
   pointer crosses the tile strip.

   grid-area:1/1 rather than position:absolute. Absolute positioning takes
   all of them out of flow, which leaves the stack with no height of its
   own and collapses the panel; the usual fix is to leave the first one in
   flow and absolutely position the rest, which then makes the panel jump
   every time a taller or shorter capture is shown. Overlapping grid items
   stay in flow, so the stack is as tall as its tallest member and the
   panel never moves. That matters more here than it looks: the captures
   are not all the same aspect ratio, and a block that resized under the
   pointer would push everything below it down the page mid-hover.

   visibility, not just opacity. opacity:0 alone leaves the hidden shots
   in the accessibility tree and their images announced, so a screen
   reader would read four descriptions of the same panel. visibility
   removes them and still contributes the height. It is not transitioned —
   it snaps at the end of the fade on the way out, which is what the delay
   is for, and immediately on the way in. */
.pr-stack{ display:grid; width:100%; position:relative; z-index:1; }
.pr-stack > .pr-shot{
  grid-area:1/1;
  opacity:0; visibility:hidden;
  transition:opacity .3s ease, visibility 0s linear .3s;
}
.pr-stack > .pr-shot[data-on]{
  opacity:1; visibility:visible;
  transition:opacity .3s ease, visibility 0s;
}

/* --- the placeholder ---------------------------------------------------
   TEMPORARY. Every block that has no capture yet draws this instead, and
   it states the filename and the pixel size the capture has to be, so
   shooting them is a work order rather than a guessing game.

   It is a deliberate plate and not an <img> pointing at a file that does
   not exist. A missing src renders as a broken-image glyph and alt text in
   the browser's own styling, which looks like a bug on a page that is
   being reviewed, and — worse — it 404s six times per load in the network
   panel and buries anything real that goes wrong there.

   Replacing one is: delete the .pr-plate, put an <img> inside .pr-shot.
   Nothing else on the page has to move. */
.pr-plate{
  aspect-ratio:16/10; display:grid; place-content:center; gap:8px; text-align:center;
  padding:20px; border:1px dashed var(--edge); background:rgba(255,255,255,.012);
}
.pr-plate b{
  font-family:var(--mono); font-size:10px; font-weight:500;
  letter-spacing:.2em; text-transform:uppercase;
  color:color-mix(in srgb, var(--g) 55%, var(--ash));
}
.pr-plate span{ font-family:var(--mono); font-size:10.5px; color:var(--ash-dim); word-break:break-all; }

/* --- the copy half ----------------------------------------------------- */
.pr-copy{ display:flex; flex-direction:column; padding:clamp(26px,3.2vw,50px); }

/* The size, colour and spacing here are the ones that have always rendered.
   They used to arrive from a `.pr-copy > p` rule written for the section
   descriptions, which outweighed this block on specificity and took the
   kicker with it; the descriptions are gone and the rule with them, so the
   values are stated here instead. The kicker was originally drawn at 10px in
   a green mix — restoring that is a design decision, not a repair. */
.pr-kick{
  display:flex; align-items:center; gap:10px; margin:0 0 24px;
  font-family:var(--mono); font-size:14.5px; line-height:1.65;
  letter-spacing:.2em; text-transform:uppercase;
  color:var(--ash);
}
.pr-kick::before{ content:""; width:20px; height:1px; flex:none; background:var(--g); }

.pr-copy h2{
  font-family:var(--display); font-variation-settings:"wdth" 108,"wght" 720;
  font-size:clamp(21px,2.5vw,30px); line-height:1.1; letter-spacing:-.01em;
  margin:0 0 14px;
}

/* --- the capability list ----------------------------------------------- */
.pr-l{ list-style:none; margin:0 0 26px; padding:0; display:grid; gap:10px; }
.pr-l li{ position:relative; padding-left:17px; font-size:13.5px; line-height:1.5; color:var(--bone); }
.pr-l li::before{ content:""; position:absolute; left:0; top:.58em;
  width:5px; height:5px; background:var(--g); }

/* --- the sub-feature strip ---------------------------------------------
   The tiles that sit where Morpho puts the logos of the companies using
   each product. Ours name the parts of the product instead, because the
   part is the thing a reader is actually deciding about — nobody buys a
   testing-forms module, they buy "it writes the PowerDB CSV for me".

   Each tile does two jobs, and they are deliberately different jobs:
   pointing at it swaps the capture in the panel, clicking it goes to the
   section of the product's article that explains it. Hover previews,
   click commits. Making the hover itself navigate, or making the click
   only swap an image, would each collapse that into one weaker gesture.

   They are <a> elements and not <button>s because the click genuinely
   navigates. A button that changes the page's location is a link wearing
   a costume, and it loses middle-click, open-in-new-tab and the status
   bar preview along the way.

   grid-auto-flow:column with auto columns rather than a fixed
   repeat(3): blocks carry three or four sub-features depending on how
   many the product actually has, and hard-coding three would either
   wrap the fourth onto a second row of one or force every product to
   invent a third. */
.pr-sub{
  list-style:none; margin:auto 0 10px; padding:0;
  display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr);
  border:1px solid var(--edge);
  position:relative;
}
.pr-sub li{ display:grid; min-width:0; position:relative; z-index:1; }
.pr-sub li + li{ border-left:1px solid var(--edge); }

/* The travelling highlight.

   When js/products.js is driving the strip it moves the lit tile along on
   its own, and a tint that appears on one tile and disappears from another
   reads as two events rather than one movement. This is the one tint,
   sliding: an absolutely positioned band the width of a single tile that
   translates to whichever tile is current.

   A pseudo-element rather than an injected <span> because <ul> may only
   contain <li>, <script> and <template> — a span between the items would
   be parsed out of the list.

   The geometry needs no measurement. The tracks are grid-auto-columns
   minmax(0,1fr) with no gap, so every track is exactly the content width
   over the number of tiles; --n carries that count and --i the index, both
   written by script. A percentage transform resolves against the element's
   own border box — which is one track wide — so translateX(i * 100%) lands
   on track i however wide the block happens to be. The separator borders
   live inside each li and paint above the band, so the band's edges fall
   on the lines rather than covering them.

   All of it is gated on [data-glide], which script sets only once it has a
   strip it can actually drive. Without it the band never appears and the
   per-tile tint below is the whole highlight, exactly as before. */
.pr-sub::before{
  content:""; position:absolute; z-index:0; pointer-events:none;
  top:0; left:0; height:100%; width:calc(100% / var(--n,1));
  transform:translateX(calc(var(--i,0) * 100%));
  background:color-mix(in srgb, var(--g) 10%, transparent);
  opacity:0;
  transition:transform var(--glide-ms,550ms) cubic-bezier(.65,0,.35,1),
             opacity .25s ease;
}
.pr-sub[data-glide]::before{ opacity:1; }

/* The tiles carry a faint wash of their own, and a tile painting over the
   band would dull it as it passed. With the band live the wash moves to
   the strip, where it covers the same area at the same value — the tiles
   go transparent and the band is the only thing tinting anything.

   This also takes out the [data-on] and :hover tints, which is the point:
   during a slide the band is between two tiles, and a tile lit underneath
   it would be a second highlight in a place the reader is not looking. */
.pr-sub[data-glide]{ background:rgba(255,255,255,.012); }
.pr-sub[data-glide] .pr-sub-t{ background:transparent; }

/* One class, two elements. Products that have an article yet carry <a>
   tiles; the ones still waiting on theirs carry <button> tiles that only
   swap the capture, so nothing on this page links to a file that is not
   written. Turning a button into a link when its article ships is an
   element swap and a href — no styling moves. The button reset here is
   what makes the two indistinguishable: a UA button brings its own font,
   its own centring and its own border, and all three have to go. */
.pr-sub-t{
  position:relative; display:flex; align-items:flex-start;
  min-width:0; width:100%; text-align:left;
  padding:12px 22px 12px 12px;
  font-family:var(--mono); font-size:10px; line-height:1.45;
  letter-spacing:.12em; text-transform:uppercase;
  color:var(--ash); background:rgba(255,255,255,.012);
  border:0; -webkit-appearance:none; appearance:none; cursor:pointer;
  transition:color .18s ease, background-color .18s ease;
}

/* The arrow Morpho puts in the corner of each tile, and it earns its
   place here for a reason it does not there: these tiles have two
   behaviours and only one of them is discoverable by pointing. The arrow
   is what says the tile is also somewhere to go.

   aria-hidden cannot be set on a pseudo-element, so this is a glyph some
   screen readers will read as "north east arrow" after the tile's name.
   That is the correct thing to hear on a link that leads somewhere, and
   it is why the arrow is here rather than a decorative border flourish. */
a.pr-sub-t::after{
  content:"\2197"; position:absolute; top:9px; right:8px;
  font-size:10px; line-height:1; color:var(--ash-dim);
  transition:color .18s ease, transform .18s ease;
}

/* Lit by the pointer, by the keyboard, and by script — three selectors
   for one appearance. [data-on] is the one js/products.js writes, and it
   is what keeps the tile and the capture agreeing when focus moved the
   panel rather than the mouse. */
.pr-sub-t:hover,
.pr-sub-t:focus-visible,
.pr-sub-t[data-on]{
  color:color-mix(in srgb, var(--g) 60%, var(--bone));
  background:color-mix(in srgb, var(--g) 10%, transparent);
}
.pr-sub-t:hover::after,
.pr-sub-t:focus-visible::after,
.pr-sub-t[data-on]::after{
  color:var(--g); transform:translate(1px,-1px);
}

/* ---- peeking: the pointer moves the band without animating it ------
   [data-peek] is set by script while a pointer or focus is inside the
   strip. The band still follows — the reader is choosing captures and
   the band is what says which one they are on — but it stops sliding to
   get there.

   The slide is the cycle's. It is 550ms because that is the right length
   for a move nobody asked for: slow enough to be followed, tied to the
   dwell. A pointer tracing four tiles fires that same 550ms sweep four
   times in a second, and the band spends the whole time somewhere the
   reader is not, still travelling to a tile they have already left. So
   under the pointer the band cuts, and the easing goes back the moment
   the pointer does.

   Only transform is dropped. opacity keeps its fade, because that is the
   band appearing and disappearing rather than the band moving, and it is
   never fired by a pointer. */
.pr-sub[data-peek]::before{ transition:opacity .25s ease; }

/* .pr-plans below carries `margin-top:auto` to pin the pair of strips to
   the bottom of the copy column. With a sub-strip present that job moves
   up to .pr-sub, and leaving both on auto would split the free space
   between them and float the sub-strip in the middle of the block. */
.pr-sub + .pr-plans{ margin-top:0; }

/* --- the plan strip ----------------------------------------------------
   Where Morpho puts its customers' logos. We have no customers to put
   there yet, and a row of placeholder logos on a products page is the
   fastest way to look like a company pretending to have some.

   What goes there instead is the thing this page is actually for: which
   plans include this product. Each cell carries ITS OWN plan colour rather
   than the block's, which is the whole point of the strip — the plans are
   cumulative, so a capability introduced in Premium is lit in the Premium
   cell AND the Unlimited cell, in two different colours, and the reader
   can see the ladder without reading a word of it. A strip painted in one
   hue would say only "this is a Premium thing", which the kicker above it
   already said.

   aria-hidden is NOT set on the off cells: "Basic, not included" is
   information, and hiding it would leave a screen reader hearing three
   plan names with no way to tell which two applied. The state is carried
   in text, inside the .pr-t-s span, not in colour alone. */
.pr-plans{
  list-style:none; margin:auto 0 24px; padding:0;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  border:1px solid var(--edge);
}
.pr-plans li{
  padding:13px 12px; display:grid; gap:5px;
  background:rgba(255,255,255,.012);
}
.pr-plans li + li{ border-left:1px solid var(--edge); }

.pr-t-n{ font-family:var(--mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ash-dim); }
.pr-t-s{ font-family:var(--mono); font-size:9px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ash-dim); }

/* The lit state. Each cell sets --pg to its own plan's hue in the markup's
   data-plan attribute below, so this rule stays hue-agnostic. */
.pr-plans li[data-on]{ background:color-mix(in srgb, var(--pg) 9%, transparent); }
.pr-plans li[data-on] .pr-t-n{ color:color-mix(in srgb, var(--pg) 72%, var(--bone)); }
.pr-plans li[data-on] .pr-t-s{ color:color-mix(in srgb, var(--pg) 60%, var(--ash)); }

.pr-plans li[data-plan="basic"]    { --pg:var(--plasma); }
.pr-plans li[data-plan="premium"]  { --pg:var(--arc);    }
.pr-plans li[data-plan="unlimited"]{ --pg:var(--ion);    }

/* --- the block CTA ------------------------------------------------------
   .btn's own border and hover already cover this; the only thing added is
   a hue-aware border so the pill belongs to the block it sits in. Not a
   filled button: --arc is the buy colour on this site and six filled amber
   buttons down one page would present six equal offers and no hierarchy.
   The filled one is at the bottom, once. */
.pr-act{ display:flex; flex-wrap:wrap; align-items:center; gap:12px 20px; }

.pr-go{ align-self:start; border-color:color-mix(in srgb, var(--g) 34%, var(--edge)); }
.pr-go:hover{ border-color:color-mix(in srgb, var(--g) 70%, var(--edge));
  background:color-mix(in srgb, var(--g) 8%, transparent); }

/* Morpho's "Read more". Set as a quiet mono link rather than a second
   pill: the pill next to it is asking for the signup, and two buttons of
   equal weight side by side make the reader choose between them instead
   of choosing the one. The article is for people who are not ready to
   press the other one yet, and it should look like the smaller ask. */
.pr-more{
  font-family:var(--mono); font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  color:color-mix(in srgb, var(--g) 50%, var(--ash));
  border-bottom:1px solid color-mix(in srgb, var(--g) 26%, var(--edge));
  padding-bottom:2px; transition:color .18s ease, border-color .18s ease;
}
.pr-more:hover{ color:var(--g); border-bottom-color:var(--g); }

/* --- per-block hue ------------------------------------------------------
   Set on the block, so the kicker, the bullets, the bloom, the placeholder
   label and the pill all resolve from one declaration. */
.pr-b[data-tier="basic"]    { --g:var(--plasma); }
.pr-b[data-tier="premium"]  { --g:var(--arc);    }
.pr-b[data-tier="unlimited"]{ --g:var(--ion);    }

/* ============================================================
   THE PRODUCT ARTICLE
   /products/<name>.html — one page per product, reached from
   the sub-feature tiles and the "How it works" link.

   Same shape as the customer stories the block layout came
   from: article left, a narrow column of facts right, and a
   row of figures under the headline. What sits in those
   figures is the part that had to change. Morpho's are
   measured results from a named customer. We have neither, so
   ours are statements about the product — which plan, what it
   reads, what it will not do without you — and every number
   that could be mistaken for a measured result is inside
   .pa-note, which says in the copy that it is arithmetic and
   not evidence.
   ============================================================ */
/* The top padding used to read calc(64px + clamp(40px,6vw,72px)). The 64px
   was not breathing room, it was clearance: these pages once carried the
   same sticky .nav bar as the rest of the site, and content that started at
   the top of the viewport would have started underneath it.

   The bar is gone from all seven articles now, so the clearance is gone
   with it. Leaving it in would have left a band of dead space above the
   breadcrumb that nothing occupies and nothing explains — the kind of gap
   that looks like a bug on a page whose first element is supposed to be
   the way back out.

   If a bar is ever reinstated here, this is one of three things to put
   back; the others are .pa-side's sticky offset and the js/nav.js tag in
   each article's markup. */
.pa-wrap{ padding:clamp(40px,6vw,72px) 0 clamp(70px,9vw,110px); }

/* One line decides the whole page's accent — rule, figures, links, stat
   headings, sidebar chips — because everything downstream reads --g and
   nothing downstream names a colour.

   The value comes from data-tier on the wrapper, using the same three
   bindings as the plan strip on the index page: Basic cyan, Premium
   amber, Unlimited violet. That is not decoration, it is the site's only
   colour key — a reader who has scrolled the products page has already
   learned that violet means Unlimited, and an article that opened amber
   for a violet product would quietly unteach it.

   The bare .pa-wrap default is amber rather than nothing, so an article
   added without a data-tier renders in a house colour instead of failing
   to resolve --g and inheriting whatever is above it. */
.pa-wrap{ --g:var(--arc); }
.pa-wrap[data-tier="basic"]    { --g:var(--plasma); }
.pa-wrap[data-tier="premium"]  { --g:var(--arc);    }
.pa-wrap[data-tier="unlimited"]{ --g:var(--ion);    }

/* --- breadcrumb --------------------------------------------------------
   Two entries and no more. A trail that reproduces the whole nav is
   navigation wearing a breadcrumb's clothes; what a reader needs here is
   the way back to the page they came from. */
.pa-crumb{
  display:flex; align-items:center; gap:10px; margin:0 0 clamp(26px,3.4vw,40px);
  font-family:var(--mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase;
}
.pa-crumb a{ color:var(--ash); transition:color .18s ease; }
.pa-crumb a:hover{ color:var(--g); }
.pa-crumb span{ color:var(--ash-dim); }

/* --- the two columns ---------------------------------------------------- */
.pa-body{ display:grid; grid-template-columns:minmax(0,1fr) 250px; gap:clamp(38px,5vw,68px); align-items:start; }

.pa-doc h1{
  font-family:var(--display); font-variation-settings:"wdth" 112,"wght" 780;
  font-size:clamp(30px,4.6vw,52px); line-height:1.06; letter-spacing:-.018em;
  margin:0 0 18px; max-width:20ch;
}
.pa-lede{ color:var(--ash); font-size:16.5px; line-height:1.68; max-width:62ch; margin:0 0 clamp(30px,4vw,44px); }

/* --- the figure row -----------------------------------------------------
   Three facts, stated in the plan hue, in the position Morpho gives its
   headline metrics. They are short phrases rather than numbers on purpose:
   see the block comment at the top of this section. */
.pa-stats{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(16px,2.4vw,30px);
  margin:0 0 clamp(30px,4vw,44px); padding:0; }
.pa-stats > div{ min-width:0; }
.pa-stats dt{
  font-family:var(--display); font-variation-settings:"wdth" 104,"wght" 700;
  font-size:clamp(19px,2.4vw,27px); line-height:1.1; letter-spacing:-.01em;
  color:var(--g); margin:0 0 8px;
}
.pa-stats dd{ margin:0; font-size:12.5px; line-height:1.55; color:var(--ash); }

/* --- the hero capture --------------------------------------------------- */
.pa-fig{ margin:0 0 clamp(34px,4.6vw,52px); }
.pa-fig .pr-shot{ width:100%; }
.pa-fig figcaption{ margin-top:12px; font-family:var(--mono); font-size:10.5px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ash-dim); }

/* --- prose --------------------------------------------------------------
   scroll-margin-top matters more here than anywhere else on the site:
   twenty-four of the twenty-five tiles on the index page land on a
   heading id in one of these articles, so this rule decides where almost
   every arrival puts its target.

   It used to be 96px, which was the sticky bar plus a breath — without it
   a tile that landed on #hazards would have parked the heading underneath
   the bar. There is no bar now, so all that is left is the breath. A
   heading flush against the top edge reads as though the page was cut off
   above it, which is why this is not zero. */
.pa-doc section{ margin:0 0 clamp(34px,4.4vw,50px); scroll-margin-top:24px; }
.pa-doc h2{
  font-family:var(--display); font-variation-settings:"wdth" 108,"wght" 720;
  font-size:clamp(20px,2.3vw,27px); line-height:1.15; letter-spacing:-.01em;
  margin:0 0 14px;
}
.pa-doc h3{ font-size:15px; font-weight:600; color:var(--bone); margin:26px 0 10px; }
.pa-doc p{ margin:0 0 14px; color:var(--ash); font-size:14.5px; line-height:1.72; max-width:68ch; }
.pa-doc strong{ color:var(--bone); font-weight:600; }
.pa-doc a:not(.btn):not(.pa-crumb a){ color:var(--g); border-bottom:1px solid color-mix(in srgb, var(--g) 30%, var(--edge)); }
.pa-doc ul{ list-style:none; margin:0 0 16px; padding:0; display:grid; gap:10px; max-width:68ch; }
.pa-doc ul li{ position:relative; padding-left:17px; color:var(--ash); font-size:14.5px; line-height:1.6; }
.pa-doc ul li::before{ content:""; position:absolute; left:0; top:.6em; width:5px; height:5px; background:var(--g); }

/* --- the arithmetic table ----------------------------------------------
   Inputs and outputs in one frame, with the inputs marked as inputs. The
   point of showing it as a table rather than a sentence is that a reader
   can see which lines are assumptions and change them; a paragraph that
   says "saves you an hour" hides exactly that. */
.pa-calc{ width:100%; border-collapse:collapse; border:1px solid var(--edge);
  background:var(--graphite); margin:0 0 16px; }
.pa-calc th, .pa-calc td{ padding:11px 14px; text-align:left; border-top:1px solid var(--edge);
  font-size:13.5px; line-height:1.5; }
.pa-calc thead th{ border-top:0; font-family:var(--mono); font-size:10px;
  letter-spacing:.16em; text-transform:uppercase; color:var(--ash-dim); font-weight:400; }
.pa-calc tbody th{ font-weight:400; color:var(--ash); }
.pa-calc td{ font-family:var(--mono); font-size:13px; color:var(--bone); white-space:nowrap; text-align:right; }
.pa-calc tbody tr[data-out] th{ color:var(--bone); font-weight:600; }
.pa-calc tbody tr[data-out]{ background:color-mix(in srgb, var(--g) 7%, transparent); }
.pa-calc tbody tr[data-out] td{ color:var(--g); }

/* --- the caveat --------------------------------------------------------
   Deliberately not fine print. It is the sentence that decides whether
   the table above it is honest, so it is set at reading size, in the
   flow, with a rule in the plan colour — the same treatment .legal-note
   gets on the legal pages. Do not shrink it. */
.pa-note{
  border:1px solid var(--edge); border-left:2px solid var(--g);
  background:var(--graphite); padding:16px 18px; margin:0 0 16px; max-width:68ch;
}
.pa-note p{ margin:0; font-size:13.5px; line-height:1.65; color:var(--ash); }
.pa-note p + p{ margin-top:10px; }

/* --- audience split -----------------------------------------------------
   Two panels, one per reader. The page is read by a one-van contractor
   and by somebody buying for forty testers, and the honest answer is that
   the product helps them for different reasons — so they get different
   panels rather than one paragraph hedged to cover both. */
.pa-split{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:clamp(14px,2vw,20px); margin:0 0 16px; }
.pa-split > div{ border:1px solid var(--edge); background:var(--graphite); padding:clamp(18px,2.2vw,26px); }
.pa-split h3{ margin:0 0 12px; }
.pa-split .pa-who{
  display:block; margin:0 0 10px; font-family:var(--mono); font-size:10px;
  letter-spacing:.18em; text-transform:uppercase; color:color-mix(in srgb, var(--g) 60%, var(--ash));
}
.pa-split p:last-child{ margin-bottom:0; }

/* --- the fact column ----------------------------------------------------
   The sticky offset was 96px, which was the 64px nav bar plus a 32px gap
   underneath it. With no bar on these pages the column would have parked
   itself an inch and a half below the top of the viewport for no reason
   anyone could see. 24px is just a margin now — enough that the panel does
   not look welded to the edge of the screen while it is pinned. */
.pa-side{ position:sticky; top:24px; display:grid; gap:26px; }
.pa-side h4{
  font-family:var(--mono); font-size:10px; letter-spacing:.2em; text-transform:uppercase;
  color:var(--ash-dim); margin:0 0 10px; font-weight:400;
}
.pa-side ul{ list-style:none; margin:0; padding:0; display:grid; gap:8px; }
.pa-side li, .pa-side p{ margin:0; font-size:13px; line-height:1.6; color:var(--ash); }
.pa-side a{ color:var(--ash); transition:color .18s ease; }
.pa-side a:hover{ color:var(--g); }

/* --- "on this page", with the reader's place in it ----------------------
   A rail down the left of the section list and a short rule on it that
   moves to whichever section is being read. js/pa-spy.js decides which
   that is and writes the two measurements; everything else is here.

   Both the rail and the marker are drawn only under [data-spy], which
   the script sets once it has matched every entry to a real section.
   With no script there is no rail to be missing a marker from — the
   list is the plain list of anchors it has always been.

   The measurements have to be measured, unlike the sub-feature band on
   the index page: those tiles are equal grid tracks, these entries are
   one or two lines depending on how the words fall. --toc-top and
   --toc-h are offsets against the list itself, which is this marker's
   containing block. */
/* Scoped to .pa-side, not because the rule needs the ancestor but because
   `.pa-side ul{ padding:0 }` above is (0,1,1) and would otherwise outweigh
   a bare .pa-toc at (0,1,0) and quietly flatten the indent — leaving the
   rail drawn underneath the text instead of beside it. */
.pa-side .pa-toc{ position:relative; padding-left:13px; }
.pa-toc[data-spy]::before{
  content:""; position:absolute; left:0; top:0; bottom:0;
  width:1px; background:var(--edge);
}
.pa-toc[data-spy]::after{
  content:""; position:absolute; left:0; width:1px;
  top:var(--toc-top,0); height:var(--toc-h,0);
  background:var(--g); opacity:0;
  transition:top .34s cubic-bezier(.65,0,.35,1),
             height .34s cubic-bezier(.65,0,.35,1),
             opacity .2s ease;
}

/* Hidden above the first section, where the reader is in the title and
   the lede and no entry is true yet. [data-at] is the script saying it
   has settled on one. */
.pa-toc[data-at]::after{ opacity:1; }
.pa-toc a[aria-current]{ color:var(--g); }

/* The plan chip. Same three hue bindings as everywhere else on the page,
   keyed off the same attribute name, so a fourth plan is one more line
   here and not a new colour convention. */
.pa-plan{
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  border:1px solid color-mix(in srgb, var(--pg) 34%, var(--edge));
  background:color-mix(in srgb, var(--pg) 9%, transparent);
  color:color-mix(in srgb, var(--pg) 68%, var(--bone));
  padding:7px 11px;
}
.pa-plan::before{ content:""; width:7px; height:7px; flex:none; background:var(--pg); }
.pa-plan[data-plan="basic"]    { --pg:var(--plasma); }
.pa-plan[data-plan="premium"]  { --pg:var(--arc);    }
.pa-plan[data-plan="unlimited"]{ --pg:var(--ion);    }

/* A Basic product is in all three plans, so "Also in" carries two chips
   rather than one. Left as inline elements they get a single collapsed
   text space between them and read as one two-word chip at a glance,
   which is the opposite of the point — the whole reason there are two is
   that they are two different colours. Margin rather than a flex gap on
   the parent, because the parent is a bare <div> shared with the
   single-chip case and giving it display:flex would change how the
   sibling <h4> sits above it. */
.pa-plan + .pa-plan{ margin-left:8px; }

/* --- works with --------------------------------------------------------
   The sibling products, at the foot of the article. This used to be three
   text links in the fact column, where it competed with the section list
   for the same 250px of rail and lost — a reader following the marker
   down the list never looked past it.

   It sits outside .pa-body rather than inside the article, so it runs the
   full width of the shell instead of the 68ch the prose is measured to.
   That is the whole reason the captures are worth showing: at article
   width three screenshots are stamps, and a stamp of a form looks exactly
   like a stamp of a chart.

   auto-fit rather than three fixed tracks, because lead-scanner lists two
   siblings and not three — ../pricing/ was in its list and is not a
   product with a capture. A fixed repeat(3) would leave that page with a
   hole where the third card is not. */
.pa-works{
  margin:clamp(48px,6vw,78px) 0 0;
  border-top:1px solid var(--edge);
  padding-top:clamp(26px,3.2vw,38px);
}

/* Sized as a label rather than as a heading. It is an <h2> because it
   introduces a section and the document outline should say so, but a
   32px display face here would announce it as the most important thing
   on the page, which it is not — the article above it is. */
.pa-works > h2{
  font-family:var(--mono); font-size:11px; letter-spacing:.12em;
  text-transform:uppercase; font-weight:500; color:var(--ash);
  margin:0 0 clamp(16px,2vw,22px);
}
.pa-works ul{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:clamp(14px,2vw,22px);
}

/* The whole card is the link, not just the name under it. A thumbnail
   that is not clickable is a thumbnail every reader clicks anyway. */
.pa-works a{
  display:grid; gap:11px;
  border:1px solid var(--edge); background:var(--graphite);
  padding:10px 10px 13px;
  transition:border-color .2s ease, background .2s ease, transform .2s ease;
}
.pa-works a:hover{
  border-color:var(--g); background:var(--steel); transform:translateY(-2px);
}
.pa-works a:focus-visible{ outline:2px solid var(--g); outline-offset:2px; }

/* height:auto against the intrinsic width/height on the <img> keeps the
   aspect ratio the file actually has, so the row reserves the right space
   before the capture loads and nothing shifts under the reader. All seven
   captures are 2200×1140, so the cards line up without being cropped to
   a ratio that would cut UI off the edge of a screenshot. */
.pa-works img{
  display:block; width:100%; height:auto;
  border:1px solid var(--edge);
}
.pa-works span{
  font-size:13.5px; font-weight:500; color:var(--bone); padding:0 2px;
  transition:color .2s ease;
}
.pa-works a:hover span{ color:var(--g); }

/* ===================== RESPONSIVE ===================== */
@media (max-width:960px){
  /* One column. The media panel is forced back to the top on the flipped
     blocks — `order:2` would otherwise put a screenshot BELOW its own
     caption on every other block, which is the one arrangement that reads
     as a mistake rather than as a variation. */
  .pr-b{ grid-template-columns:minmax(0,1fr); }
  .pr-b:nth-child(even) .pr-media{ order:0; }
  .pr-media{ min-height:0; }
  .pr-plans{ margin-top:6px; }

  /* The fact column stops being a column. Left as a sidebar it would be
     250px of short lines beside a full-width article, and sticky
     positioning on an element taller than the viewport pins nothing. It
     goes under the article, and its groups run across instead of down. */
  .pa-body{ grid-template-columns:minmax(0,1fr); }
  .pa-side{ position:static; grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
    border-top:1px solid var(--edge); padding-top:26px; }

  /* The marker goes with the sticky positioning that justified it. Down
     here the list has already scrolled past the reader by the time they
     reach it, so a rule pointing at where they were is answering a
     question nobody is still asking. The script keeps running and
     aria-current stays correct — that is a true statement about the
     document at any width — but there is nothing to draw. */
  .pa-side .pa-toc{ padding-left:0; }
  .pa-toc[data-spy]::before,
  .pa-toc[data-spy]::after{ display:none; }
}

@media (max-width:640px){
  .pa-stats{ grid-template-columns:1fr; gap:18px; }
  .pa-split{ grid-template-columns:1fr; }
  /* The table is two columns of short values; it fits. What does not fit
     is nowrap on a currency figure inside a 320px viewport. */
  .pa-calc td{ white-space:normal; }
  .pa-calc th, .pa-calc td{ padding:10px 11px; font-size:12.5px; }
}

/* A cross-fade is exactly the kind of thing prefers-reduced-motion is
   asking about, but the swap itself is information, not decoration —
   removing it would leave the tiles pointing at a panel that never
   changes. So the transition goes and the change stays: the capture cuts
   straight to the new one. */
@media (prefers-reduced-motion:reduce){
  .pr-stack > .pr-shot,
  .pr-stack > .pr-shot[data-on]{ transition:none; }
  .pr-sub-t, a.pr-sub-t::after{ transition:none; }
  .pr-sub-t:hover::after,
  .pr-sub-t:focus-visible::after,
  .pr-sub-t[data-on]::after{ transform:none; }

  /* Same reasoning for the band: where it is says which capture is showing,
     so it stays; the slide between tiles is the decoration, so it goes and
     the band cuts across. Script reads the same query and holds the strip
     still rather than advancing it on a timer, so under this setting the
     band only ever moves because the reader moved it. */
  .pr-sub::before{ transition:none; }

  /* The card lift is decoration and nothing else — the border and the
     name already change colour, so hover stays legible without it. */
  .pa-works a{ transition:border-color .2s ease, background .2s ease; }
  .pa-works a:hover{ transform:none; }
}

@media (max-width:720px){
  .pr-key{ grid-template-columns:1fr; }
  .pr-key > div{ padding:18px 0; }
  .pr-key > div + div{ border-left:0; border-top:1px solid var(--edge); padding-left:0; }
  .pr-key > div:not(:last-child){ padding-right:0; }

  /* The plan strip stays three across at every width. It is three short
     words and it is the page's whole colour key in miniature — stacked it
     becomes three rows of near-identical text and stops being a strip you
     read at a glance. */
  .pr-plans li{ padding:11px 8px; }
  .pr-t-n{ font-size:9px; letter-spacing:.1em; }
  .pr-t-s{ font-size:8.5px; letter-spacing:.1em; }

  /* The sub-feature strip DOES stack here, unlike the plan strip above it,
     and for the opposite reason. The plan strip is three one-word labels
     that stay legible squeezed; these are two- and three-word phrases in
     four columns, which at 320px is one word per line and a tile four
     lines tall. Stacked they are four full-width rows — which is also the
     only shape in which they are a comfortable tap target, and on a touch
     screen tapping is the only thing they do. */
  .pr-sub{ grid-auto-flow:row; }
  .pr-sub li + li{ border-left:0; border-top:1px solid var(--edge); }

  /* The strip turned on its side, so the band does too: one row tall,
     full width, travelling down instead of across. Same --i and --n, same
     percentage trick against its own box — only the axis changes, so
     nothing in script has to know which shape it is looking at. */
  .pr-sub::before{
    width:100%; height:calc(100% / var(--n,1));
    transform:translateY(calc(var(--i,0) * 100%));
  }
  .pr-sub-t{ padding:11px 22px 11px 12px; }
  a.pr-sub-t::after{ top:11px; }
}

/* ═══════════════ PAGE TRANSITION: INDEX ⇄ ARTICLE ═══════════════

   Clicking "Read more" on a product block slides the article up over the
   page from the bottom edge, and going back slides it down again. This is
   the browser's cross-document View Transitions API, not a script.

   WHY IT IS WORTH HAVING AT ALL

   The article is not a different place, it is more of the same place. A
   normal navigation says otherwise: the block you were reading is gone,
   the scroll position is gone, and the article arrives with no memory of
   having been asked for. Sliding it up over the top says the block is
   still underneath, which is true — Back really does put you on it.

   WHY THE DIRECTION IS FREE

   In a cross-document transition BOTH snapshots — the outgoing page and
   the incoming one — are styled by the stylesheet of the page being
   navigated TO. So the direction does not need to be computed, passed
   between documents, or held in sessionStorage: the rule that runs is
   simply whichever page you are arriving at.

     arriving at the article  →  html[data-page="article"]  →  rises
     arriving at the index    →  html[data-page="index"]    →  sinks

   That is the whole mechanism, and it is why the two <html> elements
   carry data-page. Do not remove those attributes.

   WHY IT DOES NOT LEAK INTO THE REST OF THE SITE

   A cross-document transition only happens when BOTH documents opt in
   with @view-transition. That declaration lives here, and this file is
   loaded only by the products index and its articles. Navigating
   products → pricing gets the ordinary instant load, because pricing
   never opted in. Adding the transition to another page is therefore a
   deliberate act — load this file, or move the at-rule — and not
   something that happens by accident.

   THE ONE CASE THIS CANNOT DECIDE ALONE

   Direction is read off the page being arrived at, so a navigation that
   ends where it began still matches a rule: clicking "Products" while
   already on the index arrives at data-page="index" and plays the
   article-leaving motion over nothing. js/pv-scope.js cancels the
   transition in that case, and the products pages load it for that
   reason alone. Removing it brings the fault back.

   WITHOUT SUPPORT

   Browsers that do not implement the API ignore @view-transition and the
   ::view-transition-* pseudo-elements entirely, and the link is a link.
   Nothing here is load-bearing; it is the navigation with a manner. */
@view-transition{ navigation:auto; }

/* The two snapshots are siblings in a pseudo-element tree, and by default
   the browser cross-fades them and paints them in a blend group. Both
   have to go: at 40% through a slide, a half-transparent copy of the old
   page showing through the new one is not a slide, it is a smear. */
::view-transition-old(root),
::view-transition-new(root){
  mix-blend-mode:normal;
  animation:none;
}
::view-transition-image-pair(root){ isolation:auto; }

/* Arriving at the article: it comes up from below its own full height and
   lands, while the page it covered drifts up a little and dims. The
   parallax is small on purpose — the outgoing page is meant to read as
   something still there behind the article, not as a second thing moving.

   The easing is a decelerating curve rather than ease-out, so the panel
   is quick to commit and slow to settle, which is what makes it feel like
   it was thrown rather than played.

   On duration: the two directions are not the same length, and that is
   deliberate rather than an oversight. Forward is the move you asked for
   and it is worth watching, so it gets the longer 1050ms. Back is a move
   you want completed, not admired — a slow exit reads as the page being
   reluctant — so it runs at 880ms.

   These are long for a page transition, and that is the point. The
   easing spends most of its budget decelerating, so the panel covers the
   screen early and then settles; what the extra time buys is the settle,
   not the travel. Read the number against the curve, not on its own — at
   this easing, 1050ms does not feel like a second of waiting, and the
   same 1050ms on a linear curve would.

   The floor is around half a second. Much under it and the panel stops
   appearing to come from anywhere: it simply arrives, and the whole
   reason for doing this instead of a normal navigation is gone. If it
   ever needs shortening, shorten it toward 700ms rather than back to the
   400ms range. */
html[data-page="article"]::view-transition-new(root){
  animation:pv-rise 1.05s cubic-bezier(.22,1,.36,1) both;
  z-index:2;
}
html[data-page="article"]::view-transition-old(root){
  animation:pv-recede 1.05s cubic-bezier(.22,1,.36,1) both;
  z-index:1;
}

/* Arriving back at the index: the same move played backwards, so Back
   undoes what the click did rather than performing a second forward
   motion. The article is the one that moves — it slides down and off —
   and the index is revealed underneath, which is the true relationship
   between the two. */
html[data-page="index"]::view-transition-old(root){
  animation:pv-sink .88s cubic-bezier(.55,0,.85,.4) both;
  z-index:2;
}
html[data-page="index"]::view-transition-new(root){
  animation:pv-return .88s cubic-bezier(.22,1,.36,1) both;
  z-index:1;
}

@keyframes pv-rise{
  from{ transform:translateY(100%); }
  to  { transform:translateY(0); }
}
@keyframes pv-recede{
  from{ transform:translateY(0) scale(1); opacity:1; }
  to  { transform:translateY(-6%) scale(.985); opacity:.5; }
}
@keyframes pv-sink{
  from{ transform:translateY(0); }
  to  { transform:translateY(100%); }
}
@keyframes pv-return{
  from{ transform:translateY(-6%) scale(.985); opacity:.5; }
  to  { transform:translateY(0) scale(1); opacity:1; }
}

/* A full-page slide is the most literal possible reading of "motion",
   and this one is decoration — the navigation happens either way. So
   under reduced-motion the pages swap with a short fade, which keeps the
   two views from cutting harshly without moving anything across the
   screen.

   The at-rule above cannot be turned off from in here, which is fine:
   opting in is not what causes the motion, these keyframes are. */
@media (prefers-reduced-motion:reduce){
  html[data-page="article"]::view-transition-new(root),
  html[data-page="index"]::view-transition-new(root){
    animation:pv-appear .2s linear both;
  }
  html[data-page="article"]::view-transition-old(root),
  html[data-page="index"]::view-transition-old(root){
    animation:pv-appear .2s linear reverse both;
  }
  @keyframes pv-appear{ from{ opacity:0; } to{ opacity:1; } }
}

/* --- RMSConnect readout ------------------------------------------------
   RMSConnect runs on the technician's laptop, not in the web app, so there
   is no capture for tools/shoot-products.mjs to take. The readout is drawn
   in HTML from the tool's own output format and hint wording, in the
   block's plan hue like every other Premium product. Used as the fourth
   capture in the Multi-device jobs block and as the figure in that article.

   Every rule is prefixed with .pr-rc because the article's .pa-doc p
   (0,1,1) would otherwise restyle the rows as body copy. */
.pr-rc{ display:flex; flex-direction:column; font-family:var(--mono); }
.pr-rc .pr-rc-bar{
  display:flex; align-items:center; gap:7px; padding:12px 16px;
  border-bottom:1px solid var(--edge); background:var(--graphite);
  font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--ash);
}
.pr-rc .pr-rc-bar i{ width:9px; height:9px; border-radius:50%; background:var(--edge); }
.pr-rc .pr-rc-bar i:first-child{ background:var(--g); }
.pr-rc .pr-rc-bar span{ margin-left:8px; color:var(--bone); }
.pr-rc .pr-rc-bar em{ margin-left:auto; font-style:normal; color:var(--g); }
.pr-rc .pr-rc-bar em::before{ content:"\2713\00a0\00a0"; }
.pr-rc .pr-rc-body{ padding:clamp(14px,2vw,20px); display:grid; gap:10px; }
.pr-rc p.pr-rc-row{
  margin:0; max-width:none; display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 10px;
  padding:10px 12px; border:1px solid var(--edge); background:var(--graphite);
  font-size:13px; line-height:1.5; color:var(--bone);
}
.pr-rc .pr-rc-row b{ min-width:11ch; font-weight:500; color:var(--bone); }
.pr-rc .pr-rc-row > span{ color:var(--g); }
.pr-rc .pr-rc-row small{ font-size:11.5px; color:var(--ash); }
.pr-rc p.pr-rc-row[data-silent]{ border-color:color-mix(in srgb, var(--g) 45%, var(--edge)); }
.pr-rc p.pr-rc-why{
  margin:0; max-width:none; padding:10px 12px;
  border:1px dashed color-mix(in srgb, var(--g) 45%, var(--edge));
  font-size:12.5px; line-height:1.5; color:var(--bone);
}
.pr-rc .pr-rc-why span{
  display:inline-block; margin-right:8px; padding:1px 6px; background:var(--g); color:var(--void);
  font-size:10px; font-weight:700; letter-spacing:.12em; text-transform:uppercase;
}
.pr-rc p.pr-rc-foot{
  margin:auto 0 0; max-width:none; padding:12px 18px 16px;
  font-size:10.5px; line-height:1.4; letter-spacing:.08em; text-transform:uppercase; color:var(--ash-dim);
}
/* In the tile strip the readout stands in for a 16:10 capture, so it has
   to keep that shape at every width or the stack would grow to fit it and
   every screenshot in the block would sit in a taller frame. Sized in cqi
   against its own width, it scales like an image: the numbers are the px
   values above divided by a 480px reference width. */
.pr-stack > .pr-rc{ aspect-ratio:16/10; container-type:inline-size; overflow:hidden; }
.pr-stack > .pr-rc .pr-rc-bar{ padding:2.5cqi 3.3cqi; gap:1.45cqi; font-size:2.3cqi; }
.pr-stack > .pr-rc .pr-rc-bar i{ width:1.9cqi; height:1.9cqi; }
.pr-stack > .pr-rc .pr-rc-bar span{ margin-left:1.7cqi; }
.pr-stack > .pr-rc .pr-rc-body{ padding:2.9cqi; gap:1.7cqi; }
.pr-stack > .pr-rc p.pr-rc-row{ padding:1.7cqi 2.5cqi; gap:.8cqi 2.1cqi; font-size:2.7cqi; }
.pr-stack > .pr-rc .pr-rc-row small{ font-size:2.4cqi; }
.pr-stack > .pr-rc p.pr-rc-why{ padding:1.7cqi 2.5cqi; font-size:2.6cqi; }
.pr-stack > .pr-rc .pr-rc-why span{ margin-right:1.7cqi; padding:.2cqi 1.25cqi; font-size:2.1cqi; }
.pr-stack > .pr-rc p.pr-rc-foot{ padding:1.7cqi 3.75cqi 2.5cqi; font-size:2.2cqi; }
/* Shown from the tile strip, the scan plays: rows arrive one at a time the
   way the tool reports them, and replay each time the tile is lit. */
@keyframes pr-rc-in{ from{ opacity:0; transform:translateY(6px); } }
.pr-stack > .pr-rc[data-on] .pr-rc-body > *{ animation:pr-rc-in .45s cubic-bezier(.2,.7,.2,1) backwards; }
.pr-stack > .pr-rc[data-on] .pr-rc-body > :nth-child(2){ animation-delay:.25s; }
.pr-stack > .pr-rc[data-on] .pr-rc-body > :nth-child(3){ animation-delay:.5s; }
.pr-stack > .pr-rc[data-on] .pr-rc-body > :nth-child(4){ animation-delay:.85s; }
@media (prefers-reduced-motion:reduce){ .pr-stack > .pr-rc[data-on] .pr-rc-body > *{ animation:none; } }
