/* home.css: Plant Toolbox home sections (#p-home).
   Generated by .plans/site-overhaul/tools/port.py from ptb-site-overhaul-src/css/10-hero.css ... 95-closing.css.
   Production state: P1 light ("Light steel") only, review flags and mockup chrome removed. Loaded after site.css. */

/* ==== 10-hero.css ==== */
/* ==========================================================================
   10-hero.css, home hero (D13; floor scenes, owner brief 2026-10-26, revision 2: callout cards). Every selector
   starts with .s-hero.

   The copy column is unchanged: the H1 keeps the eight hours, then the lead, the CTA (O9), the link to the decision
   demo and the role strip. The picture is three illustrated plant floors in turn (figure.hf-figure.h-floor), the
   kinds of area Plant Toolbox is used in: Production line (Harbor Point), Machine shop (Fairport), Processing
   (Harbor Point). The drawings are generated (.plans/floor-concepts/tools/build.py hero, never hand-edited):
   floor/defs.svg.html (shared symbols, read once) and floor/{line,shop,batch}.svg.html, each a wide crop, a phone
   crop (<use> of the wide scene), and one callout per place Plant Toolbox is at work (2-3 per scene): in the wide
   crop a .hf-co group (a .hf-dot on the object and a .hf-ln leader, pathLength 100, dot to card), the phone crop's
   own inline .hf-co for its one phone spot, and an HTML .hf-card (where / data / decision, at most 3 lines, placed
   by --x/--y or --px/--py and data-side), then a .hf-sr text of every card. Their look and placement are
   content/css/home-floor.css, scoped to .hf-figure; this block owns the frame, the cycle, the tabs and the
   callouts' motion. The figcaption
   carries the one "Illustrative · concept" tag and the line of the scene that is on.

   Layout. Below 1100: one column, the figure (at most 720px) after the CTA row and the role strip. Below 720 the role
   strip and the lead's second sentence (.h-lead-more) hide and the "How a miss becomes an action" link moves under
   the figure (.h-ghost-under; the copy column's .h-ghost-top hides), as before (RD-09); the frame shows each scene's
   phone crop (tighter, so people and cards read) with its one phone callout (home-floor.css hides the other cards;
   their dots and lines live only in the hidden wide crop), and In turn and Pause show their icons only. From 1100: copy and drawing split 1 : 1, and 1 : 1.1 from 1200 (the drawing about 590px wide at
   1440); the H1 is sized by its column (8.1cqi, at most 52px; 43px at 1440), so each sentence holds one line. Every crop is 4:3, so the frame keeps its
   height when the scene changes.

   Motion (prefers-reduced-motion: no-preference only). Autoplay while no tab is chosen: a 30 s loop, 10 s per scene,
   0.8 s crossfade (the incoming scene fades in on top, z-index 2, over the outgoing one, which hides at 10.8 s).
   Every part runs that loop with its scene's delay (--hf-d: line -0.8 s, shop -20.8 s, processing -10.8 s, so the
   line is fully on at load). When a scene is on, each callout plays from 0.8 s, 0.8 s apart: the dot pops (0.25 s),
   the line draws dot to card (dashoffset 100 to 0, 0.45 s), the card fades and rises (0.35 s), so three cards are on
   by 3.45 s and rest until the scene leaves; the caption, the tab, its progress bar and the scene's .hf-sr switch at
   0.4 s, mid-crossfade, so only one is exposed at a time. Callouts reset while their scene is hidden.
   Hovering the drawing with a mouse, keyboard focus on a scene tab, or the Pause toggle (a checkbox; it reads Pause
   and shows pressed while paused) pause every animation, the callouts and conveyors too (--hf-ps). A chosen tab (a
   radio) stops the loop and the drawing's own motion, and plays that scene's callouts once; "In turn", the fourth
   radio of the group and the default, starts the loop again. Reduced motion or no animation support: no loop, no In
   turn or Pause; the production line shows at rest with its callouts, and the tabs still switch scenes.
   ========================================================================== */

.s-hero { padding-top: var(--s-7); padding-bottom: var(--s-7); }
@media (min-width: 768px) {
  .s-hero { padding-top: var(--s-8); padding-bottom: var(--s-8); }
}
.s-hero::before { display: none; }

.s-hero .h-grid { display: grid; gap: var(--s-6); min-width: 0; }
.s-hero .h-text { min-width: 0; }
.s-hero .hero-title {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  font-weight: 600;
  color: var(--ink);
}
.s-hero .hero-title-line { display: block; color: var(--display-accent); }
.s-hero .h-lead { margin-top: var(--s-5); }

/* CTA row: the primary button (to /contact, O9), then the link-ghost to the loop (#lp-loop). Stacked on phones. */
.s-hero .h-actions { display: grid; gap: var(--s-3); justify-items: start; margin-top: var(--s-6); }
.s-hero .h-actions .btn { width: 100%; }
@media (min-width: 720px) {
  .s-hero .h-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-5); }
  .s-hero .h-actions .btn { width: auto; }
}

/* Role strip (from 720 only): one row of five cells with a hairline between them. A sans label (issue 7): no mono,
   no tracking. */
.s-hero .h-strip {
  display: grid;
  grid-template-columns: repeat(5, max-content);
  margin-top: var(--s-6);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-3);
}
.s-hero .h-strip li { padding: var(--s-2) var(--s-3); border-left: 1px solid var(--hairline); }
.s-hero .h-strip li:first-child { padding-left: 0; border-left: 0; }

/* Phone (below 720): the lead keeps its first sentence and the role strip goes, so the frame starts above y 600.
   The link to the demo moves under the figure, so the toast ends above the fold. */
@media (max-width: 719px) {
  .s-hero .h-grid { gap: var(--s-5); }
  .s-hero .h-lead-more,
  .s-hero .h-strip,
  .s-hero .h-ghost-top { display: none; }
  .s-hero .h-actions { margin-top: var(--s-5); }
}
.s-hero .h-ghost-under { margin-top: var(--s-3); }
@media (min-width: 720px) {
  .s-hero .h-ghost-under { display: none; }
}

/* ---- The figure: frame, scenes ---------------------------------------------------------------------------- */
/* Below 1100 the figure is at most 720px wide instead of spanning the container. */
.s-hero .h-visual { min-width: 0; max-width: 720px; }
.s-hero .h-floor { min-width: 0; margin: 0; }
.s-hero .hf-frame {
  position: relative;
  display: grid;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  overflow: clip;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-decision);
  box-shadow: var(--shadow-1);
  isolation: isolate;
  /* Off screen the drawing is skipped (its loop keeps time, so it is in step when it comes back); the frame's size
     comes from its width and the 4:3 ratio, so nothing moves. */
  content-visibility: auto;
}
/* The scenes share one cell and are opaque, so an incoming scene covers the outgoing one. The line shows unless a tab
   is chosen; the others are visibility: hidden, so they are neither focused nor announced. (:has() rules apart, so
   a browser without :has() keeps the base rule.) */
.s-hero .hf-scene { position: relative; grid-area: 1 / 1; min-width: 0; background: var(--surface); }
.s-hero .hf-scene:not(.hf-scene--l),
.s-hero .hf-cap-ln:not(.hf-cap-ln--l) { visibility: hidden; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) :is(.hf-scene, .hf-cap-ln) { visibility: hidden; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-l:checked) :is(.hf-scene--l, .hf-cap-ln--l),
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-s:checked) :is(.hf-scene--s, .hf-cap-ln--s),
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-p:checked) :is(.hf-scene--p, .hf-cap-ln--p) { visibility: visible; }
.s-hero .hf-svg { display: block; width: 100%; height: 100%; }
/* The crop not in use is hidden with visibility, not display: display: none would cancel its callout animations, so
   crossing 720 (a phone turned sideways) would restart them out of step with the loop. */
.s-hero .hf-svg--phone { position: absolute; top: 0; left: 0; visibility: hidden; }
.s-hero .hf-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Controls: three tabs (labels of .state-input radios), then Pause / Play -------------------------------- */
/* Each tab sits on a 2px track; its bar fills over the 10 s its scene is on, or stays full while it is chosen. */
.s-hero .hf-ctl { display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-3); }
.s-hero .hf-tabs { display: flex; flex: 1 1 auto; gap: var(--s-1); min-width: 0; }
.s-hero .hf-tab {
  position: relative;
  padding: 6px 10px 8px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  color: var(--ink-3);
  border-radius: var(--r-control) var(--r-control) 0 0;
  cursor: pointer;
}
.s-hero .hf-tab::before,
.s-hero .hf-tab::after { content: ""; position: absolute; right: 0; bottom: 0; left: 0; height: 2px; background: var(--hairline); }
.s-hero .hf-tab::after { background: var(--teal); transform: scaleX(0); transform-origin: left; }
.s-hero .hf-tab:hover { color: var(--ink); }
.s-hero .hf-tab--l { color: var(--ink); background: var(--select-bg); }
.s-hero .hf-tab--l::after { transform: none; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) .hf-tab { color: var(--ink-3); background: none; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) .hf-tab::after { transform: scaleX(0); }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-l:checked) .hf-tab--l,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-s:checked) .hf-tab--s,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-p:checked) .hf-tab--p { color: var(--ink); background: var(--select-bg); }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-l:checked) .hf-tab--l::after,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-s:checked) .hf-tab--s::after,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-p:checked) .hf-tab--p::after { transform: none; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-l:focus-visible) .hf-tab--l,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-s:focus-visible) .hf-tab--s,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-p:focus-visible) .hf-tab--p,
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-0:focus-visible) .hf-turn,
.s-hero .hf-figure:has(> .hf-ctl > #hf-pause:focus-visible) .hf-pz { outline: 2px solid var(--focus); outline-offset: 2px; }
/* In turn and Pause / Play: shown only with motion. In turn is the label of the fourth radio (#hf-sc-0, checked by
   default, so Tab lands on it): quiet while the loop runs, a button once a tab is chosen; choosing it starts the loop
   again and keeps focus; below 720 it hides (visibility, so it keeps its place and can take focus) while the loop runs
   and it has no keyboard focus, so no idle ▶ sits by Pause. Pause is the checkbox's label, shown while the loop runs:
   always the word Pause; checked (paused), it is pressed like a chosen tab and its glyph turns to ▶. A chosen tab
   takes the checkbox out of the tab order. Names come from the visible words (2.5.3) and stay constant: "In turn (all
   three floors)", "Pause the scenes" (checked = paused). */
.s-hero .hf-btn {
  display: none;
  flex: none;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 4px 10px;
  font: 600 13px/1.5 var(--font-sans);
  color: var(--teal-ink);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--r-control);
  cursor: pointer;
}
.s-hero .hf-btn:hover { border-color: var(--teal-ink); }
.s-hero .hf-btn .icon { width: 12px; height: 12px; }
.s-hero .hf-turn,
.s-hero .hf-turn:hover { color: var(--ink-3); background: none; border-color: transparent; cursor: default; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) .hf-turn {
  color: var(--teal-ink);
  background: var(--bg);
  border-color: var(--hairline);
  cursor: pointer;
}
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) .hf-turn:hover { border-color: var(--teal-ink); }
.s-hero .hf-pz-b { display: none; }
.s-hero .hf-figure:has(> .hf-ctl > #hf-pause:checked) .hf-pz-a { display: none; }
.s-hero .hf-figure:has(> .hf-ctl > #hf-pause:checked) .hf-pz-b { display: inline; }
.s-hero .hf-figure:has(> .hf-ctl > #hf-pause:checked) .hf-pz {
  color: var(--ink);
  background: var(--select-bg);
  box-shadow: inset 0 -2px 0 var(--teal);
}
@media (prefers-reduced-motion: no-preference) {
  .s-hero :is(.hf-turn, .hf-pz) { display: inline-flex; }
  .s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) :is(.hf-pz, .hf-pause) { display: none; }
}
@media not all and (prefers-reduced-motion: no-preference) {
  .s-hero .hf-pause { display: none; }
  /* No loop: In turn shows the production line, so its focus ring goes on that tab. */
  .s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-0:focus-visible) .hf-tab--l { outline: 2px solid var(--focus); outline-offset: 2px; }
}
/* The keyboard hint shows only while a radio of the group (a scene tab or In turn) has focus, never for Pause. From
   720 it hangs under the figure, out of flow, so the columns never move; below 720 it stays in flow (the demo link
   follows the figure there). */
.s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > :focus-visible)) > .kbd-hint { display: none; }
@media (min-width: 720px) {
  .s-hero .h-floor { position: relative; }
  .s-hero .h-floor > .kbd-hint { position: absolute; top: 100%; left: 0; }
}
.s-hero .hf-cap { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); max-width: none; }
.s-hero .hf-cap .tag { vertical-align: 1px; }
.s-hero .hf-cap-lns { display: grid; min-width: 0; }
.s-hero .hf-cap-ln { grid-area: 1 / 1; }
@media (forced-colors: active) {
  .s-hero .hf-tab::after { forced-color-adjust: none; background: Highlight; }
}

/* Phone (below 720): the phone crop and its one callout (its card's place and the other cards' hiding are in
   home-floor.css), compact tabs and icon-only toggles. */
@media (max-width: 719px) {
  .s-hero .hf-svg--wide { position: absolute; top: 0; left: 0; visibility: hidden; }
  .s-hero .hf-svg--phone { position: static; visibility: inherit; }
  .s-hero .hf-tabs { gap: 2px; }
  .s-hero .hf-tab { padding: 6px 5px 8px; font-size: 12px; }
  .s-hero .hf-btn { padding: 4px 9px; }
  /* While the loop runs, In turn would be a lone idle ▶ by Pause: hidden (in place) unless it has keyboard focus. */
  .s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > .hf-radio:checked)):not(:has(> .hf-ctl > .hf-radios > #hf-sc-0:focus-visible)) .hf-turn { visibility: hidden; }
  .s-hero .hf-btn-t {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ---- From 1100: copy and drawing side by side, 1 : 1, then 1 : 1.1 once the container is full width ---------- */
@media (min-width: 1100px) {
  .s-hero .h-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-6);
    align-items: center;
  }
  /* The H1 is sized by its column (container units, at most 52px), so each sentence holds one line. */
  .s-hero .h-text { container-type: inline-size; }
  .s-hero .hero-title { font-size: min(var(--fs-display), 3.25rem); font-size: min(3.25rem, 8.1cqi); }
  .s-hero .h-lead { max-width: none; font-size: 1.0625rem; line-height: 1.55; }
  .s-hero .h-visual { max-width: none; }
}
@media (min-width: 1200px) {
  .s-hero .h-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); }
}

/* ---- Motion ---------------------------------------------------------------------------------------------- */
/* Callouts read their animation from custom properties set on their scene: --hf-a-dot / --hf-a-ln / --hf-a-card
   (name; none at rest), --hf-t-dot / --hf-t-ln / --hf-t-card (duration), --hf-n (iterations), --hf-d (the scene's
   delay), --hf-lead (from the scene's start to the first dot) and --hf-i (the spot's index from .hf-co--N and
   .hf-card--N, 0 to 2; a scene has 2 or 3). Each spot starts at lead + 0.8 s × i: its dot, then its line 0.25 s
   later, then its card 0.7 s later. --hf-ps (paused) holds them, the loop and the drawing's own motion. Every :has()
   here is child-anchored (> .hf-ctl > …), so the animated parts' style updates never scan the drawing's nodes. */
.s-hero .hf-dot {
  transform-box: fill-box;
  transform-origin: center;
  animation: var(--hf-a-dot, none) var(--hf-t-dot, 0s) linear calc(var(--hf-d, 0s) + var(--hf-lead, 0s) + var(--hf-i, 0) * 0.8s) var(--hf-n, 1) backwards;
  animation-play-state: var(--hf-ps, running);
}
.s-hero .hf-ln {
  animation: var(--hf-a-ln, none) var(--hf-t-ln, 0s) linear calc(var(--hf-d, 0s) + var(--hf-lead, 0s) + var(--hf-i, 0) * 0.8s + 0.25s) var(--hf-n, 1) backwards;
  animation-play-state: var(--hf-ps, running);
}
.s-hero .hf-card {
  animation: var(--hf-a-card, none) var(--hf-t-card, 0s) linear calc(var(--hf-d, 0s) + var(--hf-lead, 0s) + var(--hf-i, 0) * 0.8s + 0.7s) var(--hf-n, 1) backwards;
  animation-play-state: var(--hf-ps, running);
}
.s-hero :is(.hf-co--2, .hf-card--2) { --hf-i: 1; }
.s-hero :is(.hf-co--3, .hf-card--3) { --hf-i: 2; }
/* Hovering the drawing (a mouse only: a tap would leave :hover stuck), keyboard focus on a scene tab, or Pause holds
   everything. (Not focus on Pause or In turn, so Play works at once.) A chosen tab rests the drawing: no conveyor, no
   breathing light. */
@media (hover: hover) {
  .s-hero .hf-figure:has(> .hf-frame:hover) { --hf-ps: paused; }
}
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:focus-visible),
.s-hero .hf-figure:has(> .hf-ctl > #hf-pause:checked) { --hf-ps: paused; }
.s-hero .hf-figure:has(> .hf-ctl > .hf-radios > .hf-radio:checked) { --hf-mv-b: none; --hf-mv-c: none; --hf-mv-l: none; }

@media (prefers-reduced-motion: no-preference) {
  .s-hero :is(.hf-scene--l, .hf-tab--l, .hf-cap-ln--l) { --hf-d: -0.8s; }
  .s-hero :is(.hf-scene--s, .hf-tab--s, .hf-cap-ln--s) { --hf-d: -20.8s; }
  .s-hero :is(.hf-scene--p, .hf-tab--p, .hf-cap-ln--p) { --hf-d: -10.8s; }
  /* Autoplay (no tab chosen): the 30 s loop on every part. */
  .s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > .hf-radio:checked)) .hf-scene {
    --hf-a-dot: hf-dot-cyc;
    --hf-a-ln: hf-ln-cyc;
    --hf-a-card: hf-card-cyc;
    --hf-t-dot: 30s;
    --hf-t-ln: 30s;
    --hf-t-card: 30s;
    --hf-n: infinite;
    --hf-lead: 0.8s;
    animation: hf-cyc 30s linear var(--hf-d) infinite;
    animation-play-state: var(--hf-ps, running);
  }
  .s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > .hf-radio:checked)) :is(.hf-cap-ln, .hf-sr) {
    animation: hf-cap 30s linear var(--hf-d) infinite;
    animation-play-state: var(--hf-ps, running);
  }
  .s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > .hf-radio:checked)) .hf-tab {
    animation: hf-tab 30s linear var(--hf-d) infinite;
    animation-play-state: var(--hf-ps, running);
  }
  .s-hero .hf-figure:not(:has(> .hf-ctl > .hf-radios > .hf-radio:checked)) .hf-tab::after {
    animation: hf-prog 30s linear var(--hf-d) infinite;
    animation-play-state: var(--hf-ps, running);
  }
  /* A chosen tab: its scene's callouts play once, then rest, even while the tabs keep keyboard focus. */
  .s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-l:checked) .hf-scene--l,
  .s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-s:checked) .hf-scene--s,
  .s-hero .hf-figure:has(> .hf-ctl > .hf-radios > #hf-sc-p:checked) .hf-scene--p {
    --hf-ps: running;
    --hf-a-dot: hf-dot-once;
    --hf-a-ln: hf-ln-once;
    --hf-a-card: hf-card-once;
    --hf-t-dot: 0.25s;
    --hf-t-ln: 0.45s;
    --hf-t-card: 0.35s;
    --hf-n: 1;
    --hf-d: 0s;
    --hf-lead: 0.1s;
  }
  /* The scene (30 s loop): in on top (0 to 0.8 s), on to 10 s, under the next one to 10.8 s, then hidden. */
  @keyframes hf-cyc {
    0% { visibility: visible; opacity: 0; z-index: 2; }
    2.667% { opacity: 1; }
    33.333% { z-index: 2; }
    33.334% { z-index: 1; }
    35.999% { visibility: visible; opacity: 1; }
    36%, 100% { visibility: hidden; opacity: 0; z-index: 1; }
  }
  /* Caption and .hf-sr: on from 0.4 s to 10.4 s, so the outgoing and incoming ones never overlap. */
  @keyframes hf-cap {
    0%, 1.332% { visibility: hidden; opacity: 0; }
    1.333% { visibility: visible; opacity: 0; }
    2.667%, 33.333% { opacity: 1; }
    34.666% { visibility: visible; opacity: 0; }
    34.667%, 100% { visibility: hidden; opacity: 0; }
  }
  @keyframes hf-tab {
    0%, 1.332%, 34.667%, 100% { color: var(--ink-3); background-color: transparent; }
    1.333%, 34.666% { color: var(--ink); background-color: var(--select-bg); }
  }
  @keyframes hf-prog {
    0%, 1.333%, 34.667%, 100% { transform: scaleX(0); }
    34.666% { transform: scaleX(1); }
  }
  /* Callout parts on the 30 s loop: the dot pops (0.25 s), the line draws (0.45 s), the card fades and rises
     (0.35 s); each holds, then resets at 25 s, when its scene is hidden. */
  @keyframes hf-dot-cyc {
    0% { opacity: 0; transform: scale(0); animation-timing-function: cubic-bezier(0.3, 1.5, 0.6, 1); }
    0.833%, 83.333% { opacity: 1; transform: scale(1); }
    83.334%, 100% { opacity: 0; transform: scale(0); }
  }
  @keyframes hf-ln-cyc {
    0% { stroke-dasharray: 100; stroke-dashoffset: 100; animation-timing-function: cubic-bezier(0.3, 0, 0.2, 1); }
    1.5%, 83.333% { stroke-dasharray: 100; stroke-dashoffset: 0; }
    83.334%, 100% { stroke-dasharray: 100; stroke-dashoffset: 100; }
  }
  @keyframes hf-card-cyc {
    0% { opacity: 0; translate: 0 6px; animation-timing-function: ease-out; }
    1.167%, 83.333% { opacity: 1; translate: 0 0; }
    83.334%, 100% { opacity: 0; translate: 0 6px; }
  }
  @keyframes hf-dot-once {
    from { opacity: 0; transform: scale(0); animation-timing-function: cubic-bezier(0.3, 1.5, 0.6, 1); }
  }
  @keyframes hf-ln-once {
    from { stroke-dasharray: 100; stroke-dashoffset: 100; animation-timing-function: cubic-bezier(0.3, 0, 0.2, 1); }
    to { stroke-dasharray: 100; stroke-dashoffset: 0; }
  }
  @keyframes hf-card-once {
    from { opacity: 0; translate: 0 6px; animation-timing-function: ease-out; }
  }
}

/* ==== 15-proof.css ==== */
/* ==========================================================================
   15-proof.css, home proof strip (D14). Scoped to .s-proof.
   One calm, static strip on the surface band: the claim ("In use on the plant floor at" over the Opella logo),
   Luke D.'s quote and attribution beside the logo, folded in from the old #vo-voices section (its head, lead and
   96 px glyph are gone), and on the right the Opella story video thumbnail (approved with the name, logo and
   quote: G1, G1b, G2 cleared).
   The h2 holds the label and the logo, so its accessible name reads "In use on the plant floor at Opella".
   The label is a sans label in --ink-3 (plan 1.2), not a mono line. The logo filter comes from site.css
   (.logo-strip img: the white file shows dark on the light theme); this sheet only sizes it. When Opella sends an
   approved dark-ink file, override it here (#pf-proof .logo-strip img { filter: none; opacity: 1; }).
   Phone: label, logo, the quote under a hairline, then the thumbnail at full width. 768px and up (owner,
   2026-10-27): the label on its own line on top; under it the logo, flush with the container's left edge, and
   the quote beside it behind a vertical hairline; the thumbnail in a 248px right-hand column. The label + logo +
   quote group is centred on the thumbnail. The h2 is a two-way subgrid of the row (see the 768 rule).
   1100px and up: the same layout with a 40px logo and the quote at a pull-quote size (22px).
   No motion in the row; only the thumbnail's hover tint fades (not under reduced motion). The player is a
   <dialog> opened by content/js/video.js; without script the thumbnail is a plain link to the MP4.
   ========================================================================== */

/* A strip, not a full section: half the section padding. */
.s-proof { padding-block: var(--s-6); }
@media (min-width: 768px) {
  .s-proof { padding-block: var(--s-7); }
}

.s-proof .pf-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  align-items: center;
}

/* The heading is set as a sans label, not a display heading. */
.s-proof .pf-claim {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
  min-width: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--ink-3);
  text-wrap: wrap;
}
.s-proof .pf-label { min-width: 0; }

/* One logo: a block, not the base two- or five-column grid. */
.s-proof .pf-logo { display: block; min-width: 0; }
.s-proof .pf-logo img { height: 32px; }
/* Dark high-contrast themes: show the white file as it is; the site.css darkening would leave it black on Canvas. */
@media (forced-colors: active) and (prefers-color-scheme: dark) {
  .s-proof .pf-logo img { filter: none; opacity: 1; }
}

/* The quote: one sentence in ink, the attribution as a sans label under it. */
.s-proof .pf-quote {
  display: grid;
  gap: var(--s-1);
  min-width: 0;
  margin: 0;
  padding-top: var(--s-4);
  border-top: 1px solid var(--hairline);
}
.s-proof .pf-quote-text { margin: 0; }
.s-proof .pf-quote-text p {
  max-width: none;
  font-size: 17px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: var(--ink);
}
.s-proof .pf-who { font-size: 14px; line-height: 1.5; color: var(--ink-3); }

/* 768px and up: three columns (logo, quote, thumbnail) over four rows: 1fr; the label; logo | quote; 1fr. The two
   empty 1fr rows share the thumbnail's extra height, which centres the group on it and keeps the middle rows
   tight (no row gap, so the empty rows add nothing; the label's bottom padding is the space under it). The h2
   spans columns 1-2 and rows 2-3 as a subgrid both ways (gap: normal, so it keeps the row's gaps): the label runs
   across both columns, so only the logo sizes column 1, and the logo sits under it, flush left. The quote (not in
   the h2) takes column 2 of row 3, over the h2's empty corner; the thumbnail spans every row on the right. */
@media (min-width: 768px) {
  .s-proof .pf-row {
    grid-template-columns: auto minmax(0, 1fr) 248px;
    grid-template-rows: 1fr auto auto 1fr;
    gap: 0 var(--s-5);
    align-items: start;
  }
  .s-proof .pf-claim {
    display: grid;
    grid-column: 1 / 3;
    grid-row: 2 / 4;
    grid-template-columns: subgrid;
    grid-template-rows: subgrid;
    gap: normal;
    align-items: start;
  }
  .s-proof .pf-label { grid-column: 1 / -1; grid-row: 1; padding-bottom: var(--s-3); }
  .s-proof .pf-logo { grid-column: 1; grid-row: 2; }
  .s-proof .pf-quote {
    grid-column: 2;
    grid-row: 3;
    padding-top: 0;
    padding-left: var(--s-5);
    border-top: 0;
    border-left: 1px solid var(--hairline);
  }
  .s-proof .pf-quote-text p { max-width: 30em; text-wrap: balance; }
  .s-proof .pf-video { grid-column: 3; grid-row: 1 / -1; }
}
@media (min-width: 1024px) {
  .s-proof .pf-quote-text p { font-size: 18px; }
}
/* 1100px and up the row has room: a larger logo and a pull-quote size, so the left group stands nearer the
   thumbnail's height. */
@media (min-width: 1100px) {
  .s-proof .pf-logo img { height: 40px; }
  .s-proof .pf-quote-text p { font-size: 22px; line-height: 1.36; }
}

/* The story thumbnail: one link, the poster over its label. The focus ring (site :focus-visible) rings both. */
.s-proof .pf-video {
  display: grid;
  gap: var(--s-2);
  align-content: start;
  min-width: 0;
  border-radius: var(--r-card);
  color: var(--teal-ink);
  text-decoration: none;
}
.s-proof .pf-video:focus-visible { outline-offset: 4px; }
/* The poster at the film's own aspect (1920 x 1012), framed like a .shot: hairline, card radius, shadow. */
.s-proof .pf-video-frame {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 1920 / 1012;
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--bg-subtle);
  box-shadow: var(--shadow-1);
}
.s-proof .pf-video-frame img { width: 100%; height: 100%; object-fit: cover; }
/* The play disc: white with a blue triangle and a soft drop shadow, legible on light and dark frames alike;
   it turns blue on hover and focus. */
.s-proof .pf-video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 52px;
  height: 52px;
  translate: -50% -50%;
  filter: drop-shadow(0 2px 8px rgba(10, 10, 26, .35));
}
.s-proof .pf-video-play circle { fill: var(--surface); fill-opacity: .94; }
.s-proof .pf-video-play path { fill: var(--action); }
.s-proof .pf-video:is(:hover, :focus-visible) .pf-video-play circle { fill: var(--action); fill-opacity: 1; }
.s-proof .pf-video:is(:hover, :focus-visible) .pf-video-play path { fill: var(--white); }
/* The label: a link in the accent, the running time in --ink-3. */
.s-proof .pf-video-label { font-size: 14px; font-weight: 600; line-height: 1.4; }
.s-proof .pf-video:hover .pf-video-label { text-decoration: underline; text-underline-offset: 3px; }
.s-proof .pf-video-time { font-weight: 400; color: var(--ink-3); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: no-preference) {
  .s-proof .pf-video-play :is(circle, path) { transition: fill var(--t-fast) ease-out, fill-opacity var(--t-fast) ease-out; }
}

/* The player: a modal <dialog> in the top layer, the Close button over the video. Its width is the smallest of
   1100px, the screen less a 16px gutter each side, and what the height leaves (less the gutters and the 52px
   Close row) times the film's aspect, so it fits a phone held either way. The UA's open-only display rule is
   kept: the grid applies to [open] only. The video has no src until video.js sets one. */
.s-proof .pf-player {
  width: min(1100px, calc(100vw - 32px), calc((100vh - 84px) * 1920 / 1012));
  width: min(1100px, calc(100vw - 32px), calc((100dvh - 84px) * 1920 / 1012));
  max-width: none;
  max-height: none;
  margin: auto;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
}
.s-proof .pf-player[open] { display: grid; gap: var(--s-2); }
.s-proof .pf-player::backdrop { background: rgba(10, 14, 26, .86); }
.s-proof .pf-player-close { justify-self: end; }
.s-proof .pf-player :focus-visible { outline-color: var(--focus-on-dark); }
.s-proof .pf-player-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1920 / 1012;
  border-radius: var(--r-card);
  background: #000;
}

/* ==== 20-loop.css ==== */
/* ==========================================================================
   20-loop.css · home row 3 "The loop", right under the Opella proof strip and above "Bring clarity…" (R1·9, owner
   2026-10-30; it was row 7, after the tier ladder, as 50-loop). Every selector starts with .s-loop.
   PDCA across the whole plant. The section sits on --bg; the four steps are white --surface cards with a hairline,
   numbered 01-04 in the numbered-tab grammar (a real sequence: .step-num, site.css section 17).
   Each card's example chip is a small product part (2026-10-29), not a line: Plan's typed goal list, Do's shift
   checklist (two inputs ticked, then ⚡ Take action → #4182, Luis Ortega's, linked to its anatomy on
   /next-8-hours#pa-anatomy since #dd-demo was parked), Check's Score Card tile with five gap-to-goal bars hanging
   from the labelled 78.0% goal, W41's −6.6 pp the widest (R1·11, owner 2026-10-30: it was a sparkline), and Act's plan
   diff (OEE off target, no action → Plan adjusted · #4182, OEE actions none → 1, which then feeds Do).
   Do (.step-decide) is the bolt step: --bolt-line edge, --bolt-soft fill, and the bolt on #4182's title in its chip. Check carries the comparison as a quiet note (.lp-note, a dashed rule under
   its paragraph): most monitoring tools stop there. Check's last meta item, "Did the actions work?", links to
   /ai#pd-effect, a shipped step with no tag since 2026-10-31 (R1·1); Act's "Decision models" and the head's "See every
   decision model" link to /ai#pd-models (R2·10).
   The flow from Do onto the scenario tabs, and the 1100+ join with the scenarios section under it, went to the
   parking lot with 55-scenarios on 2026-10-29 (content/views/home/.parking-lot/55-scenarios.css).
   Phone: steps stacked (number and title share a row). 768: two-column trace. 1100: four columns with the connector,
   which runs --lp-gap (24px) above the cards; each node sits centred on it with a short stem down to its card.
   Play once (R1·10, owner 2026-10-30: "play it once when it comes into view, and leave the final state in place").
   Time-based, not scroll-driven: the connector draws in 400ms, Plan, Do, Check and Act rise in 120ms apart from
   150ms, Check's bars grow down from the goal at 700ms and Act's "Plan adjusted" row comes in at 950ms, all done in
   about 1.4s, and it never rewinds. home.js holds it at its first frame (.is-wait) until it is in view: from 1100
   the rail as one, once its top is 80% down the screen; below 1100 each card on its own on the same line, with no
   stagger on a phone and Do and Act 120ms behind their row-mates at 768-1099. No script: it plays on load. Reduced
   motion: nothing moves, every part rests in its finished look.
   ========================================================================== */
.s-loop .section-head h2 { max-width: 21em; }

/* The trace ------------------------------------------------------------------ */
.s-loop .lp-rail { position: relative; margin-top: var(--s-6); }
.s-loop .trace { margin-top: 0; }
.s-loop .trace::before { display: none; }
/* Each step has five in-flow rows: number, title, body, example, meta. From 768 the steps are row subgrids of
   the trace, so the example boxes and the meta rules line up across every card in a row. */
.s-loop .step {
  gap: var(--s-3);
  min-width: 0;
  background: var(--surface);
  border-color: var(--hairline);
}
.s-loop .step h3 { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); color: var(--ink); }
.s-loop .step h3 .tag { font-size: var(--fs-tag); font-weight: 500; letter-spacing: 0; }
.s-loop .step-decide { border-color: var(--bolt-line); background: var(--bolt-soft); }
.s-loop .step p { font-size: 16px; line-height: 1.55; color: var(--ink-2); }
.s-loop .lp-body { display: grid; align-content: start; gap: var(--s-3); min-width: 0; }
@media (min-width: 768px) {
  .s-loop .lp-step { grid-row: span 5; grid-template-rows: subgrid; row-gap: var(--s-3); }
}
@supports not (grid-template-rows: subgrid) {
  @media (min-width: 768px) {
    .s-loop .lp-step { display: flex; flex-direction: column; }
    .s-loop .step .lp-eg { margin-top: auto; }
  }
}
/* Phone: the number and the title share the first row; body, example and meta span the card. */
@media (max-width: 767px) {
  .s-loop .lp-step { grid-template-columns: auto minmax(0, 1fr); column-gap: var(--s-3); padding: var(--s-5) var(--s-4); }
  .s-loop .lp-step > :is(.lp-body, .lp-eg, .step-meta) { grid-column: 1 / -1; }
  .s-loop .lp-step > .step-num { grid-column: 1; align-self: baseline; }
  .s-loop .lp-step > h3 { grid-column: 2; align-self: baseline; }
}
/* The example: one row of the Line 4 story under the step's promise (the board key over the value). Sans; ids and
   figures tabular. Since 2026-10-29 each example is a small product part, not a line: see the chip rules below. */
.s-loop .step .lp-eg {
  display: grid;
  align-content: start;
  gap: 2px;
  padding: var(--s-2) var(--s-3);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--r-control);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.s-loop .step-decide .lp-eg { background: var(--surface); border-color: var(--bolt-line); }
.s-loop .lp-eg-k { font-size: 13px; font-weight: 600; color: var(--ink-3); }
.s-loop .lp-eg > span:last-child { min-width: 0; }
/* The bolt on Do's open row: the ⚡ glyph in --bolt-ink before "Take action" (the button that raised #4182). */
.s-loop .lp-bolt { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; color: var(--bolt-ink); }
.s-loop .lp-bolt::before {
  content: "";
  flex: none;
  width: 12px;
  height: 12px;
  background: var(--bolt-ink);
  -webkit-mask: var(--app-i-bolt) center / contain no-repeat;
  mask: var(--app-i-bolt) center / contain no-repeat;
}
/* Chips are product parts: .app for the scoped recipes (.app-type, .app-st, .app-pill, .app-av, .app-spark), frame
   chrome off (precedent .s-tiers .tl-toasts). No strip, so no Illustrative tag. `.s-loop .step p` (0,2,1) would force
   16px --ink-2 on the inner p, so the reset is 0,3,0 and every part rule is written at .s-loop .step .lp-eg (0,4,0+). */
.s-loop .step .lp-eg.app { gap: 6px; padding: var(--s-2) 10px; box-shadow: none; overflow: visible; }
.s-loop .step .lp-eg :where(p, ul) { margin: 0; padding: 0; max-width: none; font-size: 13px; line-height: 1.35; color: inherit; }
.s-loop .step .lp-eg .lp-eg-h { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-2); font-size: 12px; font-weight: 600; color: var(--app-mute); }
/* Plan: the week's typed goal list (S Q D C I letter, KPI, target); OEE is the one this story follows. */
.s-loop .step .lp-eg .lp-goals { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; column-gap: 6px; list-style: none; }
.s-loop .step .lp-eg .lp-goals li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; padding: 4px 0; border-top: 1px solid var(--app-line-2); }
.s-loop .step .lp-eg .lp-goals li:first-child { border-top: 0; }
.s-loop .step .lp-eg .lp-goals .is-focus { font-weight: 700; }
.s-loop .step .lp-eg .lp-goals b { white-space: nowrap; }
/* Do: the shift's inputs as a checklist being worked. Two are ticked (quiet, with "done" for screen readers); the open
   one is Take action → #4182, the owned action from Act, with its owner under it (no status pill: it wraps at 1100,
   and the open box already says not done). That row is the chip's one link, to #4182's anatomy on
   /next-8-hours#pa-anatomy: the whole two-line row is the hit area (min 40px), the id carries the underline.
   Run sheet: 3 CILs at 06:15, cap torque 07:00 to 09:00 done. */
.s-loop .step .lp-eg .lp-do { display: grid; list-style: none; }
.s-loop .step .lp-eg .lp-do li { display: grid; grid-template-columns: 18px minmax(0, 1fr); align-items: start; column-gap: 8px; padding: 4px 0; border-top: 1px solid var(--app-line-2); }
.s-loop .step .lp-eg .lp-do li:first-child { border-top: 0; }
.s-loop .step .lp-eg .lp-do .is-done { color: var(--app-mute); }
.s-loop .step .lp-eg .lp-do .app-check { vertical-align: 0; }
.s-loop .step .lp-eg .lp-do-act { display: grid; gap: 4px; min-height: 40px; font-weight: 700; color: var(--app-ink); text-decoration: none; }
.s-loop .step .lp-eg .lp-act-id { color: var(--link-ink, var(--teal-ink)); text-decoration: underline; text-underline-offset: 3px; }
.s-loop .step .lp-eg .lp-do-act:hover .lp-act-id { text-decoration-thickness: 2px; }
.s-loop .step .lp-eg .lp-act-f { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 6px; font-size: 12px; font-weight: 500; color: var(--app-ink-2); }
.s-loop .step .lp-eg .lp-who { display: inline-flex; align-items: center; gap: 6px; }
/* Check: the Score Card tile, value and status over five weeks of gap to goal (R1·11, owner 2026-10-30: bars of the
   gap, with more week-to-week variance). The dashed goal runs along the top and each week's bar hangs from it, 4.85px
   a point, so W41's 6.6 points is 32px of the 40px box (the sparkline's height, so the 768-1099 row with Act keeps its
   height). Earlier weeks are a lighter --app-bad, W41 the full colour; the labels column holds "Goal 78.0%" over
   "−6.6 pp", and the axis names every week. W37-W40 (75.8, 73.1, 74.6, 72.2%) are illustrative; W41's 71.4% is the
   Week 41 / #4182 story. Local px rules: the shared site.css bar rule is not used. */
.s-loop .step .lp-eg .lp-kpi { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px var(--s-2); }
.s-loop .step .lp-eg .lp-kpi > b { font-size: 22px; line-height: 1.1; letter-spacing: -.4px; color: var(--app-ink); }
.s-loop .step .lp-eg .lp-kpi small { font-size: 13px; color: var(--app-mute); }
.s-loop .step .lp-eg .lp-bars { display: grid; grid-template-columns: minmax(0, 1fr) 62px; column-gap: 6px; }
.s-loop .step .lp-eg .lp-bars-p { position: relative; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: 6px; height: 40px; }
.s-loop .step .lp-eg .lp-bars-goal { position: absolute; z-index: 1; top: 2px; left: -2px; right: -2px; border-top: 2px dashed var(--line-strong); }
.s-loop .step .lp-eg .lp-bar { display: block; align-self: start; margin-top: 4px; height: calc(var(--g) * 4.85px); border-radius: 0 0 4px 4px; background: color-mix(in srgb, var(--app-bad) 55%, var(--surface)); transform-origin: 50% 0; }
.s-loop .step .lp-eg .lp-bar.is-last { background: var(--app-bad); }
.s-loop .step .lp-eg .lp-bars-lab { display: grid; align-content: space-between; height: 40px; padding-bottom: 2px; text-align: right; }
.s-loop .step .lp-eg :is(.lp-goal-l, .lp-gap-l) { font-size: 11px; line-height: 12px; white-space: nowrap; }
.s-loop .step .lp-eg .lp-goal-l { font-weight: 600; color: var(--app-mute); }
.s-loop .step .lp-eg .lp-bars-x { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: 6px; padding-right: 68px; font-size: 11px; letter-spacing: -.2px; color: var(--app-mute); text-align: center; white-space: nowrap; }
/* Act: the plan diff. Change rides on the −/+ gutter, the struck "none", the words and none → 1, never colour alone.
   Both rows name the KPI: Line 4 has 17 open actions in all; none was on OEE. */
.s-loop .step .lp-eg :is(.lp-was, .lp-now) { position: relative; display: grid; justify-items: start; gap: 2px; padding: 4px 8px 4px 22px; border-radius: var(--app-r-tag); }
.s-loop .step .lp-eg .lp-was { background: var(--app-fill); color: var(--app-mute); }
.s-loop .step .lp-eg .lp-now { background: var(--bolt-soft); box-shadow: inset 0 0 0 1px var(--bolt-line); color: var(--bolt-ink); }
.s-loop .step .lp-eg :is(.lp-was, .lp-now)::before { position: absolute; left: 8px; top: 4px; font-weight: 700; }
.s-loop .step .lp-eg .lp-was::before { content: "\2212"; content: "\2212" / ""; }
.s-loop .step .lp-eg .lp-now::before { content: "+"; content: "+" / ""; }
.s-loop .step .lp-eg .lp-now-k { font-size: 12px; font-weight: 700; }
.s-loop .step .lp-eg .lp-now del { padding: 0 2px; color: var(--app-mute); text-decoration-thickness: 1.5px; }
.s-loop .step .lp-eg .lp-now ins { text-decoration: none; font-weight: 700; color: var(--app-good-ink); }
/* Meta: the product words behind the step, sans 13px --ink-3. Each item stays whole; the separator rides at the end
   of an item, so a wrapped line never starts with a dot. */
.s-loop .step .step-meta {
  display: flex;
  flex-wrap: wrap;
  align-content: start;
  gap: 2px 0.6em;
  padding-top: var(--s-3);
  border-top: 1px solid var(--hairline);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-3);
}
.s-loop .step-meta > span { min-width: 0; }
.s-loop .step-meta > span:not(:last-child)::after { content: "\00B7"; margin-left: 0.6em; }
.s-loop .step-decide .step-meta { border-top-color: var(--bolt-line); }

/* Check's meta link ("Did the actions work?") stays whole on its line (its Early access tag went 2026-10-31, R1·1). */
.s-loop .step-meta a { color: var(--link-ink, var(--teal-ink)); text-decoration: underline; text-underline-offset: 3px; }
.s-loop .step-meta > span:has(> a) > * { white-space: nowrap; }

/* The note under Check's paragraph: a quiet aside. It echoes the old boundary's dashed rule; it is not a quote. */
.s-loop .step .lp-note {
  padding-left: var(--s-3);
  border-left: 2px dashed var(--line-strong);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}

/* Connector and nodes (desktop) */
.s-loop .lp-connector { display: none; }
@media (min-width: 1100px) {
  .s-loop .lp-connector {
    display: block;
    position: absolute;
    z-index: 1;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--line-strong);
  }
  .s-loop .lp-rail { --lp-gap: var(--s-5); padding-top: var(--lp-gap); }
  /* Nodes and stems sit against each card's padding box (inside its 1px border): the node is centred on the
     connector's 1px line, and the stem runs from the node's foot to the card's top edge. */
  .s-loop .step::before { top: calc(-1 * var(--lp-gap) - 5.5px); background: var(--line-strong); z-index: 2; }
  .s-loop .step::after {
    content: "";
    position: absolute;
    top: calc(-1 * var(--lp-gap) + 4.5px);
    left: calc(var(--s-5) + 4px);
    width: 2px;
    height: calc(var(--lp-gap) - 5.5px);
    background: var(--line-strong);
  }
  .s-loop .step-decide::before, .s-loop .step-decide::after { background: var(--bolt-mark); }
}

/* Play once (R1·10; see the header). Time-based and finite: each part plays FROM hidden TO its finished look (fill
   both), so with reduced motion nothing runs and every part rests finished. The keyframes are site.css section 35's
   (rv-in, rv-draw-x, rv-draw-y); the connector no longer carries .rv-draw, so section 35's view() rule never scrubs it.
   --lp-d is each card's start; a chip part starts after its card (Check's bars 310ms after, Act's row 440ms after). */
@media (prefers-reduced-motion: no-preference) {
  .s-loop .lp-connector { animation: rv-draw-x .4s ease-out both; transform-origin: 0 50%; }
  .s-loop .lp-step { animation: rv-in .45s ease-out var(--lp-d, 0ms) both; }
  .s-loop .lp-bar { animation: rv-draw-y .45s ease-out calc(var(--lp-d, 0ms) + 310ms) both; }
  .s-loop .lp-now { animation: rv-in .45s ease-out calc(var(--lp-d, 0ms) + 440ms) both; }
  /* 768-1099: two cards a row, each waiting on its own; Do and Act run 120ms behind their row-mates. */
  @media (min-width: 768px) and (max-width: 1099px) {
    .s-loop .lp-step--do, .s-loop .lp-step--act { --lp-d: 120ms; }
  }
  /* 1100+: one row, waiting as one: Plan, Do, Check and Act from 150ms, 120ms apart (bars at 700ms, Act's row at
     950ms, done about 1.4s). */
  @media (min-width: 1100px) {
    .s-loop .lp-step--plan { --lp-d: 150ms; }
    .s-loop .lp-step--do { --lp-d: 270ms; }
    .s-loop .lp-step--check { --lp-d: 390ms; }
    .s-loop .lp-step--act { --lp-d: 510ms; }
  }
}
/* The wait (home.js): until its part is in view every animation in it holds at its first frame. Paused, not removed,
   so the start never jumps. After the play rules and at their specificity (0,2,0), so it wins (the .is-wait pattern of
   page-next-8-hours.css 7b.3, not shared). No script, no .is-wait: it plays on load. */
.s-loop .is-wait,
.s-loop .is-wait * { animation-play-state: paused; }

@media (forced-colors: active) {
  .s-loop .lp-eg { border: 1px solid; }
  .s-loop .lp-bolt::before { forced-color-adjust: none; background: CanvasText; }
  .s-loop .step .lp-note { border-left-color: CanvasText; }
  .s-loop .step .lp-eg .lp-bar { forced-color-adjust: none; background: CanvasText; }
  .s-loop .step .lp-eg .lp-bars-goal { border-top-color: CanvasText; }
  .s-loop :is(.lp-was, .lp-now) { border: 1px solid; }
}

/* ==== 30-next-eight.css ==== */
/* Home "Bring clarity to your operational teams before they leave the huddle" (#ne-next-eight). The console is the
   shared run sheet: markup from Main::renderRunSheet() (content/classes/RunSheet.php), rules in site.css section 50
   "Run sheet" (.run-sheet), shared with /next-8-hours. Only the section head is styled here. */

/* Head: on wide screens the "How the huddle sets the shift" link sits beside the lead instead of on a row of its own. */
@media (min-width: 1024px) {
  .s-next-eight .section-head { grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--s-6); }
  .s-next-eight .section-head > :not(.link-ghost) { grid-column: 1; }
  .s-next-eight .section-head > .link-ghost { grid-column: 2; grid-row: 2; align-self: end; } /* row 2: the lead (D26: no eyebrow) */
}

/* ==== 45-tiers.css ==== */
/* ==========================================================================
   45-tiers.css · home row 6 "One plan, every tier" (I4), right after the decision demo. Every selector
   starts with .s-tiers. The tokens (.s-tiers {…}) are unchanged.
   The escalate demo (owner ask 2026-10-19, round 4): five moments of action #4207 on one card, driven by five
   .state-input radios #tl-s1 to #tl-s5 in a role="radiogroup" (DOM order is moment order, so the arrow keys step
   forward and back). Moments: 1 Line 4 finds the loss, 2 Maintenance prices the part (escalated), 3 the plant
   approves the spend (escalated), 4 Maintenance buys the part (de-escalated), 5 Line 4 installs it (de-escalated).
   Round 4 (owner: "the image covering the entire background with the card laying over top of it", no arrows, the
   chart at the bottom and more subtle): a control row (title, five step pills), then the stage, then
   the loss chart. The stage is one framed box: the moment's 1280x720 picture fills it edge to edge and crossfades;
   over it sit the caption (top-left panel), the toast (bottom-left) and the #4207 card in its .app frame (right,
   vertically centred), each in its own grid area, so the caption and the toast never run under the card. The tier
   rail and its move words are gone.
   Round 5 (owner: "make a chat with comment + sub actions"; the chart "make higher resolution / line chart"): the
   card's field grid, Owner row and problem statement give way to the action's thread, as the product draws it:
   comments (avatar, name and time, a white bubble), sub-action cards (assignee, due date and status in dashed
   controls) and the composer (Comment | Action, the text box, Send). A moment adds its entries at the bottom of a
   fixed-height thread; the sub-action it completes turns from Open to Done. Under the stage the chart is one quiet
   line: the 7-day rolling total of lost minutes for every day of weeks 33-49, muted red over the goal of 30 and
   muted green at or below it, 12px --ink-3 words, a 1px goal line; each moment reveals it to the end of a week.
   Every per-moment layer carries one class .tl-n1 to .tl-n5 and shows only on its moment. Layers are swapped with
   visibility (never display) inside a .dd-stack cell, so each cell keeps the height of its tallest layer and the
   section is the same height on all five moments. Thread entries (.tl-t1 to .tl-t5) show from their moment on
   (display), inside a viewport of fixed height, so the card never changes height either; the current moment's
   entries are tinted and marked "New" (.tl-new).
   Motion (no-preference only): the picture crossfades, the chart's reveal slides (clip-path, 600ms) with its end dot,
   and a new thread entry rises in. Reduced motion: no crossfade, no slide, and no scroll mode.
   Scroll mode (owner ask 2026-10-26, home.js adds .is-scrolly; the autoplay and its Pause/Replay button are gone):
   the panel's grid row becomes a tall track and the panel sticks below the site header (--tl-pin-top) while the
   scroll steps the radios 1 to 5 and back; .is-cut compacts the card and the stage when the panel is a little too
   tall for the screen, and below 768px a compact phone stage takes over (see "Phone scroll mode"); the current pill
   shows a thin bar for the scroll through its moment (--tl-p). Without the class (no script, reduced motion, a
   landscape phone, or a screen too short) the ladder is the click-driven one.
   Widths: from 1180px the card is 440px over the picture; at 1024-1179px it is a compact 360px card over the
   picture (shorter thread, one-row composer), so its left edge still clears the subject (the left 740px of each
   scene); at 768-1023px the card sits beside the picture (picture, caption and toast in column 1), so the footer
   label and the picture it changes share the screen; below 768px everything stacks.
   ========================================================================== */

.s-tiers {
  --tl-gutter: 36px;     /* the track's rail column */
  --tl-rail: 11px;       /* rail centre, from the track's left edge */
  --tl-node-y: 23px;     /* node centre, from a rung's top */
  --tl-rail-ink: color-mix(in srgb, var(--line-strong) 55%, transparent);
  --tl-pad: var(--s-5);
}
@media (max-width: 559px) { .s-tiers { --tl-gutter: 32px; --tl-rail: 9px; --tl-pad: var(--s-4); } }

/* One column at every width: the ladder (head and panel), then the link row. */
.s-tiers .tl-grid { display: grid; gap: var(--s-6); min-width: 0; }

/* The link row: where the depth lives (Action plans). Wraps; 40px taps on a phone (base link-ghost). */
.s-tiers .tl-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-6); max-width: none; min-width: 0; margin: 0; }

/* The ladder: head, the radiogroup (out of flow: its inputs are fixed .state-inputs), the panel. */
.s-tiers .tl-ladder { display: grid; gap: var(--s-5); align-content: start; min-width: 0; }
.s-tiers .tl-radios { position: absolute; width: 0; height: 0; overflow: hidden; }
.s-tiers .tl-ladder-head { display: grid; gap: var(--s-3); }
.s-tiers .tl-ladder-head h3 { max-width: 24em; }
.s-tiers .tl-ladder-lead { font-size: 16px; line-height: 1.55; color: var(--ink-2); }
/* The panel: the control row, the stage, the loss chart. No box of its own (round 4): the stage is the frame. */
.s-tiers .tl-panel { display: grid; gap: var(--s-3); min-width: 0; }
.s-tiers .tl-panel p { max-width: none; }
.s-tiers .tl-panel-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-4); }
.s-tiers .tl-panel-title { margin-right: auto; font-size: 13px; font-weight: 600; line-height: 1.5; color: var(--ink-3); }
/* The keyboard hint takes the title's place while a radio has keyboard focus: the same one 13px row (kept short
   enough to share it with the pills at 768px), so nothing shifts, and it is on screen with the pinned panel. */
.s-tiers:has(.tl-in:focus-visible) .tl-panel-title { display: none; }
.s-tiers .tl-panel-head .kbd-hint { margin: 0 auto 0 0; }

/* The five moments as pills: each is the label of its radio. The current one is filled. */
.s-tiers .tl-steps { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.s-tiers .tl-step { display: inline-flex; align-items: center; min-height: 28px; padding: 2px 10px; font-size: 13px; font-weight: 600; line-height: 1.3; color: var(--ink-2); background: var(--surface); border: 1px solid var(--hairline); border-radius: 999px; cursor: pointer; }
.s-tiers:has(#tl-s1:checked) .tl-step[for="tl-s1"],
.s-tiers:has(#tl-s2:checked) .tl-step[for="tl-s2"],
.s-tiers:has(#tl-s3:checked) .tl-step[for="tl-s3"],
.s-tiers:has(#tl-s4:checked) .tl-step[for="tl-s4"],
.s-tiers:has(#tl-s5:checked) .tl-step[for="tl-s5"] { color: var(--surface); background: var(--teal); border-color: var(--teal); }

/* Per-moment layers: hidden in place (visibility, so the cell keeps its tallest layer's height); the current moment's
   layers show. A layer can belong to several moments (.tl-n2 .tl-n3 …), so one rule per moment covers them all. */
.s-tiers :is(.tl-n1, .tl-n2, .tl-n3, .tl-n4, .tl-n5) { visibility: hidden; opacity: 0; }
.s-tiers:has(#tl-s1:checked) .tl-n1,
.s-tiers:has(#tl-s2:checked) .tl-n2,
.s-tiers:has(#tl-s3:checked) .tl-n3,
.s-tiers:has(#tl-s4:checked) .tl-n4,
.s-tiers:has(#tl-s5:checked) .tl-n5 { visibility: visible; opacity: 1; }

/* The loss chart, under the stage and quiet (round 5): one row of label | line | value in 12px --ink-3. The line is
   the 7-day rolling total of lost minutes (min/wk) for each of the 119 days of weeks 33-49, computed offline and
   inlined (viewBox 1180x210: 10 units a day, 210 minutes at the top of the 20px track, the goal of 30 at y 180). A
   1.5px non-scaling stroke drawn twice through two clipPaths split at the goal's y: muted red above, muted green at
   or below, each over a 10% fill between the line and the goal. The goal is a 1px hairline across the full width
   with "goal 30" just past its right end, on its height. Each moment reveals the line to the end of a week by
   clipping its wrapper to --tl-x (% of the width; 1: wk 41, 2: wk 42, 3: wk 43, 4: wk 46, 5: wk 49), and a small
   dot sits on the revealed end (--tl-y, px from the top). The value says over or under the goal in words. */
.s-tiers .tl-loss {
  --tl-over: color-mix(in srgb, var(--danger-ink) 50%, var(--surface));  /* muted red, over the goal */
  --tl-ok: color-mix(in srgb, var(--ok-ink) 62%, var(--surface));        /* muted green, at or below it */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-1) var(--s-4);
  min-width: 0;
}
.s-tiers .tl-loss-lbl, .s-tiers .tl-loss-val { font-size: 12px; line-height: 1.4; color: var(--ink-3); }
.s-tiers .tl-loss-val { font-weight: 600; text-align: left; }
.s-tiers .tl-track { --tl-x: 52.54; --tl-y: 2.57; position: relative; min-width: 0; height: 20px; margin-right: 52px; }
.s-tiers:has(#tl-s2:checked) .tl-track { --tl-x: 58.47; --tl-y: 2.19; }
.s-tiers:has(#tl-s3:checked) .tl-track { --tl-x: 64.41; --tl-y: 2.48; }
.s-tiers:has(#tl-s4:checked) .tl-track { --tl-x: 82.2; --tl-y: 2.38; }
.s-tiers:has(#tl-s5:checked) .tl-track { --tl-x: 100; --tl-y: 18.1; }
.s-tiers :is(.tl-goal, .tl-line-w, .tl-line) { position: absolute; inset: 0; display: block; width: 100%; height: 100%; overflow: visible; }
.s-tiers .tl-goal line { stroke: var(--ink-3); stroke-width: 1; vector-effect: non-scaling-stroke; opacity: .55; }
.s-tiers .tl-goal-w { position: absolute; left: calc(100% + 10px); top: 11px; font-size: 12px; line-height: 12px; color: var(--ink-3); white-space: nowrap; }
/* The reveal: everything right of --tl-x is clipped (4px of room above, below and left for the stroke). */
.s-tiers .tl-line-w { clip-path: inset(-4px calc(100% - var(--tl-x) * 1%) -4px -4px); }
.s-tiers .tl-over { color: var(--tl-over); }
.s-tiers .tl-ok { color: var(--tl-ok); }
.s-tiers .tl-ln { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
.s-tiers .tl-area { fill: currentColor; fill-opacity: .1; stroke: none; }
.s-tiers .tl-dot { position: absolute; left: calc(var(--tl-x) * 1%); top: calc(var(--tl-y) * 1px); width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%; background: var(--tl-over); box-shadow: 0 0 0 2px var(--bg); }
.s-tiers:has(#tl-s5:checked) .tl-dot { background: var(--tl-ok); }

/* The stage. Phone first (below 768px): one column, the caption, the toast and the card in flow, and no picture (owner
   2026-10-29: "Hide the image on mobile", so the card and the chart fit the screen). From 768px the five pictures are
   stacked absolutely in .tl-pic and cropped with object-fit: cover from 30% across, so the subject (the left 60% of
   each 1280x720 scene) survives the crop. */
.s-tiers .tl-stage { display: grid; gap: var(--s-4); min-width: 0; }
.s-tiers .tl-pic { position: relative; min-width: 0; aspect-ratio: 4 / 3; overflow: hidden; background: var(--bg); border: 1px solid var(--hairline); border-radius: var(--r-decision); }
.s-tiers .tl-pic > img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; object-position: 30% 50%; }
@media (max-width: 767px) { .s-tiers .tl-pic { display: none; } } /* display, so the lazy pictures never load on a phone */
.s-tiers .tl-caps { min-width: 0; }
.s-tiers .tl-cap { margin: 0; font-size: 15px; line-height: 1.5; color: var(--ink-2); }
.s-tiers .tl-cap-tier { display: block; margin-bottom: 2px; font-size: 12px; font-weight: 700; line-height: 1.4; letter-spacing: .04em; text-transform: uppercase; color: var(--teal-ink); }
.s-tiers .tl-cap b { display: block; margin-bottom: 2px; font-size: 16px; line-height: 1.35; font-weight: 700; color: var(--ink); }
/* The toast cell is an .app (the toast's styles live under .app) with the frame's box taken off. */
.s-tiers .tl-toasts { display: grid; min-width: 0; overflow: visible; background: none; border: 0; border-radius: 0; box-shadow: none; }

/* 768-1023px: the card beside the picture, not under it, so the footer label and the picture it changes are on
   screen together. Column 1 stacks the picture (the phone's 4:3 crop), the caption and the toast;
   column 2 is the card (all three rows). The toast row is the only fr row, so the card's extra height opens below
   the toast, not between it and the caption. (A card over the picture here would cover the subject.) */
@media (min-width: 768px) and (max-width: 1023px) {
  .s-tiers .tl-stage { grid-template-columns: minmax(0, 1fr) min(400px, 54%); grid-template-rows: auto auto 1fr; column-gap: var(--s-5); }
  .s-tiers .tl-pic { grid-column: 1; grid-row: 1; }
  .s-tiers .tl-caps { grid-column: 1; grid-row: 2; }
  .s-tiers .tl-toasts { grid-column: 1; grid-row: 3; justify-self: start; align-self: start; }
  .s-tiers .tl-fig { grid-column: 2; grid-row: 1 / span 3; }
}

/* 1024px and up: the picture fills the framed stage edge to edge (object-position 0%, so any crop comes off the right,
   under the card) and the rest floats over it on a grid. Column 1 holds the caption (row 1, top, a white panel) and
   the toast (row 2, bottom); the two auto rows stretch, so the slack sits between them. Column 2 holds the card (both
   rows, vertically centred), 24px in from the edges, at a width that keeps its left edge right of the scene's subject
   (image x 740 or more): 360px (a compact card, below) at 1024-1179, 440px from 1180. The stage is 16:9, but
   overflow: clip is not a scroll container, so its automatic minimum height is its content: if the card plus its
   24px insets is ever taller than 16:9, the stage grows to fit and the card never overflows. */
@media (min-width: 1024px) {
  .s-tiers .tl-stage {
    --tl-card-w: 360px;
    position: relative;
    grid-template-columns: minmax(0, 1fr) var(--tl-card-w);
    grid-template-rows: auto auto;  /* no fr row: Chrome leaves items that span an fr row out of the 16:9 floor */
    gap: var(--s-5);
    padding: var(--s-5);
    aspect-ratio: 16 / 9;
    overflow: clip;
    background: var(--bg);
    border: 1px solid var(--hairline);
    border-radius: var(--r-decision);
  }
  .s-tiers .tl-pic { position: absolute; inset: 0; z-index: 0; aspect-ratio: auto; border: 0; border-radius: calc(var(--r-decision) - 1px); }
  .s-tiers .tl-pic > img { object-position: 0% 50%; }
  .s-tiers :is(.tl-caps, .tl-toasts, .tl-fig) { position: relative; z-index: 1; }
  .s-tiers .tl-caps { grid-column: 1; grid-row: 1; justify-self: start; align-self: start; max-width: 300px; }
  /* The caption panel is on each caption, not on the cell, so it hugs the current moment's copy: a short caption
     leaves no empty band over the art (the cell still keeps the tallest caption's height, out of sight). Kept tight
     so the panel stays near the art's calm top-left band. */
  .s-tiers .tl-cap {
    justify-self: start;
    align-self: start;
    padding: 9px 13px;
    font-size: 13px;
    line-height: 1.4;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    border: 1px solid var(--hairline);
    border-radius: var(--r-card);
    box-shadow: 0 6px 20px rgba(20, 40, 80, .12);
  }
  .s-tiers .tl-cap-tier { margin-bottom: 0; }
  .s-tiers .tl-cap b { font-size: 14px; }
  .s-tiers .tl-toasts { grid-column: 1; grid-row: 2; justify-self: start; align-self: end; }
  .s-tiers .tl-fig { grid-column: 2; grid-row: 1 / -1; align-self: center; }
  .s-tiers .tl-app { box-shadow: 0 24px 56px rgba(16, 32, 64, .30), 0 4px 12px rgba(16, 32, 64, .16); }
}
@media (min-width: 1180px) {
  .s-tiers .tl-stage { --tl-card-w: 440px; }
  .s-tiers .tl-caps { max-width: 340px; }
  .s-tiers .tl-cap { padding: 10px 14px; font-size: 14px; }
  .s-tiers .tl-cap b { font-size: 15px; }
}

/* The card (#4207, the shared .app-drawer in an .app frame), round 5: the head (eyebrow, title, board path), the chip
   row (status, ⚡ KPI chip, due date), the thread, the composer, the footer. No field grid, no Owner row and no
   problem statement: the first comment states the problem. The crumb, the path, the status and the footer each hold
   one cell per moment, swapped by visibility, and the thread has a fixed height, so the card keeps one height on all
   five moments. The drawer is a container (tl-card) so the composer can fold to two rows when the card is narrow. */
.s-tiers .tl-fig { min-width: 0; margin: 0; }
.s-tiers .tl-app .app-stage > .app-drawer { flex: 1 1 100%; width: 100%; max-width: none; margin-left: 0; box-shadow: var(--app-shadow); }
.s-tiers .tl-card { container: tl-card / inline-size; }
.s-tiers .tl-card .app-drawer-h { padding: 12px 16px 10px; }
.s-tiers .tl-card .app-drawer-b { gap: 10px; padding: 10px 16px 12px; }
.s-tiers .tl-card .app-drawer-t { margin-top: 4px; font-size: var(--app-fs-h3); font-weight: 700; line-height: 1.3; letter-spacing: normal; color: var(--app-ink); }
.s-tiers :is(.tl-crumb, .tl-path) { display: grid; min-width: 0; }
.s-tiers .tl-crumb { display: inline-grid; }
.s-tiers .tl-b { grid-area: 1 / 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.s-tiers .tl-path .tl-b { white-space: normal; }
@container app-bar (max-width: 299px) { .s-tiers .tl-crumb-pre { display: none; } }
/* The status takes the first chip row on its own (the KPI chip and the due date share the second), so the room the cell
   reserves for its widest pill never opens a gap before the next chip. Each pill hugs its own text; the cell still
   reserves the widest pill, so the card never changes height. */
.s-tiers .tl-status { flex: 1 0 100%; max-width: 100%; }
.s-tiers .tl-status > * { justify-self: start; width: fit-content; }

/* The thread (the product's action thread): a fixed-height well, newest entry at the bottom. An entry is a comment
   (.app-msg: avatar, then the name and time over a white bubble), and under some a sub-action card: its title, then
   three dashed controls (assignee with a person glyph and caret, due date with a calendar glyph, status with a glyph,
   the word and a caret). An entry shows from its moment on (.tl-t1 to .tl-t5, by display, so the list grows). The well
   is a column flex box packed to its end with overflow hidden, so once the entries outgrow it the oldest run out of
   the top, under a soft fade. A sub-action's status is a .dd-stack: Open (blue, a ring) until the next tier completes
   it, then Done (green, a check); the word carries the state. The current moment's entries take the soft selection
   tint and a "New" tag; a status that turns Done on this moment takes a soft green fill. Text is 12px or more.
   Heights: 256px from 768 (the card stays inside the 16:9 stage at 1440) and up to 340px on a phone (the entries
   wrap more), sized to the screen (see the phone stage, after scroll mode). Each is tall enough for moment 1's two
   comments whole (so moment 1 has no fade) and, on moments 2-5, for the new entries plus the controls row of the
   sub-action they complete, below the fade. The compact card over the
   picture at 1024-1179 is narrower: its thread clips moment 1 too, and at 1024-1099 (176px) it shows only the
   newest entry whole (see the compact card, below the composer). */
.s-tiers .tl-thread {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 8px;
  height: 256px;
  margin: 0;
  padding: 0 8px 10px;
  overflow: hidden;
  list-style: none;
  background: var(--app-well);
  border: 1px solid var(--app-line-2);
  border-radius: var(--app-r);
}
.s-tiers .tl-thread::before { content: ""; position: absolute; inset: 0 0 auto; z-index: 1; height: 36px; background: linear-gradient(var(--app-well), transparent); pointer-events: none; }
.s-tiers:has(#tl-s1:checked) .tl-thread::before { opacity: 0; } /* moment 1: nothing is clipped yet */
.s-tiers .tl-thread > li { flex: none; }
.s-tiers:has(#tl-s1:checked) :is(.tl-t2, .tl-t3, .tl-t4, .tl-t5),
.s-tiers:has(#tl-s2:checked) :is(.tl-t3, .tl-t4, .tl-t5),
.s-tiers:has(#tl-s3:checked) :is(.tl-t4, .tl-t5),
.s-tiers:has(#tl-s4:checked) .tl-t5 { display: none; }
.s-tiers .tl-thread .app-msg small { margin-bottom: 3px; }
.s-tiers .tl-thread .app-msg small b { color: var(--app-ink); }
/* Avatars: one calm colour per person (decoration; the name is always beside it). */
.s-tiers .tl-av-mc { color: var(--app-s20-ink); background: var(--app-s20-bg); border-color: color-mix(in srgb, var(--app-s20-ink) 30%, var(--app-card)); }
.s-tiers .tl-av-lo { color: var(--app-s30-ink); background: var(--app-s30-bg); border-color: color-mix(in srgb, var(--app-s30-ink) 30%, var(--app-card)); }
.s-tiers .tl-av-sw { color: var(--app-warn-ink); background: var(--app-warn-soft); border-color: color-mix(in srgb, var(--app-warn-ink) 30%, var(--app-card)); }
.s-tiers .tl-bub { padding: 6px 10px; font-size: 13px; line-height: 1.4; color: var(--app-ink); background: var(--app-card); border: 1px solid var(--app-line); border-radius: 2px 10px 10px 10px; }
.s-tiers .tl-sub { margin-top: 6px; padding: 8px 9px; background: var(--app-card); border: 1px solid var(--app-line); border-radius: var(--app-r); }
.s-tiers .tl-sub-t { font-size: 13px; font-weight: 700; line-height: 1.35; color: var(--app-ink); }
.s-tiers .tl-sub-c { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.s-tiers .tl-ctl { display: inline-flex; align-items: center; gap: 4px; min-height: 24px; padding: 0 7px; font-size: 12px; font-weight: 600; line-height: 1.2; color: var(--app-ink-2); white-space: nowrap; background: var(--app-card); border: 1px dashed var(--app-owner-line); border-radius: var(--app-r-round); }
.s-tiers .tl-ctl.dd-stack { display: inline-grid; align-items: center; }
.s-tiers .tl-ctl .app-i { width: 13px; height: 13px; vertical-align: 0; }
.s-tiers .tl-ctl .app-i--down { width: 12px; height: 12px; color: var(--app-mute); }
.s-tiers .tl-st { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; color: var(--app-nogoal-ink); }
.s-tiers .tl-st--done { color: var(--app-good-ink); }
.s-tiers:has(#tl-s1:checked) .tl-t1 :is(.tl-bub, .tl-sub),
.s-tiers:has(#tl-s2:checked) .tl-t2 :is(.tl-bub, .tl-sub),
.s-tiers:has(#tl-s3:checked) .tl-t3 :is(.tl-bub, .tl-sub),
.s-tiers:has(#tl-s4:checked) .tl-t4 :is(.tl-bub, .tl-sub),
.s-tiers:has(#tl-s5:checked) .tl-t5 :is(.tl-bub, .tl-sub) { background: var(--select-bg); border-color: var(--app-primary-line); }
.s-tiers:has(#tl-s2:checked) .tl-done2,
.s-tiers:has(#tl-s3:checked) .tl-done3,
.s-tiers:has(#tl-s4:checked) .tl-done4,
.s-tiers:has(#tl-s5:checked) .tl-done5 { background: var(--app-good-soft); border-color: color-mix(in srgb, var(--app-good) 55%, var(--app-card)); }
.s-tiers .tl-new { display: inline-block; margin-left: 6px; padding: 0 6px; font-size: 12px; font-weight: 700; line-height: 16px; letter-spacing: 0; text-transform: none; color: var(--app-primary-dark); background: var(--app-card); border: 1px solid var(--app-primary-line); border-radius: 8px; vertical-align: 1px; }

/* The composer (drawn, aria-hidden): Comment | Action (Comment selected), the text box with its placeholder and a
   paperclip, and the dark Send pill with a paper plane. One row; two rows (the segments above) on a drawer narrower
   than 420px; one row without the segments in the compact card. */
.s-tiers .tl-comp { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 6px; }
.s-tiers .tl-seg { min-height: 32px; }
.s-tiers .tl-seg > * { padding: 0 10px; }
.s-tiers .tl-cbox { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-width: 0; min-height: 32px; padding: 0 10px; overflow: hidden; color: var(--app-mute); white-space: nowrap; }
.s-tiers .tl-cbox .app-i { width: 14px; height: 14px; }
.s-tiers .tl-send { gap: 6px; min-height: 32px; padding: 4px 12px; color: var(--app-card); background: var(--app-navy); border-color: var(--app-navy); border-radius: var(--app-r-round); }
.s-tiers .tl-send .app-i { width: 14px; height: 14px; }
/* Two rows below a 420px drawer: one row there would squeeze the text box under its placeholder and paperclip. The
   placeholder also truncates (.tl-ph), so a wider system font never pushes the paperclip out of the box. */
.s-tiers .tl-ph { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
@container tl-card (max-width: 419px) {
  .s-tiers .tl-comp { grid-template-columns: minmax(0, 1fr) auto; }
  .s-tiers .tl-seg { grid-column: 1 / -1; justify-self: start; }
}
/* The compact card (1024-1179px, 360px over the picture): the strip at the kit's 44px, tighter head and body, the
   sub-action controls without their carets (one row), the composer as one row without the Comment | Action switch,
   and no disabled Escalate on moment 5 (so that footer stays one row). The thread is 176px at 1024-1099 (card 520px:
   at 1024 the stage is 570px and the card's left edge lands at image x 747) and the full 256px from 1100 (card
   600px: at 1100 the stage is 648px and the card starts at image x 742). Moment 1 clips here, so it keeps the fade. */
@media (min-width: 1024px) and (max-width: 1179px) {
  .s-tiers .tl-app .app-bar { min-height: 44px; padding: 0 12px; gap: 6px; }
  .s-tiers .tl-card .app-drawer-h { padding: 10px 14px 8px; }
  .s-tiers .tl-card .app-drawer-b { gap: 8px; padding: 8px 14px 10px; }
  .s-tiers .tl-thread { height: 176px; }
  .s-tiers:has(#tl-s1:checked) .tl-thread::before { opacity: 1; }
  .s-tiers .tl-thread { padding: 0 5px 8px; }
  .s-tiers .tl-thread .app-msg { gap: 6px; }
  .s-tiers .tl-sub { padding: 7px; }
  .s-tiers .tl-ctl { padding: 0 5px; }
  .s-tiers .tl-ctl .app-i--down { display: none; }
  .s-tiers .tl-comp { grid-template-columns: minmax(0, 1fr) auto; }
  .s-tiers .tl-comp .tl-seg { display: none; }
  .s-tiers .tl-f .app-btn.off { display: none; }
}
@media (min-width: 1024px) and (max-width: 1099px) { .s-tiers .tl-thread::before { height: 16px; } } /* the newest entry's name row can sit at the top */
@media (min-width: 1100px) and (max-width: 1179px) { .s-tiers .tl-thread { height: 256px; } }
/* Glyphs the kit does not carry (24x24 masks, stroke 1.8, like section 49q). */
.s-tiers .tl-i-user { --app-i: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4.5 20c1.4-3.7 4.2-5.6 7.5-5.6s6.1 1.9 7.5 5.6'/%3E%3C/svg%3E"); }
.s-tiers .tl-i-cal { --app-i: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15.5' rx='2'/%3E%3Cpath d='M3.5 10h17M8 3v4M16 3v4'/%3E%3C/svg%3E"); }
.s-tiers .tl-i-ring { --app-i: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4'%3E%3Ccircle cx='12' cy='12' r='7.5'/%3E%3C/svg%3E"); }
.s-tiers .tl-i-clip { --app-i: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m20 11.5-8.4 8.4a5 5 0 0 1-7.1-7.1l9-9a3.3 3.3 0 0 1 4.7 4.7l-9 9a1.7 1.7 0 0 1-2.4-2.4l8.3-8.3'/%3E%3C/svg%3E"); }
.s-tiers .tl-i-send { --app-i: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 3 10.5 13.5M21 3l-6.5 18-4-7.5L3 9.5z'/%3E%3C/svg%3E"); }

/* The footer: one layer per moment, each row holding that moment's own buttons. Ghost actions sit at the end. */
.s-tiers .tl-foot { flex: 1 1 100%; min-width: 0; }
.s-tiers .tl-f { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.s-tiers .tl-f > .ghost { margin-left: auto; }
.s-tiers .tl-f .app-btn.off { cursor: not-allowed; }
/* Phone: the Open link takes row 1 and the action buttons row 2, so every moment's footer is the same two rows. */
@media (max-width: 767px) { .s-tiers .tl-f > a.app-btn { flex: 1 1 100%; } }
/* Scroll mode (home.js adds .is-scrolly; see the head of this block). The ladder's second row (the panel's) is the
   track, --tl-track-h tall (the panel plus the run), and the panel sticks at --tl-pin-top at the top of that row
   (start, never stretched). A screen a little short for the panel: .is-cut drops the stage's 16:9 for
   --tl-stage-h (so the stage keeps the full width and the picture's cover crop only loses some top and bottom) and
   sets the thread to --tl-thread-h, with a 16px top fade (moment 1 too when it clips: --tl-fade1). Shorter still, .is-tight
   also takes the drawn composer away (aria-hidden) and tightens the strip, the card's paddings, the panel's gaps and
   the stage's inset. --tl-thread-min is the shortest thread home.js may set per card width: tall enough that each
   moment's new entries and the sub-action control they turn Done stay below the fade (440px card 214, 360px card
   228; at 768-1023 the card is beside the picture and keeps its 256, and at 1024-1099 the thread is already its
   compact 176). The current pill's bar: a 2px teal rule 4px under the pill, scaled to --tl-p (the others carry none). */
.s-tiers.is-scrolly { --tl-thread-min: 256px; }
@media (min-width: 1024px) { .s-tiers.is-scrolly { --tl-thread-min: 228px; } }
@media (min-width: 1180px) { .s-tiers.is-scrolly { --tl-thread-min: 214px; } }
.s-tiers.is-scrolly .tl-ladder { grid-template-rows: auto var(--tl-track-h, auto); }
.s-tiers.is-scrolly .tl-panel { position: sticky; top: var(--tl-pin-top, 80px); align-self: start; }
.s-tiers.is-cut .tl-stage { aspect-ratio: auto; height: var(--tl-stage-h, auto); }
/* A cut stage is wider than 16:9, so the cover crop takes height: take it mostly off the bottom (behind the toast),
   so the scene's callouts in the upper half stay clear of the caption panel. */
@media (min-width: 1024px) { .s-tiers.is-cut .tl-pic > img { object-position: 0% 25%; } }
.s-tiers.is-cut .tl-thread { height: var(--tl-thread-h); }
.s-tiers.is-cut .tl-thread::before { height: 16px; }
.s-tiers.is-cut:has(#tl-s1:checked) .tl-thread::before { opacity: var(--tl-fade1, 1); }
.s-tiers.is-tight .tl-panel { gap: var(--s-2); }
@media (min-width: 1024px) { .s-tiers.is-tight .tl-stage { padding: var(--s-3); } }
.s-tiers.is-tight .tl-app .app-bar { min-height: 44px; }
.s-tiers.is-tight .tl-card .app-drawer-h { padding-top: 8px; padding-bottom: 6px; }
.s-tiers.is-tight .tl-card .app-drawer-b { gap: 8px; padding-top: 8px; padding-bottom: 8px; }
.s-tiers.is-tight .tl-card .app-drawer-f { padding-top: 8px; padding-bottom: 8px; }
.s-tiers.is-tight .tl-comp { display: none; }
/* Phone (below 768px; owner 2026-10-26: "Reapply to mobile", 2026-10-29: "Hide the image on mobile. Let's make sure
   the chat window and the chart at the bottom is within the mobile screen's vertical height"). The stacked phone
   stage was ~1400px, so every phone gets the compact stage, in scroll mode and click-driven alike (reduced motion,
   no script, a short screen, or a panel fitPhone() cannot fit): the title and hint go, and the pills become
   numbered dots with the current one spelled out (1 [2 Maintenance] 3 4 5); the picture is gone (above); the
   caption is its title alone (the thread says the rest); the toast goes (the crumb and the footer carry the tier
   and the move); the card keeps what changes on each moment: the crumb, the status chip, the thread (no avatars,
   the names stay, so it wraps less; carets go) and the footer on one row; the drawer head, the KPI and due chips,
   the composer and the frame's inset go; the loss chart is one row (line and value, below).
   The thread's height: click-driven, the screen's height (100svh) less the header and the rest of the panel
   (--tl-chrome, 248px at 360-767 wide: pills, caption, bar, crumb, chip, footer, chart, gaps and 8px clear above
   and below), between the floor and 340px, so the panel fits under the header on its own. In scroll mode
   fitPhone() sets --tl-thread-h; it still grows the band (--tl-band-h, 110px at its least) by up to 40px before
   the thread, and with no band left that room goes to the thread here (capped at 340px). --tl-thread-min keeps
   each moment's new entries and the control they turn Done in view below the fade (214 from 375px, 232 below). */
@media (max-width: 767px) {
  .s-tiers, .s-tiers.is-scrolly { --tl-thread-min: 214px; --tl-chrome: 248px; }
  .s-tiers .tl-panel { gap: var(--s-2); }
  .s-tiers :is(.tl-panel-title, .tl-cap-tier, .tl-cap-x, .tl-toasts, .tl-comp) { display: none; }
  .s-tiers .tl-panel .tl-panel-head .kbd-hint { display: none; } /* outranks site.css's focus-visible show (0,4,0) */
  .s-tiers .tl-steps { flex-wrap: nowrap; gap: 6px; }
  .s-tiers .tl-step { justify-content: center; min-width: 28px; padding: 2px 9px; }
  .s-tiers .tl-step-t { display: none; }
  .s-tiers:has(#tl-s1:checked) .tl-step[for="tl-s1"] .tl-step-t,
  .s-tiers:has(#tl-s2:checked) .tl-step[for="tl-s2"] .tl-step-t,
  .s-tiers:has(#tl-s3:checked) .tl-step[for="tl-s3"] .tl-step-t,
  .s-tiers:has(#tl-s4:checked) .tl-step[for="tl-s4"] .tl-step-t,
  .s-tiers:has(#tl-s5:checked) .tl-step[for="tl-s5"] .tl-step-t { display: inline; }
  .s-tiers .tl-stage { gap: var(--s-2); }
  .s-tiers .tl-cap b { margin: 0; font-size: 15px; }
  .s-tiers .tl-app .app-bar { min-height: 40px; padding: 0 12px; }
  .s-tiers .tl-app .app-stage { padding: 0; }
  .s-tiers .tl-app .app-stage > .app-drawer { border: 0; border-radius: 0; box-shadow: none; }
  .s-tiers .tl-card .app-drawer-h { display: none; }
  .s-tiers .tl-card .app-drawer-b { gap: 8px; padding: 8px 10px; }
  .s-tiers .tl-card .app-chips > :not(.tl-status) { display: none; }
  .s-tiers .tl-thread { height: clamp(var(--tl-thread-min), calc(100svh - var(--header-h) - var(--tl-chrome)), 340px); padding: 0 5px 8px; }
  .s-tiers.is-scrolly .tl-thread { height: min(340px, calc(var(--tl-thread-h, 340px) + var(--tl-band-h, 110px) - 110px)); }
  .s-tiers .tl-thread::before { height: 16px; }
  .s-tiers:has(#tl-s1:checked) .tl-thread::before { opacity: var(--tl-fade1, 1); }
  .s-tiers .tl-thread .app-msg { grid-template-columns: minmax(0, 1fr); }
  .s-tiers .tl-thread .app-msg > .app-av { display: none; }
  .s-tiers .tl-ctl { padding: 0 5px; }
  .s-tiers .tl-ctl .app-i--down { display: none; }
  .s-tiers .tl-card .app-drawer-f { padding: 8px 10px; }
  .s-tiers .tl-f > a.app-btn { flex: 0 1 auto; }
  .s-tiers .tl-f .app-btn.off { display: none; }
}
@media (max-width: 374px) { .s-tiers, .s-tiers.is-scrolly { --tl-thread-min: 232px; } }
.s-tiers.is-scrolly .tl-step { position: relative; }
.s-tiers.is-scrolly .tl-step::after { content: ""; position: absolute; left: 8px; right: 8px; bottom: -6px; height: 2px; border-radius: 1px; background: var(--teal); transform: scaleX(var(--tl-p, 0)); transform-origin: 0 50%; pointer-events: none; }

/* Keyboard: the radios are the control (the arrow keys step through the moments). Mirror focus on the card and labels. */
.s-tiers:has(.tl-in:focus-visible) .tl-app { outline: 2px solid var(--focus); outline-offset: 3px; }
.s-tiers:has(#tl-s1:focus-visible) label[for="tl-s1"],
.s-tiers:has(#tl-s2:focus-visible) label[for="tl-s2"],
.s-tiers:has(#tl-s3:focus-visible) label[for="tl-s3"],
.s-tiers:has(#tl-s4:focus-visible) label[for="tl-s4"],
.s-tiers:has(#tl-s5:focus-visible) label[for="tl-s5"] { outline: 2px solid var(--focus); outline-offset: 2px; }

/* Motion (no-preference only): the picture crossfades (400ms); the chart's reveal slides to the new week and its dot
   follows (600ms); a thread entry rises 8px into place as it appears (it runs when the entry stops being display: none). */
@keyframes tl-enter {
  from { opacity: 0; transform: translateY(8px); }
}
@media (prefers-reduced-motion: no-preference) {
  .s-tiers .tl-pic > img { transition: opacity 400ms ease, visibility 400ms ease; }
  .s-tiers .tl-line-w { transition: clip-path 600ms ease-out; }
  .s-tiers .tl-dot { transition: left 600ms ease-out, top 600ms ease-out, background-color 600ms ease-out; }
  .s-tiers .tl-thread > li { animation: tl-enter 320ms ease-out both; }
}

/* Phone: the chart is one row, the line and its value (the label goes; the caption and the value say what it is). */
@media (max-width: 767px) {
  .s-tiers .tl-loss { grid-template-columns: minmax(0, 1fr) auto; }
  .s-tiers .tl-loss-lbl { display: none; }
  .s-tiers .tl-loss-val { grid-column: 2; grid-row: 1; }
  .s-tiers .tl-track { grid-column: 1; grid-row: 1; }
}

/* Forced colors: the stage, the caption panel and the pills keep an edge, and the current moment's pill takes
   Highlight; the toast cell (an .app) drops the frame's edge (the toast keeps its own); the line, its fill, its dot
   and the goal draw in CanvasText; the thread's top fade (a gradient in page colours) goes. */
@media (forced-colors: active) {
  .s-tiers .tl-step { border: 1px solid; }
  .s-tiers:has(#tl-s1:checked) .tl-step[for="tl-s1"],
  .s-tiers:has(#tl-s2:checked) .tl-step[for="tl-s2"],
  .s-tiers:has(#tl-s3:checked) .tl-step[for="tl-s3"],
  .s-tiers:has(#tl-s4:checked) .tl-step[for="tl-s4"],
  .s-tiers:has(#tl-s5:checked) .tl-step[for="tl-s5"] { forced-color-adjust: none; color: HighlightText; background: Highlight; border-color: Highlight; } /* the current moment (house rule: selected = Highlight) */
  .s-tiers .tl-toasts { border: 0; }
  .s-tiers :is(.tl-over, .tl-ok) { forced-color-adjust: none; color: CanvasText; }
  .s-tiers .tl-goal line { forced-color-adjust: none; stroke: CanvasText; }
  .s-tiers .tl-dot { forced-color-adjust: none; background: CanvasText; box-shadow: none; }
  .s-tiers .tl-thread::before { display: none; }
  .s-tiers.is-scrolly .tl-step::after { forced-color-adjust: none; background: CanvasText; }
}
@media (forced-colors: active) and (min-width: 1024px) {
  .s-tiers .tl-stage, .s-tiers .tl-cap { border: 1px solid; }
}

/* ==== 95-closing.css ==== */
/* Moved to site.css §24a on 2026-10-28: the closing band (.s-closing) is now every page's last section. */
