/* ============================================================================
   ECOLAB · WALMART SUPPLIER WUEI — STAGE
   ----------------------------------------------------------------------------
   Everything the experience needs, and nothing the host page provides. This
   file must stay self-contained: the stage ships inside an iframe with no
   Ecolab chrome around it, so it may not depend on chrome.css or on any style
   the review page happens to supply.

   Rules enforced here, inherited from the Virtual Enzyme Experience RND 03:
     - every selector is scoped under #wuei-stage, so nothing leaks to a host
     - the reset is scoped too; there is no bare `*` or `body` rule
     - nothing is position:fixed
     - --u is a REAL pixel, never a container fraction. A container-derived
       type size cancels browser zoom exactly — measured x1.000 at 200% on the
       enzyme build before it was corrected — and no amount of tuning fixes
       that. Fixed px zooms properly; the cost is that the composition has to
       REFLOW below 1376 rather than scale, which the mobile mode does.

   GEOMETRY MODEL
   The design frame is 1376 x 1215 (as of 2026-08-21; 860 in RND 5, 774 before
   that — see the min-height note below), so every measurement below is
   the literal number from the file:  width: calc(1296 * var(--u)).
   ========================================================================== */

#wuei-stage {
  /* inline-size, not size: only cqw is ever used, and inline-size is what lets
     the stage take an auto height in mobile mode. `size` needs a definite
     height in both axes and would collapse the moment the ratio comes off. */
  container-type: inline-size;
  container-name: stage;
  position: relative;
  width: 100%;

  /* The stage is its own query container, so a screen can respond to the width
     the STAGE actually has rather than the viewport's — the two are different
     on the review page, where the deck sits inside the Ecolab chrome. Same
     declaration the enzyme deck carries. */
  container-type: inline-size;
  container-name: stage;

  /* 1376 is Ecolab's own content column, so capping here costs nothing in the
     real embed and is what lets browser zoom work. */
  max-width: calc(1376 * var(--u));
  margin-inline: auto;

  /* ONE SHARED HEIGHT. The tracker, all five screens and the nav share a
     single grid cell, so the cell — and therefore the stage — is always as
     tall as its tallest occupant. The box cannot jump between screens, and if
     a reader's text-spacing settings grow one screen, every screen grows with
     it. That is what makes the iframe contract honest: one height, one aspect
     ratio, correct on all five.

     1215 AT 1376, AND NO LONGER A NAMED RATIO AT ALL. It was 774 (16:9) through
     RND 4 and 860 (16:10) in RND 5; the client's round of 2026-08-20 added a
     six-line caption, two band definitions, a footnote and a fourth reading to
     the result screen, and the result screen now asks for 1171 at the cap. 1215 gives
     it 44, which is the room a reader's own text-spacing sheet needs.

     The last 56 of that came from spacing rather than copy: the four blocks
     at the foot of the result screen sat 8 to 12 apart, inside their own
     leading, and read as one grey mass. They are graded now, with a rule
     where the subject changes. Air was the point, so it is not slack to
     reclaim later.

     THE HEIGHT IS CONTENT-DRIVEN NOW AND THE README SAYS SO. Chasing a tidy
     ratio would mean cutting the client's copy to fit a number, which is
     backwards — and the host is sent the real height on every change, so
     nothing downstream depends on the shape. Below the cap the stage grows as
     the copy wraps: measured, the result screen wants about 1,043 at a 1064
     stage. That is expected, not a defect.

     The 16:9 history, kept because three screens still narrate it: It was 774 — exactly 16:9
     — and the client's round of 2026-08-14 added a scope instruction and a
     third guidance step to the calculator and rebased the rail on the result
     screen, which took the tallest screen past it. The instruction was to give
     the content room rather than compress it back, so the ratio moved instead
     of the type.

     Measured across the desktop range with the worst-case content — the
     longest segment labels, an off-scale reader so every status string is at
     full length — the tallest screen asks for 801 at 1376 and 807 at 1024. 860
     clears that by 53, which is the room for a reader's own text-spacing sheet
     rather than slack.

     THE EMBED DOES NOT CARE, and that is why this is safe to move: the host
     is sent the real height over postMessage on every change (see reportHeight
     below), so an iframe sized from that message follows this number wherever
     it goes. What breaks if anyone hard-codes a 56.25% padding-top box around
     it is the 16:9 assumption, not the deck — the README states 16:10 now. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  min-height: calc(1215 * var(--u));

  background: var(--surface-page);

  --u: 1px;

  /* --- brand ------------------------------------------------------------ */
  --brand-blue: #006bd3;
  --brand-navy: #093675;
  /* 6.19:1 with white. It was #0a7bef, a LIGHTER blue than --brand-blue, which
     took the deck's only forward control from 5.19 down to 4.12 and failed
     1.4.3 in the exact state a reader is in with the pointer on Next. Hover now
     darkens, which is also what .cx-help-sum already does with --endeavour. */
  --blue-hover: #0a5fbe;
  --endeavour: #044ea0;

  /* --- ink -------------------------------------------------------------- */
  --ink-body: #32373e;
  --ink-strong: #313233;
  /* 5.32:1 on white, 4.91 on --band, 4.59 on --pill-tint. It was #6e7680,
     which is 4.60 on white and was signed off on that number alone — but this
     role also sets on --band, where it measures 4.25 and fails 1.4.3 for
     normal text. Three roles were sitting on it: the calculator readout's
     "Your Water Use Efficiency score" and unit, and all five keys of the
     close screen's recap.
     Darkened until the WORST of its three grounds clears 4.5, not the best. */
  --meta: #646c76;
  --ink-inverse: #ffffff;
  /* A lighter tone for secondary text ON the navy surface, so a sub-line can
     sit below a white heading without competing with it. 8.51:1 on
     --brand-navy, well past AA for body. The pin preview referenced this token
     by name and it was never defined, so its sub-line fell back to inherited
     white and printed identically to the two lines around it. */
  --tint-on-navy: #cfddf2;

  /* --- the benchmark rail ------------------------------------------------
     The three band colours from the CDP Operational Water Performance build,
     so this deck and that page draw the same object the same way. They are
     FILLS on a graphic whose meaning is carried in the legend beside it and
     in the .sr table under it, so none of them owes a contrast ratio; the
     figures and labels around the rail are what 1.4.3 measures, and those are
     ink on white. */
  --rail-loc: #6aa5ee;
  --rail-ent: #2d5a8e;
  --rail-sector: #d5d9dd;

  /* --- surface ---------------------------------------------------------- */
  --surface-page: #ffffff;
  --band: #f4f6f8;
  --pill-tint: #e3f0fc;
  --surface-inactive: #d9d9d9;
  /* --brand-blue #006bd3 flattened at 30% over white. It is the exact colour
     the blocked Next button already showed via opacity:0.3; it exists as a
     paint value so the button can stop using opacity — see the [aria-disabled]
     rule, where opacity was silently dimming the focus ring too. */
  --btn-off: #b3d3f2;

  /* --- line -------------------------------------------------------------
     Fill and boundary are separate tokens and must stay separate. #D9D9D9 as
     a BOUNDARY measures 1.41:1 on white and fails 1.4.11; as a FILL it is a
     surface and owes no ratio at all. Darkening the single token to fix the
     borders is what broke the first enzyme remediation round. */
  --band-line: #dbe3ea;
  /* 3.45:1 on white and 3.19 on --band. It was #949494, which is 3.03 on white
     — deliberately at the 1.4.11 minimum — but 2.80 on --band, and the segment
     card's hover fill IS --band. That put the unselected card's empty
     selection disc under the bar in the one state where a reader is actively
     choosing. Same reasoning as --meta: measured against its worst ground. */
  --boundary: #8a8a8a;
  --progress-track: rgba(0, 107, 211, 0.2);

  /* --- data visual ------------------------------------------------------- */
  --sky: #80b5e9;
  --tint-enz: #5b96d8; /* 3.09:1 — AA non-text */

  /* --- hazard ------------------------------------------------------------
     Caustic red is the house "this is the problem side" colour, carried over
     from the enzyme build where it means exactly that. Here it marks a flagged
     area and an over-target bar. 5.73:1 on white. */
  --caustic-red: #c4262e;

  /* --- type ------------------------------------------------------------- */
  --font-sans: "Roboto", system-ui, sans-serif;
  --font-mono: "Roboto Mono", ui-monospace, monospace;

  /* --- motion -----------------------------------------------------------
     Three curves only, per the Ecolab house vocabulary. The fourth is for the
     single focal element that is allowed to overshoot. */
  --ease-fade: ease;
  --ease-size: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-travel: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-state: 0.2s;
  --dur-enter: 0.7s;
  --dur-build: 1s;
  --stagger: 0.13s;

  font-family: var(--font-sans);
  color: var(--ink-body);
  -webkit-font-smoothing: antialiased;
  text-size-adjust: 100%;

  /* Last-resort guard against a word leaving the frame under the 1.4.12
     spacing sheet. `anywhere`, not `break-word`: they break identically on
     screen, but only `anywhere` also reduces a box's MIN-CONTENT width, which
     is what matters for flex items whose automatic minimum size is their
     longest unbreakable word. */
  overflow-wrap: anywhere;
}

/* Scoped reset. Deliberately not a bare `*` — this must not reach a host. */
#wuei-stage *,
#wuei-stage *::before,
#wuei-stage *::after {
  box-sizing: border-box;
}
#wuei-stage p,
#wuei-stage h1,
#wuei-stage h2,
#wuei-stage h3,
#wuei-stage h4,
#wuei-stage figure,
#wuei-stage ol,
#wuei-stage ul,
#wuei-stage dl,
#wuei-stage dd {
  margin: 0;
}
/* lists are structural here, never bullets. The UA's 1em margin and 40px
   padding-left silently shift the tracker down and squeeze its segments. */
#wuei-stage ol,
#wuei-stage ul {
  padding: 0;
  list-style: none;
}
#wuei-stage img {
  display: block;
  max-width: 100%;
}
#wuei-stage button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
#wuei-stage input {
  font: inherit;
  color: inherit;
}
#wuei-stage :focus-visible {
  outline: calc(3 * var(--u)) solid var(--brand-navy);
  outline-offset: calc(2 * var(--u));
}
/* ...except the screen container. stage.js gives each screen tabindex="-1" and
   focuses it on every navigation so AT announces the new screen. It is a
   programmatic target, never a tab stop, so 2.4.7 does not ask for a ring —
   and the rule above was drawing one around the whole stage on load. */
#wuei-stage .screen:focus,
#wuei-stage .screen:focus-visible {
  outline: none;
}

/* ============================================================================
   SCREENS
   All five live in the DOM at once. Only the active one is visible or
   focusable; the rest are inert so keyboard and AT never reach them.
   ========================================================================== */

#wuei-stage .screen {
  /* visibility:hidden, not display:none — a screen that is out of flow
     contributes no height, and the shared-height promise depends on every
     screen being measured. `inert` closes the gap for anything that ignores
     visibility for focus purposes. */
  grid-area: 1 / 1;
  min-width: 0;

  /* The tracker occupies the top 44 (y38 + 6) and the nav the bottom 68
     (48 tall + 20 clear). Both overlay this cell, so every screen reserves
     that space rather than being positioned around it. */
  padding: calc(96 * var(--u)) calc(40 * var(--u)) calc(88 * var(--u));

  display: flex;
  flex-direction: column;

  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.34s var(--ease-fade),
    visibility 0s linear 0.34s;
}
#wuei-stage .screen:not(.on) {
  pointer-events: none;
}
#wuei-stage .screen.on {
  opacity: 1;
  visibility: visible;
  transition:
    opacity 0.34s var(--ease-fade),
    visibility 0s;
}

/* ============================================================================
   PROGRESS TRACKER
   Five segments across the 1296 content column with 2px dividers. Built with
   flex so it resolves to exactly 1296 rather than reproducing a rounding gap.

   NO EDGE ON THE UNFILLED SEGMENT, and that is deliberate — do not "fix" it.
   1.4.11 covers interface COMPONENTS and GRAPHICS REQUIRED TO UNDERSTAND THE
   CONTENT. This tracker is neither: it carries no controls, and it is
   aria-hidden, which is the build declaring it is not required to understand
   anything. Progress is independently carried by Next/Back and by each screen
   naming itself. If the tracker is ever given a real accessible presence, the
   3:1 edge has to come back with it. The two go together.
   ========================================================================== */

#wuei-stage .track {
  grid-area: 1 / 1;
  align-self: start;
  margin: calc(38 * var(--u)) calc(40 * var(--u)) 0;
  height: calc(6 * var(--u));
  display: flex;
  gap: calc(2 * var(--u));
  z-index: 5;
}
#wuei-stage .track li {
  flex: 1;
  background: var(--progress-track);
  overflow: hidden;
}
#wuei-stage .track li i {
  display: block;
  height: 100%;
  background: var(--brand-navy);
  transform: scaleX(0);
  transform-origin: left;
}
/* Segments already behind you are history — they snap, they do not replay. */
#wuei-stage .track li.done i {
  transform: scaleX(1);
}
/* MOVE 4, GEOMETRY GROWTH. The segment for the screen you just landed on wipes
   left to right on the size curve, so the tracker fills as the screen builds
   rather than blinking to a new colour. */
#wuei-stage .track li.now i {
  background: var(--brand-blue);
  transform: scaleX(1);
  transition: transform 0.9s var(--ease-size) 0.15s;
}

/* ============================================================================
   NAV
   Pinned bottom-right by alignment, Next's right edge landing on the 40px
   margin. The gate hint sits to their left, in the same row.
   ========================================================================== */

/* The row spans the full content column rather than hugging the right edge,
   because it now carries the attribution on the left as well as the controls
   on the right. Both sit on one baseline, which is what stops the credit line
   floating at whatever height its screen's content happened to end at. */
#wuei-stage .nav {
  grid-area: 1 / 1;
  align-self: end;
  justify-self: stretch;
  margin: 0 calc(40 * var(--u)) calc(20 * var(--u));
  display: flex;
  align-items: center;
  gap: calc(12 * var(--u));
  z-index: 4;
}
#wuei-stage .cdp {
  margin-right: auto;
  font: 400 calc(11 * var(--u)) / 1.4 var(--font-sans);
  letter-spacing: calc(0.6 * var(--u));
  color: var(--meta);
  max-width: calc(560 * var(--u));
}
/* THE GATE LINE. Sits between the attribution and Back, which puts it hard
   against the controls it explains rather than adrift in the middle of the row.
   The nav is a flex row and .cdp already carries margin-right:auto, so
   everything after it clusters right by construction — this needs no alignment
   of its own, only a max-width so a long reason cannot start eating into the
   attribution.

   MONO AND --meta, NOT RED. It is a statement about what is not finished yet,
   not a validation error against something the reader got wrong, and dressing
   an ordinary incomplete form in error colour teaches people to ignore the
   colour. --meta measures 5.32:1 on white. The mono face is the deck's
   instrumentation voice, which is what the tracker and every .label already
   use for "the machine is telling you something".

   [hidden] NEEDS ITS OWN LINE because this rule sets display — the trap this
   file documents on .btn and the plant view hit on .pv-body. stage.js sets
   .hidden when there is no reason to give, and without the guard below the
   line would simply never go away. */
#wuei-stage .navhint {
  display: inline-flex;
  align-items: center;
  max-width: calc(430 * var(--u));
  margin-right: calc(4 * var(--u));
  font: 500 calc(12.5 * var(--u)) / 1.35 var(--font-mono);
  letter-spacing: calc(0.4 * var(--u));
  color: var(--meta);
  text-align: right;
}
#wuei-stage .navhint[hidden] {
  display: none;
}

#wuei-stage .btn {
  display: inline-flex;
  align-items: center;
  gap: calc(10 * var(--u));
  height: calc(48 * var(--u));
  padding: 0 calc(30 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--brand-blue);
  color: var(--ink-inverse);
  font: 500 calc(20 * var(--u)) / 1 var(--font-sans);
  white-space: nowrap;
  transition:
    transform 0.22s var(--ease-fade),
    background var(--dur-state) var(--ease-fade),
    opacity var(--dur-state) var(--ease-fade);
}
#wuei-stage .btn:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(calc(-2 * var(--u)));
  background: var(--blue-hover);
}
/* [aria-disabled] as well as :disabled — the nav's Next is now the former, so
   it stays focusable and can carry the reason it is blocked.

   NOT opacity, AND THAT IS THE WHOLE POINT. This was `opacity: 0.3`, which
   dims the element and EVERYTHING PAINTED WITH IT — including the focus ring.
   The shared indicator is --brand-navy #093675; at 30% over white it composites
   to rgb(181,195,214), which measures 1.79:1 against the page. So the one
   control this design deliberately keeps focusable, precisely so it can carry
   the reason it is blocked, was the one control a keyboard reader could not
   see they had reached. 2.4.7 asks 3:1.

   --btn-off is that same 30% blue precomputed as a flat fill, so the button
   looks identical to before and the outline is drawn at full strength on top
   of it: navy on white is 12.31:1.

   The label stays white, as it appeared before. Its contrast on the pale fill
   is poor and that is conformant — 1.4.3 exempts disabled controls — and the
   gate line beside the button carries the words anyway. */
#wuei-stage .btn:disabled,
#wuei-stage .btn[aria-disabled="true"] {
  background: var(--btn-off);
  color: var(--ink-inverse);
  cursor: default;
}
#wuei-stage .btn.ghost:disabled,
#wuei-stage .btn.ghost[aria-disabled="true"] {
  background: transparent;
  color: var(--boundary);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) var(--boundary);
}
/* display:inline-flex outranks the UA's [hidden]{display:none}, so a hidden
   button would still paint. The cover has no Back button. */
#wuei-stage .btn[hidden] {
  display: none;
}
#wuei-stage .btn.ghost {
  background: transparent;
  color: var(--brand-navy);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) var(--brand-navy);
}
#wuei-stage .btn.ghost:hover:not(:disabled) {
  background: rgba(9, 54, 117, 0.06);
}
#wuei-stage .btn .chev {
  width: calc(5 * var(--u));
  height: calc(12 * var(--u));
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}
/* Small ghost pill, for in-screen controls rather than deck navigation. */
#wuei-stage .btn-sm {
  display: inline-flex;
  align-items: center;
  gap: calc(8 * var(--u));
  height: calc(38 * var(--u));
  padding: 0 calc(20 * var(--u));
  border-radius: calc(999 * var(--u));
  background: transparent;
  color: var(--brand-blue);
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) var(--brand-blue);
  font: 500 calc(15 * var(--u)) / 1 var(--font-sans);
  white-space: nowrap;
  transition:
    transform 0.22s var(--ease-fade),
    background var(--dur-state) var(--ease-fade),
    color var(--dur-state) var(--ease-fade);
}
#wuei-stage .btn-sm:hover:not(:disabled) {
  transform: translateY(calc(-2 * var(--u)));
  background: var(--brand-blue);
  color: var(--ink-inverse);
}
#wuei-stage .btn-sm:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ============================================================================
   MOTION — the house vocabulary, retimed for a click-through
   The reference builds trigger on scroll via IntersectionObserver. Nothing
   here scrolls, so the trigger is screen arrival. The timings, curves and
   stagger rates are unchanged from the CDP, Insights Hub and enzyme builds.
   ========================================================================== */

/* MOVE 1 — RISE AND FADE. Default entrance: 26px up, 700ms, staggered 0.13s. */
#wuei-stage .rev {
  opacity: 0;
  transform: translateY(calc(26 * var(--u)));
  transition:
    opacity var(--dur-enter) var(--ease-fade),
    transform var(--dur-enter) var(--ease-fade);
  transition-delay: var(--d, 0s);
}
#wuei-stage .screen.on .rev.in {
  opacity: 1;
  transform: none;
}

/* MOVE 2 — LINE-MASK RISE. Display headlines only. Each line rises from 112%
   below behind a mask, on the long expo-out curve. The slowest move here, and
   the only one that fires on arrival rather than as part of the stagger. */
#wuei-stage .lines .ln {
  display: block;
  overflow: hidden;
}
#wuei-stage .lines .ln > span {
  display: block;
  transform: translateY(112%);
  transition: transform 1s var(--ease-travel);
  transition-delay: var(--d, 0s);
}
#wuei-stage .screen.on .lines.in .ln > span {
  transform: none;
}

/* MOVE 4 — GEOMETRY GROWTH. Bars and tracks grow from zero on the size curve;
   any label inside is held transparent until the growth has stopped. */
#wuei-stage .grow {
  transition: transform var(--dur-build) var(--ease-size) var(--d, 0s);
  transform: scaleX(0);
  transform-origin: left;
}
#wuei-stage .screen.on .grow.in {
  transform: scaleX(1);
}

/* LEFT-TO-RIGHT BUILD. Text wipes in from its own left edge rather than fading
   in place, so a row of data reads as being written across.
   The transition lives on .in, NOT on the base state: these classes are added
   at runtime, so a transition on the hidden state would animate the HIDING
   too — opacity would spend its whole delay still visible and then fade out
   just as it was being asked to fade in. */
#wuei-stage .wipe {
  clip-path: inset(0 100% 0 0);
  opacity: 0;
}
#wuei-stage .screen.on .wipe.in {
  clip-path: inset(0 0 0 0);
  opacity: 1;
  transition:
    clip-path 0.55s var(--ease-size) var(--d, 0s),
    opacity 0.3s var(--ease-fade) var(--d, 0s);
}

/* RIPPLE DOWN. Blocks arrive from just above and settle, staggered top to
   bottom, on the travel curve. For elements that should land after the text
   they belong to has finished building. */
#wuei-stage .drop {
  opacity: 0;
  transform: translateY(calc(-10 * var(--u)));
}
#wuei-stage .screen.on .drop.in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.32s var(--ease-fade) var(--d, 0s),
    transform 0.44s var(--ease-travel) var(--d, 0s);
}

/* Reduced motion: land the finished state. Never hide, never freeze mid-build,
   and hard-set anything that was mid-transition. */
@media (prefers-reduced-motion: reduce) {
  #wuei-stage .rev,
  #wuei-stage .lines .ln > span,
  #wuei-stage .grow,
  #wuei-stage .wipe,
  #wuei-stage .drop {
    transition: none;
  }
  /* SPECIFICITY, not redundancy — do not merge these into the list above.
     .wipe and .drop declare their transition on the REVEALED state rather than
     the base one, for the reason given where they are defined, and
     `#wuei-stage .screen.on .wipe.in` is one id and four classes against the
     one id and one class of `#wuei-stage .wipe`. The shorthand above therefore
     loses the cascade and never applies.

     For .drop that is harmless: neither value it transitions actually differs
     between the two states under reduce, so nothing can animate. For .wipe it
     is a real failure — clip-path genuinely changes, from none to
     inset(0 0 0 0), so the wipe still played at full length for a reader who
     had asked for no motion. Seven of them on the close screen, three on the
     plant screen. Matching the specificity here is what actually turns it off. */
  #wuei-stage .screen.on .wipe.in,
  #wuei-stage .screen.on .drop.in {
    transition: none;
  }
  #wuei-stage .rev {
    opacity: 1;
    transform: none;
  }
  #wuei-stage .wipe {
    clip-path: none;
    opacity: 1;
  }
  #wuei-stage .drop {
    opacity: 1;
    transform: none;
  }
  #wuei-stage .lines .ln > span {
    transform: none;
  }
  #wuei-stage .grow {
    transform: scaleX(1);
  }
  /* .screen.on, NOT .screen. The state rule at the top of this file
     re-declares `transition` on .on, which is one class heavier, and a media
     query adds no specificity — so the kill switch lost to it on the only
     element that was actually animating and the cross-fade ran anyway for a
     reader who asked for no motion. This is the identical trap the SPECIFICITY
     note below fixed for .wipe and .drop; .screen.on was left out of it. Both
     selectors are listed so the unfocused state is landed too. */
  #wuei-stage .screen,
  #wuei-stage .screen.on {
    transition: none;
  }
  /* the tracker still shows position, it just arrives filled instead of wiping */
  #wuei-stage .track li.now i {
    transition: none;
  }
}

/* ============================================================================
   SHARED TYPE ROLES
   Sizes are the literal design values. Roles that recur across screens live
   here; anything used on exactly one screen lives in that screen's own block.
   ========================================================================== */

#wuei-stage .eyebrow {
  font: 700 calc(12 * var(--u)) / 1.2 var(--font-mono);
  letter-spacing: calc(2.2 * var(--u));
  text-transform: uppercase;
  color: var(--brand-navy);
}
/* THE EYEBROW AS A PILL. A shared modifier rather than three local ones,
   because it is the same object on the calculator and on the result and they
   have to stay identical — the step labels are the deck's spine and a reader
   crossing screens should not have to re-read them to know they are the same
   thing.

   inline-flex so it shrinks to its own words: .eyebrow is a <p>, and a
   full-width block would draw a tinted bar across the column instead of a
   label. --pill-tint is the house fill for exactly this, and navy on it
   measures 10.7:1, so the tint costs the label nothing in legibility. */
#wuei-stage .eyebrow.pill {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: calc(28 * var(--u));
  padding: 0 calc(14 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--pill-tint);
}
#wuei-stage .h-cover {
  font: 700 calc(66 * var(--u)) / calc(66 * var(--u)) var(--font-sans);
  color: var(--brand-blue);
}
#wuei-stage .h-screen {
  font: 700 calc(40 * var(--u)) / calc(41 * var(--u)) var(--font-sans);
  color: var(--brand-blue);
}
/* THE SAME CLIP THE COVER ALREADY FIXED, ON THE OTHER DISPLAY ROLE. 00-cover
   measured it and solved it for .h-cover and the fix was never carried across:
   Roboto's own ascent plus descent is 1.171em, so 40px of .h-screen needs 46.8
   inside a 41px line box, and MOVE 2's .ln mask is overflow:hidden, so it cuts
   the difference off the bottom. Every masked screen headline in the deck ends
   in a descender — "describes your facility?" and "Where that water is going."
   — so the tails of the y and the g were being shaved.

   Opened to 1.2 (48, which holds 46.8 with 1.2 to spare). The extra leading is
   taken back BELOW the block only, never above it: pulling the top up by 3.5
   as well made the headline overlap the step pill sitting over it by 3px on
   both calculator columns — a fix for one collision causing another. Whatever
   is above a headline is spacing somebody chose; what is below it is this
   rule's to reclaim. Scoped to .lines because only a masked headline clips. */
#wuei-stage .h-screen.lines {
  line-height: 1.2;
  margin-bottom: calc(-7 * var(--u));
}
#wuei-stage .h-screen.lines .ln + .ln {
  margin-top: calc(-7 * var(--u));
}
#wuei-stage .h-block {
  font: 500 calc(25 * var(--u)) / calc(29 * var(--u)) var(--font-sans);
  color: var(--brand-navy);
}
#wuei-stage .dek {
  font: 300 calc(22 * var(--u)) / calc(28 * var(--u)) var(--font-sans);
  color: var(--ink-body);
}
#wuei-stage .lead {
  font: 400 calc(18 * var(--u)) / calc(25 * var(--u)) var(--font-sans);
  color: var(--ink-body);
}
#wuei-stage .body {
  font: 400 calc(16 * var(--u)) / calc(24 * var(--u)) var(--font-sans);
  letter-spacing: calc(0.4 * var(--u));
  color: var(--ink-body);
}
#wuei-stage .caption {
  font: 400 calc(14 * var(--u)) / 1.6 var(--font-sans);
  color: var(--meta);
}
#wuei-stage .source {
  font: 400 calc(11 * var(--u)) / 1.4 var(--font-sans);
  letter-spacing: calc(0.6 * var(--u));
  color: var(--meta);
}
/* Mono instrumentation label. Uppercase, generously tracked — that is what
   makes it read as a label rather than as prose. */
#wuei-stage .label {
  font: 500 calc(11 * var(--u)) / 1.2 var(--font-mono);
  letter-spacing: calc(1.1 * var(--u));
  text-transform: uppercase;
  color: var(--meta);
}
#wuei-stage .tag {
  display: inline-flex;
  align-items: center;
  height: calc(18 * var(--u));
  padding: 0 calc(7 * var(--u));
  border-radius: calc(2 * var(--u));
  font: 500 calc(9.5 * var(--u)) / 1 var(--font-mono);
  letter-spacing: calc(0.5 * var(--u));
  text-transform: uppercase;
  color: var(--ink-inverse);
}
#wuei-stage .tag.navy {
  background: var(--brand-navy);
}

/* The big figure. Roboto Mono Bold stands in for Cartograph CF, which is the
   house statistic face but is not in the Ecolab font package — the style guide
   names mono as the agreed substitution and they are close enough that a
   missing licence cannot break a layout. */
#wuei-stage .stat {
  font: 700 calc(110 * var(--u)) / 1 var(--font-mono);
  color: var(--brand-blue);
  font-variant-numeric: tabular-nums;
}

/* Visually hidden but available to assistive tech. */
#wuei-stage .sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================================
   SHARED COMPONENTS
   Only the pieces that genuinely recur across screens. Everything else belongs
   to the screen that uses it.
   ========================================================================== */

/* --- field: a mono label over a bordered input. Calculator and close. ----- */
#wuei-stage .field {
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--u));
  min-width: 0;
}
#wuei-stage .field > input {
  height: calc(56 * var(--u));
  padding: 0 calc(20 * var(--u));
  border-radius: calc(6 * var(--u));
  background: var(--surface-page);
  border: calc(1.5 * var(--u)) solid var(--boundary);
  font: 500 calc(22 * var(--u)) / 1 var(--font-sans);
  color: var(--ink-body);
  transition: border-color 0.22s var(--ease-fade);
  width: 100%;
}
#wuei-stage .field > input::placeholder {
  color: var(--meta);
}
#wuei-stage .field > input:hover:not(:disabled) {
  border-color: var(--brand-blue);
}
#wuei-stage .field > input:disabled {
  background: var(--band);
  color: var(--meta);
  cursor: not-allowed;
}

/* --- card: white box, radius 10, perceivable boundary --------------------- */
#wuei-stage .card {
  background: var(--surface-page);
  border: calc(1 * var(--u)) solid var(--boundary);
  border-radius: calc(10 * var(--u));
}

/* --- panel: the best-practice card, blue head over a band body ------------ */
#wuei-stage .pcard {
  border-radius: calc(8 * var(--u));
  overflow: hidden;
  background: var(--band);
}
#wuei-stage .pcard > h4 {
  background: var(--brand-blue);
  color: var(--ink-inverse);
  font: 700 calc(13.5 * var(--u)) / calc(17 * var(--u)) var(--font-sans);
  padding: calc(9 * var(--u)) calc(14 * var(--u));
}
#wuei-stage .pcard > h4 em {
  display: block;
  font: 500 calc(11 * var(--u)) / 1.2 var(--font-mono);
  letter-spacing: calc(1 * var(--u));
  text-transform: uppercase;
  font-style: normal;
  opacity: 0.92;
  margin-bottom: calc(3 * var(--u));
}
#wuei-stage .pcard > p {
  padding: calc(11 * var(--u)) calc(14 * var(--u));
  font: 400 calc(13 * var(--u)) / calc(19 * var(--u)) var(--font-sans);
  color: var(--ink-body);
}

/* ============================================================================
   MOBILE MODE
   ----------------------------------------------------------------------------
   Below 1024px of stage width the 16:9 lock comes off and the deck becomes a
   portrait, auto-height, genuinely stacked layout.

   The type scale does NOT change. Shrinking type for mobile is what made
   1.4.4 unfixable on the enzyme build: zoom shrinks the CSS viewport by
   exactly the factor it magnifies by, so a reader at 1376 who zooms to 200%
   lands at 688 CSS px — across the breakpoint, into the smaller scale — and
   the headline went 66 device px to 68. What changes on a phone is the
   LAYOUT, not the type.

   Mobile deliberately does not hold one shared height: matching every screen
   to the tallest would hand the cover a screenful of dead scroll to match the
   plant view. A phone reader is already scrolling, and the parent follows by
   postMessage either way. The shared height is a promise about the DESKTOP
   stage, where it buys Ecolab one aspect-ratio value correct on all five.
   ========================================================================== */

#wuei-stage.is-mobile {
  display: block;
  min-height: 0;
  max-width: none;
  --m: 20px; /* mobile page margin */
  padding-bottom: calc(20 * var(--u));
}
#wuei-stage.is-mobile .screen {
  grid-area: auto;
  position: relative;
  inset: auto;
  padding: 0 var(--m);
  opacity: 1;
  visibility: visible;
  transition: none;
}
#wuei-stage.is-mobile .screen:not(.on) {
  display: none;
}
#wuei-stage.is-mobile .track {
  position: relative;
  left: auto;
  top: auto;
  width: auto;
  height: calc(4 * var(--u));
  margin: var(--m) var(--m) 0;
}
/* In flow beneath the screen rather than pinned, so it can never sit on top of
   content whose height it cannot know. */
#wuei-stage.is-mobile .nav {
  position: relative;
  right: auto;
  bottom: auto;
  margin: calc(24 * var(--u)) var(--m) 0;
  gap: calc(10 * var(--u));
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* On a phone the two buttons are flex:1 and fill the row, so the line takes a
   row of its own above them and reads left like the rest of the stack. */
#wuei-stage.is-mobile .navhint {
  flex: 1 0 100%;
  max-width: none;
  text-align: left;
  margin-right: 0;
  margin-bottom: calc(4 * var(--u));
}
#wuei-stage.is-mobile .btn {
  flex: 1 1 auto;
  justify-content: center;
  padding: 0 calc(18 * var(--u));
}
/* On a phone the row wraps: the credit takes its own line above the two
   buttons rather than competing with them for a 350px measure. */
#wuei-stage.is-mobile .cdp {
  flex: 1 0 100%;
  margin-right: 0;
  margin-bottom: calc(10 * var(--u));
  max-width: none;
}

/* LEADING, not size. The display headlines are drawn solid — .h-cover is 66/66
   and .h-screen 40/41 — because on desktop each line sits in its own .ln mask
   and holds exactly one line. In a 280px column those lines wrap INSIDE a
   single mask, and solid leading puts the second line through the first.
   Only line-height changes; font-size is untouched, so this costs nothing
   against 1.4.4. */
#wuei-stage.is-mobile .h-cover,
#wuei-stage.is-mobile .h-screen {
  line-height: 1.2;
}
/* THE COVER HEADLINE IS CUT ON A PHONE. Everywhere else in this deck the type
   scale is held across the breakpoint and only the layout changes — that is
   the house rule and the enzyme deck keeps to it. The cover is the one place
   it does not survive: 66px against a 350 content box put "How much water does
   your plant use to make one unit?" on SIX lines and the reader met a wall of
   blue before anything else. 42 puts it on three and leaves it comfortably the
   largest thing in the deck, which is the job the size was doing. */
#wuei-stage.is-mobile .h-cover {
  font-size: calc(42 * var(--u));
}
/* The 110px statistic is drawn against a 1296 column. On a phone it is the one
   role that genuinely cannot fit — 110px of tabular mono needs ~300px and the
   content box is 350. Kept as large as it can be rather than reduced to a
   body size, and it still measures x2.000 under zoom because the mobile value
   is itself a fixed px. */
#wuei-stage.is-mobile .tag {
  height: calc(22 * var(--u));
  padding: 0 calc(8 * var(--u));
}
/* 2.5.8 asks for 24px; anything a finger drives gets more. */
#wuei-stage.is-mobile button,
#wuei-stage.is-mobile [role="tab"] {
  min-height: calc(24 * var(--u));
}
