/* =========================================================
   PRAVA — capability, approach, pricing, auth and policy pages
   Depends on css/styles.css for tokens, shell and buttons.
   1. Page hero
   2. Prose and section rhythm
   3. Scope / feature grids
   4. Numbered stages
   5. Pricing
   6. Auth
   7. Policy documents
   8. Closing call to action
   9. Responsive
   ========================================================= */

/* ---------- 1. Page hero ---------- */
.phero {
  position: relative;
  padding: calc(var(--nav-h) + clamp(52px, 10vh, 124px)) var(--pad) clamp(40px, 6vh, 72px);
  border-bottom: 1px solid var(--line-soft);
}

.phero__inner {
  position: relative;
  z-index: 1;
  max-width: 70ch;
}

.phero__title {
  margin: 20px 0 clamp(20px, 2.8vh, 32px);
  font-size: clamp(2.2rem, 5.4vw, 4.1rem);
  line-height: 1;
  letter-spacing: -.048em;
}

.phero__lede {
  max-width: 58ch;
  color: var(--text-mid);
  font-size: clamp(1.02rem, .55vw + .92rem, 1.2rem);
  line-height: 1.62;
}

.phero__definition {
  max-width: 62ch;
  color: var(--text-mid);
  font-size: clamp(0.96rem, .45vw + .88rem, 1.1rem);
  line-height: 1.65;
  margin-top: 14px;
}

.phero__definition strong {
  color: var(--text);
  font-weight: 600;
}

.phero__meta {
  display: flex;
  gap: clamp(20px, 4vw, 56px);
  flex-wrap: wrap;
  margin-top: clamp(30px, 4.5vh, 50px);
  padding-top: clamp(20px, 3vh, 28px);
  border-top: 1px solid var(--line);
}

.phero__fact {
  display: grid;
  gap: 8px;
}

.phero__key {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.phero__val {
  font-size: .98rem;
  color: var(--text);
}

/* ---- a hero that carries artwork ----
   Only the pages given .phero--art. The layer is sized to the image and
   sits inside the hero, so it scrolls away with it rather than following
   the reader down the page, and it is held off the right edge by the
   page's own measure.

   Blended rather than masked at the edges: the renders sit on a flat
   ground, so screen drops the black and multiply drops the white, and
   nothing has to be guessed about where the artwork ends. */
.phero--art {
  position: relative;
  overflow: hidden;
  min-height: clamp(440px, 54vh, 620px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.phero__art {
  position: absolute;
  top: 50%;
  /* held off the right edge by the page's own measure unless the page
     says otherwise -- artwork on the right was exempted from the
     content margin, and the hero clips, so nothing can escape */
  right: var(--art-right, var(--pad-r));
  transform: translateY(-50%);
  height: var(--art-h, 76%);
  /* the page names its own pair of renders, their proportions, and how
     tall the layer stands -- two cuts of the same subject are not always
     the same shape */
  aspect-ratio: var(--art-ratio);
  pointer-events: none;
  background-image: var(--art);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  filter: var(--art-filter, none);
  -webkit-mask-image: var(--art-mask, none);
  mask-image: var(--art-mask, none);
  mix-blend-mode: screen;
}

:root[data-theme="light"] .phero__art {
  right: var(--art-right-light, var(--art-right, var(--pad-r)));
  background-image: var(--art-light);
  aspect-ratio: var(--art-ratio-light);
  height: var(--art-h-light, var(--art-h, 76%));
  filter: var(--art-filter-light, var(--art-filter, none));
  -webkit-mask-image: var(--art-mask-light, var(--art-mask, none));
  mask-image: var(--art-mask-light, var(--art-mask, none));
  mix-blend-mode: multiply;
}

.phero__art--endpoint {
  --art: url("../images/endpoint-black.png");
  --art-ratio: 673 / 779;
  --art-light: url("../images/endpoint-white.png");
  --art-ratio-light: 705 / 746;
}

.phero__art--malware {
  --art: url("../images/malware-black.png");
  --art-ratio: 725 / 743;
  --art-light: url("../images/malware-white.png");
  --art-ratio-light: 810 / 891;
  /* the light cut is a grey room, not a white ground: the lift takes it
     to white, where multiply stops tinting the page, and the mask takes
     the edges and the room's corner with it */
  --art-filter-light: brightness(1.07) contrast(1.05);
  --art-mask-light: radial-gradient(ellipse 60% 62% at 50% 48%,
      #000 40%, rgba(0, 0, 0, .55) 72%, transparent 95%);
}

/* Both cuts of this one are landscape, and the light one wider again, so
   each stands at the height that brings them to the same width. */
.phero__art--why {
  --art: url("../images/why-bg.png");
  --art-ratio: 1536 / 1024;
  --art-light: url("../images/why-bg-light.png");
  --art-ratio-light: 1536 / 1024;
  --art-h: 62%;
  /* set further out than the page measure, floored so that a narrow
     gutter cannot push it past the hero edge and get it cropped */
  --art-right: max(8px, calc(var(--pad-r) - min(56px, 3.2vw)));
  /* Neither cut sits on a pure ground -- the dark one is near-black and
     the light one near-white -- so screen and multiply both leave a
     faint rectangle. The mask feathers the edge away; the lift takes
     the light cut the last of the way to white. */
  --art-mask: radial-gradient(ellipse 58% 62% at 50% 50%,
      #000 42%, rgba(0, 0, 0, .55) 74%, transparent 96%);
  --art-filter-light: contrast(1.75);
}

/* nearly 2:1, so it stands much shorter than the others to come out at
   a comparable width */
.phero__art--landscape {
  --art: url("../images/landscape-bg.png");
  --art-ratio: 1406 / 727;
  --art-light: url("../images/landscape-bg-light.png");
  --art-ratio-light: 1536 / 1024;
  /* the two cuts are a long way from the same shape -- 1.93 against
     1.50 -- so the light one stands much taller to arrive at the same
     width on the page */
  --art-h: 57%;
  --art-h-light: 73%;
  --art-right: max(8px, calc(var(--pad-r) - min(56px, 3.2vw)));
  /* a wider ellipse than the others: the mesh runs to both edges of
     this frame, so a tight mask would cut the subject rather than the
     ground around it */
  --art-mask: radial-gradient(ellipse 76% 64% at 50% 50%,
      #000 46%, rgba(0, 0, 0, .55) 78%, transparent 97%);
  /* the light cut is far taller in proportion, so its mask is too --
     the shared one would have taken the top and bottom of the subject */
  --art-mask-light: radial-gradient(ellipse 78% 80% at 50% 50%,
      #000 54%, rgba(0, 0, 0, .6) 84%, transparent 99%);
  /* Contrast alone was the wrong lever for this render: it pivots at
     mid-grey, and this subject is light grey on near-white, so raising
     it pushed the peaks lighter rather than darker. Dimming first, then
     pulling the contrast hard, darkens the subject and still clips the
     ground to white. */
  --art-filter-light: grayscale(1) brightness(.82) contrast(2.6);
}

/* Both cuts are landscape and the dark one is the wider of the two, so
   the light one stands taller to reach the same width on the page. */
.phero__art--threat {
  --art: url("../images/threatIntel-black.png");
  --art-ratio: 1099 / 691;
  --art-light: url("../images/threatIntel-white.png");
  --art-ratio-light: 1137 / 767;
  /* sized so the node on the render's left edge clears the hero copy */
  --art-h: 52%;
  --art-h-light: 56%;
}

/* a wide ~2:1 render, so it stands short; the default right position
   holds it to the content right margin */
.phero__art--thinks {
  --art: url("../images/thinks-black.png");
  --art-ratio: 1271 / 635;
  --art-light: url("../images/thinks-white.png");
  --art-ratio-light: 1345 / 677;
  --art-h: 38%;
}

.phero__art--phishing {
  --art: url("../images/email-black.png");
  --art-ratio: 1042 / 758;
  --art-light: url("../images/email-white.png");
  --art-ratio-light: 1170 / 751;
  /* sized so the person icon on the render's left edge stays clear of
     the hero copy rather than sitting on it */
  --art-h: 62%;
  --art-h-light: 54%;
}

/* the light render of this pair is cut much wider than the dark one, so
   it is set shorter to arrive at the same width on the page */
.phero__art--pentest {
  --art: url("../images/penetration-black.png");
  --art-ratio: 1536 / 1024;
  --art-light: url("../images/penetration-white.png");
  --art-ratio-light: 1076 / 438;
  /* the dark cut of this one is mostly empty ground around the subject,
     so it stands taller than the light cut to read at the same size */
  --art-h: 74%;
  --art-h-light: 33%;
  /* The dark cut sits further out than the others: at this size the
     left of its frame would otherwise reach the rule above the meta
     row. Floored, because below about 1400px the gutter is narrower
     than the shift and the hero would crop its right edge off.

     The light cut is framed tight around the subject and does not need
     it, so it stays on the page's own measure like every other page. */
  --art-right: max(8px, calc(var(--pad-r) - min(120px, 6.6vw)));
  --art-right-light: var(--pad-r);
}

/* the copy sits above it, and a scrim keeps the left clear -- on .phero
   itself, not on the art, so a theme filter could never invert it */
.phero--art .phero__inner {
  position: relative;
  z-index: 2;
}

.phero--art::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg,
      var(--bg) 0%,
      var(--bg) 38%,
      transparent 62%);
}

@media (max-width: 900px) {

  .phero__art {
    height: calc(var(--art-h, 76%) * .71);
    top: 30%;
    opacity: .3;
  }

  :root[data-theme="light"] .phero__art {
    height: calc(var(--art-h-light, var(--art-h, 76%)) * .71);
  }
}

/* ---------- 2. Prose and section rhythm ---------- */
.psec {
  position: relative;
  padding: clamp(50px, 8vh, 100px) var(--pad);
  border-bottom: 1px solid var(--line-soft);
}

.psec--quiet {
  background: var(--bg-elev);
}

.psec__head {
  max-width: 54ch;
}

/* Centered section head & FAQ (applies to every page with an FAQ) */
.psec:has(.pfaq) .psec__head,
.psec[aria-labelledby*="faq"] .psec__head,
.psec[aria-labelledby*="secfaq"] .psec__head,
.psec--center .psec__head,
.psec__head--center {
  max-width: 54ch;
  margin-inline: auto;
  text-align: center;
}

/* for a heading meant to hold one line */
/* a centred section: heading and FAQ share the page's centre line,
   while the questions themselves stay left-aligned for scanning */
.psec--center .psec__head {
  margin-inline: auto;
  text-align: center;
}

.psec--center .psec__head .eyebrow {
  justify-content: center;
}

.psec--center .pfaq {
  margin-inline: auto;
}

.psec__head--wide {
  max-width: none;
}

.psec__title {
  margin-top: 18px;
  font-size: clamp(1.6rem, 3.2vw, 2.5rem);
  letter-spacing: -.04em;
}

.psec__lede {
  margin-top: 18px;
  color: var(--text-mid);
  max-width: 60ch;
  line-height: 1.7;
}

.psec__body {
  max-width: 68ch;
  margin-top: clamp(26px, 4vh, 40px);
  color: var(--text-mid);
  line-height: 1.78;
}

.psec__body p+p {
  margin-top: 1.3em;
}

.psec__body strong {
  color: var(--text);
  font-weight: 600;
}

/* ---------- 3. Scope / feature grids ---------- */
.pgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  margin-top: clamp(34px, 5vh, 60px);
  background: var(--line-soft);
  border-block: 1px solid var(--line-soft);
}

.pgrid--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* a grid whose cell count does not fill its last row: the leftover cells
   stretch across it, so the divider background never shows as an empty
   grey slot */
@media (min-width: 1081px) {
  .pgrid:not(.pgrid--2):not(.pgrid--3) > .pcell:last-child:nth-child(3n+1) {
    grid-column: 1 / -1;
  }

  .pgrid:not(.pgrid--2):not(.pgrid--3) > .pcell:last-child:nth-child(3n+2) {
    grid-column: span 2;
  }
}

@media (min-width: 721px) {
  .pgrid--2 > .pcell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

/* plain bullet lists inside section prose */
.psec__body ul {
  display: grid;
  gap: 8px;
  margin: 1em 0 1.3em;
}

.psec__body ul li {
  position: relative;
  padding-left: 1.6rem;
}

.psec__body ul li::before {
  content: "";
  position: absolute;
  left: .2rem;
  top: .85em;
  width: 11px;
  height: 1px;
  background: var(--line-strong);
}

.pcell {
  background: var(--bg);
  padding: clamp(22px, 2.6vw, 34px) clamp(18px, 2.2vw, 30px) clamp(28px, 3.4vw, 42px);
  transition: background-color .5s var(--ease);
}

.psec--quiet .pcell {
  background: var(--bg-elev);
}

.pcell:hover {
  background: var(--surface);
}

.pcell__key {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.pcell__title {
  margin: 16px 0 12px;
  font-size: clamp(1.05rem, 1.4vw, 1.28rem);
  letter-spacing: -.028em;
}

.pcell__body {
  font-size: .9rem;
  line-height: 1.68;
  color: var(--text-mid);
}

/* a plain ruled list, for scope items */
.plist {
  display: grid;
  gap: 0;
  margin-top: clamp(26px, 4vh, 42px);
  max-width: 70ch;
}

/* the number hangs in the left gutter; everything else in the item,
   however many inline pieces it has, flows as one block beside it */
.plist li {
  position: relative;
  padding: 16px 0 16px calc(2.6rem + 12px);
  border-top: 1px solid var(--line-soft);
  color: var(--text-mid);
  line-height: 1.6;
}

.plist li strong {
  color: var(--text);
}

.plist li:last-child {
  border-bottom: 1px solid var(--line-soft);
}

.plist li::before {
  content: counter(pitem, decimal-leading-zero);
  counter-increment: pitem;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .1em;
  color: var(--text-faint);
  position: absolute;
  left: 0;
  top: calc(16px + .3em);
}

.plist {
  counter-reset: pitem;
}

/* Scope section, wide screens: the covered-areas list sits beside the
   intro prose instead of under it. The heading spans the top, then the
   body and the list share a row. Targeted by the section's own label so
   no other .psec is touched; it collapses back to one stacked column
   below the breakpoint, and both edges keep the page's symmetric content
   margin (the grid lives inside the section's padding). */
@media (min-width: 860px) {
  .psec[aria-labelledby="scope-title"] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "head list"
      "body list";
    column-gap: clamp(32px, 5vw, 96px);
    align-items: start;
  }

  .psec[aria-labelledby="scope-title"] > .psec__head { grid-area: head; }
  .psec[aria-labelledby="scope-title"] > .psec__body { grid-area: body; }

  /* the list rides up beside the heading, sized to its content and held
     over to the right, inset a little from the margin so it does not sit
     hard against the edge */
  .psec[aria-labelledby="scope-title"] > .plist {
    grid-area: list;
    margin-top: 0;
    max-width: none;
    width: max-content;
    justify-self: end;
    margin-right: clamp(36px, 5vw, 108px);
  }
}

.pfaq {
  margin-top: clamp(34px, 5vh, 56px);
  max-width: 74ch;
  margin-inline: auto;
  text-align: left;
}

.pfaq details {
  border-top: 1px solid var(--line-soft);
}

.pfaq details:last-of-type {
  border-bottom: 1px solid var(--line-soft);
}

.pfaq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 20px 0;
  cursor: pointer;
  list-style: none;
  font-size: 1rem;
  color: var(--text);
}

.pfaq summary::-webkit-details-marker {
  display: none;
}

.pfaq summary::after {
  content: "+";
  font-family: var(--mono);
  color: var(--text-mid);
  flex: none;
}

.pfaq details[open] summary::after {
  content: "–";
}

.pfaq p {
  padding: 0 0 22px;
  max-width: 64ch;
  color: var(--text-mid);
  line-height: 1.72;
}

/* ---------- 6. Auth ---------- */
.pauth {
  position: relative;
  min-height: calc(100vh - var(--nav-h));
  display: grid;
  place-items: center;
  padding: calc(var(--nav-h) + clamp(40px, 7vh, 80px)) var(--pad) clamp(50px, 8vh, 90px);
}

.pauth__card {
  position: relative;
  z-index: 1;
  width: min(27rem, 100%);
  padding: clamp(26px, 3.4vw, 40px);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  background: linear-gradient(170deg, var(--surface), var(--bg-elev) 92%);
}

.pauth__title {
  margin: 16px 0 10px;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  letter-spacing: -.038em;
}

.pauth__lede {
  color: var(--text-mid);
  font-size: .93rem;
  line-height: 1.6;
}

.pauth__form {
  display: grid;
  gap: clamp(14px, 2vh, 20px);
  margin-top: clamp(24px, 3.4vh, 32px);
}

.pauth__foot {
  margin-top: clamp(20px, 3vh, 26px);
  padding-top: clamp(18px, 2.6vh, 24px);
  border-top: 1px solid var(--line-soft);
  font-size: .88rem;
  color: var(--text-mid);
}

.pauth__foot a {
  color: var(--text);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: opacity .3s var(--ease);
}

.pauth__foot a:hover {
  opacity: .62;
}

.pauth__note {
  margin-top: 14px;
  font-size: .8rem;
  line-height: 1.6;
  color: var(--text-faint);
}

/* ---------- 7. Policy documents ---------- */
.pdoc {
  position: relative;
  padding: clamp(40px, 6vh, 76px) var(--pad) clamp(56px, 9vh, 104px);
}

.pdoc__wrap {
  position: relative;
  z-index: 1;
  max-width: 46rem;
  margin-inline: auto;
  color: var(--text-mid);
  line-height: 1.78;
}

.pdoc__wrap h2 {
  margin: clamp(40px, 6vh, 68px) 0 clamp(14px, 2vh, 20px);
  font-size: clamp(1.3rem, 2vw, 1.7rem);
  letter-spacing: -.032em;
  color: var(--text);
}

.pdoc__wrap h2:first-child {
  margin-top: 0;
}

.pdoc__wrap h3 {
  margin: clamp(26px, 3.6vh, 38px) 0 10px;
  font-size: 1.02rem;
  color: var(--text);
}

.pdoc__wrap p {
  margin-bottom: 1.25em;
  max-width: 68ch;
}

.pdoc__wrap ul {
  display: grid;
  gap: 9px;
  margin-bottom: 1.25em;
  max-width: 64ch;
}

.pdoc__wrap li {
  position: relative;
  padding-left: 1.6rem;
}

.pdoc__wrap li::before {
  content: "";
  position: absolute;
  left: .2rem;
  top: .78em;
  width: 11px;
  height: 1px;
  background: var(--line-strong);
}

.pdoc__wrap a {
  color: var(--text);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
}

.pdoc__wrap code {
  font-family: var(--mono);
  font-size: .88em;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  padding: .1em .38em;
}

/* an open question the page cannot answer on its own */
.pdoc__todo {
  margin: clamp(22px, 3vh, 32px) 0;
  padding: clamp(18px, 2.4vw, 26px);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.pdoc__todo p {
  margin: 0;
  font-size: .9rem;
  color: var(--text-mid);
}

.pdoc__todo strong {
  display: block;
  margin-bottom: 8px;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text);
  font-weight: 400;
}

/* ---- legal documents: summary, contents, placeholders, related ---- */
.pdoc__note {
  margin: 0 0 clamp(28px, 4vh, 40px);
  padding: clamp(18px, 2.4vw, 26px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.pdoc__note p {
  margin: 0;
  max-width: none;
  color: var(--text);
}

.pdoc__note p+p {
  margin-top: .8em;
}

.pdoc__toc {
  margin: 0 0 clamp(36px, 5vh, 56px);
  padding-bottom: clamp(24px, 3.4vh, 34px);
  border-bottom: 1px solid var(--line-soft);
}

.pdoc__toc::before {
  content: "Contents";
  display: block;
  margin-bottom: 14px;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.pdoc__wrap .pdoc__toc ul {
  gap: 6px;
  max-width: none;
  margin: 0;
}

.pdoc__wrap .pdoc__toc li {
  padding-left: 0;
}

.pdoc__wrap .pdoc__toc li::before {
  content: none;
}

.pdoc__wrap .pdoc__toc a {
  border-bottom: 0;
  color: var(--text-mid);
  transition: color .3s var(--ease);
}

.pdoc__wrap .pdoc__toc a:hover {
  color: var(--text);
}

.pdoc__wrap h2[id] {
  scroll-margin-top: calc(var(--nav-h, 72px) + 24px);
}

/* a fact the company still has to supply: easy to spot and to grep */
.pdoc__fill {
  padding: 0 .3em;
  border: 1px dashed var(--line-strong);
  border-radius: 4px;
  color: var(--text);
  font-family: var(--mono);
  font-size: .86em;
}

.pdoc__related {
  margin-top: clamp(48px, 7vh, 76px);
  padding-top: clamp(24px, 3.4vh, 34px);
  border-top: 1px solid var(--line-soft);
}

.pdoc__related>p {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.pdoc__wrap .pdoc__related ul {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 24px;
  max-width: none;
}

@media (max-width: 640px) {
  .pdoc__wrap .pdoc__related ul {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* legal centre: one row per document */
.pdoc__wrap .lhub {
  display: grid;
  gap: 0;
  max-width: none;
  margin: 0;
  border-top: 1px solid var(--line-soft);
}

.pdoc__wrap .lhub li {
  padding: 0;
  border-bottom: 1px solid var(--line-soft);
}

.pdoc__wrap .lhub li::before {
  content: none;
}

.pdoc__wrap .lhub a {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
  gap: 6px clamp(18px, 3vw, 36px);
  padding: clamp(18px, 2.6vh, 24px) 0;
  border-bottom: 0;
  transition: opacity .3s var(--ease);
}

.pdoc__wrap .lhub a:hover {
  opacity: .7;
}

.lhub__name {
  color: var(--text);
  font-weight: 500;
}

.lhub__desc {
  color: var(--text-mid);
  font-size: .94rem;
  line-height: 1.6;
}

@media (max-width: 640px) {
  .pdoc__wrap .lhub a {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- marketing pages: tables, code, logs, roles, stories ---- */
.ptable-wrap {
  margin-top: clamp(28px, 4vh, 44px);
  overflow-x: auto;
  border-block: 1px solid var(--line-soft);
}

.ptable {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  font-size: .92rem;
}

.ptable th,
.ptable td {
  padding: 14px 16px 14px 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
  line-height: 1.55;
}

.ptable tr:last-child td {
  border-bottom: 0;
}

.ptable th {
  font-family: var(--mono);
  font-size: .64rem;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.ptable td {
  color: var(--text-mid);
}

.ptable td:first-child {
  color: var(--text);
}

.pcode {
  margin-top: clamp(22px, 3vh, 32px);
  padding: clamp(16px, 2vw, 22px);
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--mono);
  font-size: .8rem;
  line-height: 1.7;
  color: var(--text);
  white-space: pre;
}

.pnote {
  margin-top: clamp(22px, 3vh, 32px);
  padding: clamp(16px, 2vw, 22px);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-mid);
  font-size: .9rem;
  line-height: 1.65;
  max-width: 74ch;
}

.pnote strong {
  color: var(--text);
}

.pfill {
  padding: 0 .3em;
  border: 1px dashed var(--line-strong);
  border-radius: 4px;
  color: var(--text);
  font-family: var(--mono);
  font-size: .86em;
}

/* a dated log: audits, tests, updates */
.plog {
  margin-top: clamp(28px, 4vh, 44px);
  border-top: 1px solid var(--line-soft);
}

.plog__item {
  display: grid;
  grid-template-columns: minmax(0, 10rem) minmax(0, 9rem) minmax(0, 1fr);
  gap: 8px clamp(18px, 3vw, 36px);
  padding: clamp(16px, 2.4vh, 22px) 0;
  border-bottom: 1px solid var(--line-soft);
}

.plog__date,
.plog__type {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding-top: .3em;
}

.plog__type {
  color: var(--text-mid);
}

.plog__title {
  color: var(--text);
  font-size: 1rem;
}

.plog__body {
  margin-top: 6px;
  color: var(--text-mid);
  font-size: .9rem;
  line-height: 1.65;
}

/* open roles */
.pjobs {
  margin-top: clamp(28px, 4vh, 44px);
  border-top: 1px solid var(--line-soft);
}

.pjob {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px 24px;
  align-items: start;
  padding: clamp(18px, 2.6vh, 26px) 0;
  border-bottom: 1px solid var(--line-soft);
}

.pjob__title {
  font-size: clamp(1.02rem, 1.3vw, 1.2rem);
  letter-spacing: -.02em;
  color: var(--text);
}

.pjob__title a {
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease);
}

.pjob__title a:hover {
  border-bottom-color: var(--line-strong);
}

.pjob__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.pjob__meta {
  margin-top: 6px;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.pjob__body {
  margin-top: 10px;
  max-width: 68ch;
  color: var(--text-mid);
  font-size: .92rem;
  line-height: 1.65;
}

/* customer stories */
.pstory {
  display: grid;
  gap: 14px;
  align-content: start;
}

.pstory__quote {
  padding-left: 16px;
  border-left: 1px solid var(--line-strong);
  color: var(--text);
  font-size: 1rem;
  line-height: 1.6;
}

.pstory__who {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.pstats {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 32px;
}

.pstat__num {
  display: block;
  font-size: 1.5rem;
  letter-spacing: -.03em;
  color: var(--text);
}

.pstat__label {
  font-size: .8rem;
  color: var(--text-dim);
}

@media (max-width: 720px) {
  .plog__item {
    grid-template-columns: minmax(0, 1fr);
  }

  .pjob {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- 404 ---- */
.p404 .phero__inner {
  min-height: calc(48vh - var(--nav-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.p404 .pcta__row.p404__row {
  justify-content: flex-start;
}

/* the whole card is the link */
a.p404__cell {
  display: block;
  color: inherit;
}

a.p404__cell .pcell__title::after {
  content: " →";
  color: var(--text-dim);
  transition: color .3s var(--ease);
}

a.p404__cell:hover .pcell__title::after,
a.p404__cell:focus-visible .pcell__title::after {
  color: var(--text);
}

/* ---------- 8. Closing call to action ---------- */
.pcta {
  position: relative;
  padding: clamp(56px, 9vh, 104px) var(--pad);
  border-top: 1px solid var(--line-soft);
}

.pcta__inner {
  position: relative;
  z-index: 1;
  max-width: 52ch;
  margin-inline: auto;
  text-align: center;
}

.pcta__title {
  margin-top: 18px;
  font-size: clamp(1.7rem, 3.4vw, 2.7rem);
  letter-spacing: -.042em;
}

.pcta__lede {
  margin-top: 18px;
  color: var(--text-mid);
}

.pcta__row {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: clamp(26px, 4vh, 40px);
}

/* ---------- 9. Responsive ---------- */
@media (max-width: 1080px) {

  .pgrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* a three-cell grid wraps to 2+1; let the lone last cell span the row
     so the empty slot does not show the grid's divider background */
  .pgrid > .pcell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

@media (max-width: 720px) {

  .pgrid,
  .pgrid--2 {
    grid-template-columns: minmax(0, 1fr);
    background: transparent;
    gap: 0;
    border-block: 0;
  }

  .pcell {
    padding-inline: 0;
    border-top: 1px solid var(--line-soft);
  }

  .phero__meta {
    gap: 18px 28px;
  }

  .pcta__row .btn {
    width: 100%;
    justify-content: center;
  }
}

/* The preview pages carry no plate at all. They are internal, and the
   artwork was making them read as finished pages when they are not.
   The stamp stays on <body> as "none" rather than being removed, so a
   page goes back to its plate by changing one word. The dot field is
   untouched -- that is the site's ground, not a page's image. */
[data-pbg="none"] {
  --pbg-image: none;
}

/* Sections marked --plate go transparent so the artwork behind them can
   show. With no artwork there is nothing to show, so they take their
   shade back and the alternating rhythm survives. */
[data-pbg="none"] .psec--plate,
[data-pbg="none"] .psec--plate .pcell {
  background: var(--bg-elev);
}

/* Stacked, the copy runs the full width and there is no clean side left
   to fade towards, so the wash covers the page instead. */

/* The alternating section shade is an opaque panel, so a section that
   should show the artwork behind it has to say so. Only sections whose
   content stays inside the scrimmed left get this: a full-width rail
   would land on the subject. */
.psec--plate {
  background: transparent;
}

.psec--plate .pcell {
  background: color-mix(in srgb, var(--bg) 72%, transparent);
}

/* Below the stage breakpoint the copy stops sitting in two columns and
   runs the width of the section, so there is no empty left half left for
   it to sit in. The plate steps back rather than the text getting
   narrower. This also replaces the phone rule in styles.css, which the
   page-scoped values above would otherwise outrank. */

/* ---------- Pricing ----------
   One section: the heading, a chooser, the chosen plan's detail, and
   the small print. The comparison happens by switching between plans
   rather than by reading a table of every plan at once. */
.pplan {
  padding-top: calc(var(--nav-h) + clamp(44px, 8vh, 96px));
  /* The section holds its content inside a measure of its own rather
     than running to the page gutter. Written as a padding rather than a
     max-width so every child -- the chooser, the rules, the leader --
     is bounded by the same edges without an extra wrapper, and so the
     page gutter still wins on any screen narrower than the measure.
     This is on the section alone: the navbar is not touched. */
}

/* The heading and the terms share the top of the page rather than
   stacking. The terms are reference rather than argument, so they sit
   to one side of it and settle level with the foot of the lede. */
.pplan__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
  gap: clamp(30px, 5vw, 80px);
  align-items: end;
}

.pplan__intro {
  max-width: 62ch;
}

/* ---- the terms, as an annotation ----
   The artwork on the other pages labels what it shows with an open
   square, a leader line and a word set in mono. The terms of an
   engagement are labels on the same kind of drawing, so they are set
   the same way: a spine, a node on it for each term, a leader out to
   the text. It draws itself once, in order, and stops. */
.pann {
  position: relative;
  display: grid;
  align-content: end;
  padding: 2px 0 6px 30px;
}

.pann__spine {
  position: absolute;
  left: 4px;
  top: 22px;
  bottom: 22px;
  width: 1px;
  background: var(--line);
  transform: scaleY(0);
  transform-origin: 50% 0;
}

.pann__row {
  position: relative;
  display: grid;
  gap: 6px;
  padding: clamp(11px, 1.6vh, 15px) 0;
}

/* the open square is the plates' own marker, at the scale they use it */
.pann__node {
  position: absolute;
  left: -30px;
  top: calc(clamp(11px, 1.6vh, 15px) + 4px);
  width: 9px;
  height: 9px;
  border: 1px solid var(--text-mid);
  background: var(--bg);
  opacity: 0;
}

.pann__lead {
  position: absolute;
  left: -20px;
  top: calc(clamp(11px, 1.6vh, 15px) + 8px);
  width: 16px;
  height: 1px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.pann__k {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.pann__v {
  font-size: .98rem;
  line-height: 1.5;
  color: var(--text);
}

/* --- the order it assembles in: spine, then node, leader, text --- */
[data-seq].is-armed .pann.is-in .pann__spine {
  transform: scaleY(1);
  transition: transform .95s var(--ease-out);
}

[data-seq].is-armed .pann__row.is-in .pann__node {
  opacity: 1;
  transition: opacity .45s var(--ease-out);
  transition-delay: calc(var(--i) * 150ms + 340ms);
}

[data-seq].is-armed .pann__row.is-in .pann__lead {
  transform: scaleX(1);
  transition: transform .5s var(--ease-out);
  transition-delay: calc(var(--i) * 150ms + 480ms);
}

/* the text waits for its leader to arrive, which is what makes the
   figure read as annotation rather than as a list fading in */
[data-seq].is-armed .pann__row.is-in {
  transition-delay: calc(var(--i) * 150ms + 600ms);
}

/* Nothing draws for a reader who asked for less: the figure is simply
   already assembled. */
@media (prefers-reduced-motion: reduce) {

  .pann__spine {
    transform: scaleY(1);
  }

  .pann__node {
    opacity: 1;
  }

  .pann__lead {
    transform: scaleX(1);
  }
}

.pplan__title {
  margin: 20px 0 clamp(20px, 2.8vh, 32px);
  font-size: clamp(2.1rem, 5vw, 3.7rem);
  line-height: 1.02;
  letter-spacing: -.046em;
}

/* ---- the chooser ---- */
.pplan__pick {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  margin-top: clamp(40px, 6vh, 70px);
  background: var(--line-soft);
  border-block: 1px solid var(--line-soft);
}

.pplan__opt {
  position: relative;
  display: grid;
  align-content: start;
  gap: 10px;
  padding: clamp(22px, 2.6vw, 32px) clamp(18px, 2.2vw, 28px) clamp(26px, 3vw, 36px);
  text-align: left;
  background: var(--bg);
  color: var(--text-mid);
  cursor: pointer;
  transition: background-color .45s var(--ease), color .45s var(--ease);
}

.pplan__opt:hover,
.pplan__opt:focus-visible {
  color: var(--text);
  background: color-mix(in srgb, var(--text) 3%, var(--bg));
}

/* the arrow is the affordance: it arrives on approach and stays on the
   plan that has been chosen */
.pplan__go {
  position: absolute;
  right: clamp(18px, 2.2vw, 28px);
  bottom: clamp(20px, 2.4vw, 28px);
  line-height: 0;
  color: var(--text-mid);
  opacity: 0;
  transform: translateX(-5px);
  transition: opacity .35s var(--ease), transform .35s var(--ease-out),
    color .35s var(--ease);
}

.pplan__opt:hover .pplan__go,
.pplan__opt:focus-visible .pplan__go {
  opacity: 1;
  transform: none;
}

.pplan__opt[aria-checked="true"] .pplan__go {
  opacity: 1;
  transform: none;
  color: var(--text);
}

/* a few pixels, which is all the movement a card this size can take */
.pplan__note {
  transition: transform .35s var(--ease-out);
}

.pplan__opt:hover .pplan__note,
.pplan__opt:focus-visible .pplan__note {
  transform: translateY(-3px);
}

.pplan__name {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.pplan__price {
  font-size: clamp(1.5rem, 2.8vw, 2.1rem);
  letter-spacing: -.04em;
  color: var(--text);
  line-height: 1;
}

.pplan__unit {
  font-size: .5em;
  letter-spacing: -.01em;
  color: var(--text-mid);
  margin-left: 2px;
}

.pplan__note {
  font-size: .88rem;
  line-height: 1.55;
  max-width: 28ch;
}

.pplan__opt[aria-checked="true"] {
  color: var(--text);
  background: var(--bg-elev);
}

/* ---- the instruction ----
   Set behind a short rule in mono, the way the rest of the page labels
   things, so it reads as part of the drawing and not as a tooltip. */
.pplan__hint {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: clamp(38px, 5.5vh, 64px);
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .04em;
  color: var(--text-mid);
}

.pplan__hint__rule {
  display: block;
  width: 26px;
  height: 1px;
  flex: none;
  background: var(--line-strong);
}

/* the chooser follows the instruction, so it no longer opens the gap */
.pplan__hint+.pplan__pick {
  margin-top: clamp(14px, 2vh, 20px);
}

/* the marker slides between the three rather than appearing under each:
   the movement is what says these are alternatives to one another */
.pplan__marker {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 0;
  height: 2px;
  background: var(--text);
  transform: translateX(0);
  z-index: 2;
  pointer-events: none;
  opacity: 0;
}

.pplan.is-live .pplan__marker {
  opacity: 1;
  /* it fades in where it belongs; it slides only once a plan is picked */
  transition: opacity .5s var(--ease) .35s;
}

.pplan.is-live.is-ready .pplan__marker {
  transition: transform .55s var(--ease-out), width .55s var(--ease-out), opacity .3s var(--ease);
}

/* ---- the leader from the chosen plan ----
   The line belongs to the card above it, so it is positioned from the
   chooser's own coordinates and moves with the choice. Its node is the
   same open square the terms above use. */
.pplan__flow {
  position: relative;
  height: 0;
  overflow: visible;
}

.pplan.is-live .pplan__flow {
  height: clamp(26px, 3.4vh, 40px);
}

.pplan__drop {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  opacity: 0;
}

.pplan.is-live .pplan__drop {
  opacity: 1;
  transition: opacity .4s var(--ease) .5s;
}

.pplan.is-live.is-ready .pplan__drop {
  transition: transform .55s var(--ease-out), opacity .3s var(--ease);
}

.pplan__drop i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--line-strong);
  transform-origin: 50% 0;
}

/* the square lands at the foot of the line, where the detail begins */
.pplan__drop i::after {
  content: "";
  position: absolute;
  left: -4px;
  bottom: -4px;
  width: 7px;
  height: 7px;
  border: 1px solid var(--text-mid);
  background: var(--bg);
}

.pplan.is-live .pplan__flow.is-drawn .pplan__drop i {
  animation: pplan-drop .5s var(--ease-out) both;
}

@keyframes pplan-drop {
  from {
    transform: scaleY(0);
    opacity: 0;
  }

  to {
    transform: scaleY(1);
    opacity: 1;
  }
}

/* ---- the detail ---- */
.pplan__detail {
  position: relative;
  margin-top: clamp(30px, 4.5vh, 50px);
}

/* the leader strip already provides the gap, so the panel stops adding
   a second one and the node lands on the first row */
.pplan.is-live .pplan__detail {
  margin-top: 0;
}

.pplan__rows {
  display: grid;
  max-width: 54rem;
}

/* Shift the entire detail panel under the chosen plan column.
   The panel becomes a flex column so align-items governs where
   both .pplan__rows and .pplan__act sit horizontally. Each child
   keeps width:100% + max-width so it never overflows. */
.pplan__detail.is-pos-left .pplan__panel.is-on {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.pplan__detail.is-pos-center .pplan__panel.is-on {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.pplan__detail.is-pos-right .pplan__panel.is-on {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

/* the rows span exactly the chosen column, matching the marker line
   above it, for all three plans */
.pplan__detail.is-pos-left .pplan__rows,
.pplan__detail.is-pos-left .pplan__act,
.pplan__detail.is-pos-center .pplan__rows,
.pplan__detail.is-pos-center .pplan__act,
.pplan__detail.is-pos-right .pplan__rows,
.pplan__detail.is-pos-right .pplan__act {
  width: 100%;
  max-width: var(--col-w, 33.33%);
}

/* key and value share a line, split at the column's midpoint so the
   value starts under the leader; inset to match the card's padding */
.pplan__detail.is-pos-left .pplan__row,
.pplan__detail.is-pos-center .pplan__row,
.pplan__detail.is-pos-right .pplan__row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  padding: clamp(13px, 1.8vh, 18px) clamp(18px, 2.2vw, 28px);
}

.pplan__detail.is-pos-left .pplan__act,
.pplan__detail.is-pos-center .pplan__act,
.pplan__detail.is-pos-right .pplan__act {
  padding-inline: clamp(18px, 2.2vw, 28px);
  justify-content: center;
}

.pplan__row {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
  gap: 10px clamp(18px, 3vw, 40px);
  padding: clamp(13px, 1.8vh, 18px) 0;
  border-top: 1px solid var(--line-soft);
}

.pplan__row:last-child {
  border-bottom: 1px solid var(--line-soft);
}

.pplan__k {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-mid);
  padding-top: .3em;
}

.pplan__v {
  color: var(--text);
  line-height: 1.55;
}

.pplan__act {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: clamp(28px, 4vh, 44px);
}

.pplan__foot {
  margin-top: clamp(46px, 7vh, 84px);
  padding-top: clamp(30px, 4.5vh, 48px);
  border-top: 1px solid var(--line-soft);
}

/* the note and the FAQs sit centred on the page */
.pplan__foot>p {
  max-width: 64ch;
  margin-inline: auto;
  text-align: center;
  color: var(--text-mid);
  line-height: 1.75;
}

.pplan__foot .pfaq {
  margin-top: clamp(26px, 4vh, 40px);
  margin-inline: auto;
}

/* =========================================================
   Live. Until the script runs, every panel is in flow and
   readable; after it, one shows at a time and its rows
   restage on each change.
   ========================================================= */
.pplan.is-live .pplan__detail {
  display: grid;
  grid-template-areas: "panel";
}

.pplan.is-live .pplan__panel {
  grid-area: panel;
}

/* Both panels stay in the cell and cross over on opacity. The hidden
   attribute cut instead of faded, and because it took the panel out of
   flow the block changed height whenever a plan whose values wrap was
   chosen -- so the page jumped on every switch. visibility keeps it out
   of the accessibility tree without either problem. */
.pplan.is-live .pplan__panel {
  opacity: 0;
  visibility: hidden;
}

.pplan.is-live .pplan__panel.is-on {
  opacity: 1;
  visibility: visible;
}

/* The crossfade is held back until the first state has been set. Taking
   the page live is itself a change of state, so without this the two
   plans that were not chosen faded out on load instead of never having
   been there. Same for the marker and the leader: they take their
   positions first and animate only from the reader's own choices. */
.pplan.is-live.is-ready .pplan__panel {
  transition: opacity .3s var(--ease), visibility 0s linear .3s;
}

.pplan.is-live.is-ready .pplan__panel.is-on {
  transition: opacity .3s var(--ease), visibility 0s;
}

/* Each row arrives a beat after the one above. This is an animation
   rather than a transition because it has to replay on every switch,
   and a transition between two identical states does nothing. */
.pplan.is-live .pplan__panel.is-on .pplan__row {
  animation: pplan-row .5s var(--ease-out) both;
  /* offset by the leader's own draw, so the rows follow it down */
  animation-delay: calc(340ms + var(--n) * 55ms);
}

.pplan.is-live .pplan__panel.is-on .pplan__act {
  animation: pplan-row .5s var(--ease-out) both;
  animation-delay: 720ms;
}

@keyframes pplan-row {
  from {
    opacity: 0;
    transform: translateY(9px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* A choice should land, not drift: the marker and the rows settle
   instantly for a reader who asked for less movement. */
@media (prefers-reduced-motion: reduce) {

  .pplan.is-live .pplan__marker {
    transition: none;
  }

  .pplan.is-live .pplan__panel.is-on .pplan__row,
  .pplan.is-live .pplan__panel.is-on .pplan__act {
    animation: none;
  }

  .pplan.is-live .pplan__panel,
  .pplan.is-live .pplan__panel.is-on,
  .pplan__go,
  .pplan__note {
    transition: none;
  }

  /* the leader marks the chosen plan without travelling or drawing */
  .pplan.is-live .pplan__drop {
    transition: none;
  }

  .pplan.is-live .pplan__flow.is-drawn .pplan__drop i {
    animation: none;
  }
}

@media (max-width: 860px) {

  .pplan__head {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(24px, 4vh, 38px);
  }

  .pplan__pick {
    grid-template-columns: minmax(0, 1fr);
  }

  /* stacked, a sliding marker and a leader from one column of three
     have nowhere sensible to go */
  .pplan__marker,
  .pplan__flow {
    display: none;
  }

  .pplan__opt[aria-checked="true"] {
    box-shadow: inset 2px 0 0 var(--text);
  }

  .pplan__row {
    grid-template-columns: minmax(0, 1fr);
  }

  /* reset column alignment on mobile – everything stacks left */
  .pplan__detail.is-pos-center .pplan__panel.is-on,
  .pplan__detail.is-pos-right .pplan__panel.is-on {
    align-items: flex-start;
  }

  .pplan__detail.is-pos-left .pplan__row,
  .pplan__detail.is-pos-center .pplan__row,
  .pplan__detail.is-pos-right .pplan__row {
    grid-template-columns: minmax(0, 1fr);
    padding-inline: 0;
  }

  .pplan__detail.is-pos-left .pplan__act,
  .pplan__detail.is-pos-center .pplan__act,
  .pplan__detail.is-pos-right .pplan__act {
    padding-inline: 0;
    justify-content: flex-start;
  }
}

/* ---------- Sequenced sections ----------
   One vocabulary for a page that reveals as it is read: the label, the
   heading a line at a time, paragraphs in order, items on a stagger, a
   figure that runs once, and a rule that draws itself.

   Nothing is hidden without .is-armed, and every transition belongs to
   the revealed state, so the hidden state applies instantly and the
   reveal is the only thing that animates. */
.sq-line {
  display: block;
  overflow: hidden;
}

.sq-line>span {
  display: block;
}

[data-seq].is-armed .sq-label,
[data-seq].is-armed .sq-line>span,
[data-seq].is-armed .sq-p,
[data-seq].is-armed .sq-item {
  opacity: 0;
}

[data-seq].is-armed .sq-label {
  transform: translateY(8px);
}

[data-seq].is-armed .sq-line>span {
  transform: translateY(100%);
}

[data-seq].is-armed .sq-p {
  transform: translateY(18px);
}

[data-seq].is-armed .sq-item {
  transform: translateY(20px);
}

[data-seq].is-armed .sq-fig {
  opacity: 0;
  transform: translateY(18px);
}

[data-seq].is-armed .sq-fig.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 1s var(--ease-out), transform 1s var(--ease-out);
}

[data-seq].is-armed .sq-rule {
  transform: scaleX(0);
}

/* --- revealed --- */
[data-seq].is-armed .sq-label.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}

[data-seq].is-armed .sq-line.is-in>span {
  opacity: 1;
  transform: none;
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
  transition-delay: calc(var(--i) * 120ms + 120ms);
}

[data-seq].is-armed .sq-p.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 1s var(--ease-out), transform 1s var(--ease-out);
}

[data-seq].is-armed .sq-item.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
  transition-delay: calc(var(--i) * 110ms);
}

.sq-rule {
  display: block;
  height: 1px;
  margin: clamp(26px, 4vh, 40px) 0 0;
  background: var(--line);
  transform-origin: 0 50%;
}

[data-seq].is-armed .sq-rule.is-in {
  transform: scaleX(1);
  transition: transform 1.1s var(--ease-out);
}

/* ---- the hero line that resolves ----
   Three statements in one place. Only one is in flow; the others are
   stacked on it and faded, so the block never changes height. */
.sq-morph {
  position: relative;
  margin-top: 14px;
  min-height: 1.6em;
  font-size: clamp(1.02rem, .55vw + .92rem, 1.2rem);
  line-height: 1.6;
  color: var(--text);
  letter-spacing: -.022em;
}

.sq-morph__w {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}

.sq-morph__w.is-on {
  opacity: 1;
  transform: none;
}

/* the last one is the point being made, so it carries the weight */
.sq-morph__w:last-child.is-on {
  font-weight: 600;
}

/* ---------- Why Prava figures ----------
   Two pictures doing the arguing: scattered signals gathering to one
   point, and a noisy field resolving to a few connected ones. Both are
   plain positioned spans -- no canvas, no library -- and both run once
   and stop. */
/* The figures used to sit in a right-hand column. The artwork this page
   carries converges on that exact spot -- and makes the same argument --
   so the two were drawn on top of each other. The figure goes under the
   copy on the scrimmed left instead, where both can be read. */
.wsplit {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 25rem);
  gap: clamp(26px, 4vw, 64px);
  align-items: center;
  margin-top: clamp(26px, 4vh, 40px);
}

.wsplit .psec__body {
  margin-top: 0;
}

/* ---- the loop ----
   Detect, connect, prove, act -- and round again. The four statements
   beside it are the same four, so this carries no words the section
   does not already say, and is hidden from anything that reads. */
.wloop {
  width: 100%;
  max-width: 24rem;
  justify-self: center;
}

.wloop svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.wloop__ring {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.wloop__turn {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}

.wloop__halo {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1;
  opacity: 0;
}

.wloop__node {
  fill: var(--text);
  opacity: 0;
}

.wloop__label {
  fill: var(--text);
  font-size: 12px;
  letter-spacing: .01em;
  opacity: 0;
}

.wloop__name {
  fill: var(--text);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -.02em;
  opacity: 0;
}

.wloop__sub {
  fill: var(--text-faint);
  font-size: 8.5px;
  letter-spacing: .2em;
  text-transform: uppercase;
  opacity: 0;
}

/* the ring draws, the stations arrive in order, then the name */
.wloop.is-in .wloop__ring {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.4s var(--ease-out);
}

.wloop.is-in .wloop__halo,
.wloop.is-in .wloop__node,
.wloop.is-in .wloop__label {
  opacity: 1;
  transition: opacity .5s var(--ease);
  transition-delay: calc(.9s + var(--i) * .16s);
}

.wloop.is-in .wloop__halo {
  opacity: .55;
}

.wloop.is-in .wloop__turn {
  opacity: 1;
  transition: opacity .5s var(--ease) 1.5s;
}

.wloop.is-in .wloop__name,
.wloop.is-in .wloop__sub {
  opacity: 1;
  transition: opacity .6s var(--ease) 1.7s;
}

.wloop.is-in .wloop__sub {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .wloop__ring {
    stroke-dashoffset: 0;
  }

  .wloop__turn,
  .wloop__node,
  .wloop__label,
  .wloop__name,
  .wloop__sub {
    opacity: 1;
  }

  .wloop__halo {
    opacity: .55;
  }
}

/* ---- the four statements beside it ---- */
.wbeats {
  display: grid;
  align-content: center;
  gap: clamp(16px, 2.4vh, 24px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.wbeats li {
  font-size: clamp(1.15rem, 2vw, 1.55rem);
  letter-spacing: -.032em;
  color: var(--text);
}

/* ---------- The six differences ---------- */
.pgrid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.wcard {
  position: relative;
}

.wcard__num {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .18em;
  color: var(--text-faint);
  margin-bottom: 16px;
  transition: color .6s var(--ease);
}

.wcard.is-in .wcard__num {
  color: var(--text-mid);
  transition-delay: calc(var(--i) * 110ms + .5s);
}

/* a thin line extends from the number as the card settles */
.wcard__num::after {
  content: "";
  display: inline-block;
  vertical-align: middle;
  width: 0;
  height: 1px;
  margin-left: 10px;
  background: var(--line-strong);
  transition: width .7s var(--ease-out);
}

.wcard.is-in .wcard__num::after {
  width: 26px;
  transition-delay: calc(var(--i) * 110ms + .5s);
}

/* ---------- The marks ----------
   One per difference, each a small drawing of the thing the card says.
   All of them run once and hold -- except the loop, which is the one
   card whose point is that it does not stop. */
.wmark {
  position: relative;
  display: block;
  width: 88px;
  height: 26px;
  margin-bottom: 16px;
}

.wmark i {
  position: absolute;
  display: block;
  opacity: 0;
}

/* one surface: separate points resolve onto a single line */
.wmark--join i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-mid);
  top: 50%;
  margin-top: -2px;
}

.wmark--join i:nth-child(1) { left: 0; }
.wmark--join i:nth-child(2) { left: 20px; }
.wmark--join i:nth-child(3) { left: 40px; }
.wmark--join i:nth-child(4) { left: 60px; }
.wmark--join i:nth-child(5) { left: 80px; }

.wcard.is-in .wmark--join i {
  animation: wm-in .6s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .45s + var(--n, 0) * 90ms);
}

.wmark--join i:nth-child(2) { --n: 1; }
.wmark--join i:nth-child(3) { --n: 2; }
.wmark--join i:nth-child(4) { --n: 3; }
.wmark--join i:nth-child(5) { --n: 4; }

/* human-led: a machine point and a reviewed one */
.wmark--pair i:nth-child(1),
.wmark--pair i:nth-child(3) {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  top: 50%;
  margin-top: -3px;
}

.wmark--pair i:nth-child(1) {
  left: 0;
  background: var(--text-mid);
}

.wmark--pair i:nth-child(3) {
  left: 52px;
  border: 1px solid var(--text);
}

.wmark--pair i:nth-child(2) {
  left: 8px;
  width: 44px;
  height: 1px;
  top: 50%;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.wcard.is-in .wmark--pair i:nth-child(1) {
  animation: wm-in .5s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .45s);
}

.wcard.is-in .wmark--pair i:nth-child(2) {
  animation: wm-draw .6s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .65s);
}

.wcard.is-in .wmark--pair i:nth-child(3) {
  animation: wm-in .5s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + 1.1s);
}

/* attack paths: a route assembling, step by step */
.wmark--path i:nth-child(odd) {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-mid);
}

.wmark--path i:nth-child(1) { left: 0; top: 2px; }
.wmark--path i:nth-child(3) { left: 26px; top: 8px; }
.wmark--path i:nth-child(5) { left: 52px; top: 14px; }
.wmark--path i:nth-child(7) { left: 78px; top: 20px; }

.wmark--path i:nth-child(even) {
  height: 1px;
  width: 26px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
  rotate: 13deg;
}

.wmark--path i:nth-child(2) { left: 3px; top: 5px; }
.wmark--path i:nth-child(4) { left: 29px; top: 11px; }
.wmark--path i:nth-child(6) { left: 55px; top: 17px; }

.wcard.is-in .wmark--path i {
  animation-duration: .5s;
  animation-timing-function: var(--ease-out);
  animation-fill-mode: forwards;
  animation-delay: calc(var(--i) * 110ms + .45s + var(--n, 0) * 190ms);
}

.wcard.is-in .wmark--path i:nth-child(odd) { animation-name: wm-in; }
.wcard.is-in .wmark--path i:nth-child(even) { animation-name: wm-draw; }

.wmark--path i:nth-child(2) { --n: 1; }
.wmark--path i:nth-child(3) { --n: 2; }
.wmark--path i:nth-child(4) { --n: 3; }
.wmark--path i:nth-child(5) { --n: 4; }
.wmark--path i:nth-child(6) { --n: 5; }
.wmark--path i:nth-child(7) { --n: 6; }

/* evidence: the parts of a finding filling in */
.wmark--rows i {
  left: 0;
  height: 1px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.wmark--rows i:nth-child(1) { top: 2px; width: 78px; }
.wmark--rows i:nth-child(2) { top: 8px; width: 58px; }
.wmark--rows i:nth-child(3) { top: 14px; width: 70px; }
.wmark--rows i:nth-child(4) { top: 20px; width: 46px; }
.wmark--rows i:nth-child(5) { top: 26px; width: 64px; }

.wcard.is-in .wmark--rows i {
  animation: wm-draw .55s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .45s + var(--n, 0) * 130ms);
}

.wmark--rows i:nth-child(2) { --n: 1; }
.wmark--rows i:nth-child(3) { --n: 2; }
.wmark--rows i:nth-child(4) { --n: 3; }
.wmark--rows i:nth-child(5) { --n: 4; }

/* continuous: the one mark that keeps going, because that is its point */
.wmark--loop i:nth-child(1) {
  left: 0;
  top: 3px;
  width: 56px;
  height: 20px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  opacity: 0;
}

.wmark--loop i:nth-child(2) {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text);
  left: 0;
  top: 3px;
  margin: -2px 0 0 -2px;
  offset-path: path("M 2 13 A 26 10 0 1 1 54 13 A 26 10 0 1 1 2 13");
  offset-distance: 0%;
  offset-rotate: 0deg;
}

.wcard.is-in .wmark--loop i:nth-child(1) {
  animation: wm-in .6s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .45s);
}

.wcard.is-in .wmark--loop i:nth-child(2) {
  animation: wm-in .6s var(--ease-out) forwards,
    wm-orbit 11s linear infinite;
  animation-delay: calc(var(--i) * 110ms + .6s), calc(var(--i) * 110ms + .6s);
}

/* clear scope: a span with defined ends */
.wmark--span i:nth-child(1),
.wmark--span i:nth-child(3) {
  width: 1px;
  height: 12px;
  top: 50%;
  margin-top: -6px;
  background: var(--text-mid);
}

.wmark--span i:nth-child(1) { left: 0; }
.wmark--span i:nth-child(3) { left: 72px; }

.wmark--span i:nth-child(2) {
  left: 0;
  top: 50%;
  width: 72px;
  height: 1px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.wmark--span i:nth-child(4) {
  display: none;
}

.wcard.is-in .wmark--span i:nth-child(1),
.wcard.is-in .wmark--span i:nth-child(3) {
  animation: wm-in .5s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .45s);
}

.wcard.is-in .wmark--span i:nth-child(3) {
  animation-delay: calc(var(--i) * 110ms + .95s);
}

.wcard.is-in .wmark--span i:nth-child(2) {
  animation: wm-draw .6s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 110ms + .6s);
}

@keyframes wm-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes wm-draw {
  from {
    opacity: 1;
    transform: scaleX(0);
  }

  to {
    opacity: 1;
    transform: scaleX(1);
  }
}

@keyframes wm-orbit {
  from {
    offset-distance: 0%;
  }

  to {
    offset-distance: 100%;
  }
}

/* Nothing moves for a reader who asked for less: the marks are hidden
   rather than frozen part-drawn, and the figures show their finished
   state. */
@media (prefers-reduced-motion: reduce) {
  .wmark {
    display: none;
  }

}

@media (max-width: 1080px) {
  .pgrid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .wsplit {
    grid-template-columns: minmax(0, 1fr);
  }

  .wloop {
    max-width: 20rem;
    justify-self: start;
  }
}

@media (max-width: 720px) {
  .pgrid--3 {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Pressure mesh ----------
   A 2x2 block whose dividers are real elements rather than gaps, so
   they can be drawn. On top of them sits a ring joining the four cell
   centres, which is the section's actual argument: identity, exposure,
   trust and speed are one surface, not four problems.

   Nothing here repeats. Every animation is a single pass that holds
   its final state. */
.pmesh {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(34px, 5vh, 60px);
}

/* The section itself is transparent so the artwork behind the page
   shows through it. The shade it used to carry sits on the cards
   instead, which is the one place copy has to stay readable. */
.pmesh__cell {
  position: relative;
  z-index: 1;
  padding: clamp(22px, 2.6vw, 34px) clamp(18px, 2.2vw, 30px) clamp(28px, 3.4vw, 42px);
  background: var(--bg-elev);
  transition: background-color .45s var(--ease);
}

/* ---- the dividers ---- */
.pmesh__rules {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.pmesh__r {
  position: absolute;
  background: var(--line-soft);
  transform-origin: 0 50%;
}

.pmesh__r--top,
.pmesh__r--mid,
.pmesh__r--bot {
  left: 0;
  right: 0;
  height: 1px;
}

.pmesh__r--top {
  top: 0;
}

.pmesh__r--mid {
  top: 50%;
}

.pmesh__r--bot {
  bottom: 0;
}

.pmesh__r--v {
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  transform-origin: 50% 0;
}

/* ---- the ring, and the one point that travels it ---- */
.pmesh__net {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  z-index: 2;
}

.pmesh__leg {
  position: absolute;
  background: var(--line-strong);
  opacity: .55;
}

/* the four sides of one square, drawn in order round the block */
.pmesh__leg--t,
.pmesh__leg--b {
  left: 0;
  right: 0;
  height: 1px;
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.pmesh__leg--r,
.pmesh__leg--l {
  top: 0;
  bottom: 0;
  width: 1px;
  transform: scaleY(0);
  transform-origin: 50% 0;
}

.pmesh__leg--t {
  top: 0;
}

.pmesh__leg--b {
  bottom: 0;
}

.pmesh__leg--r {
  right: 0;
}

.pmesh__leg--l {
  left: 0;
}

.pmesh__spark {
  position: absolute;
  left: 0;
  top: 0;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 50%;
  background: var(--text);
  opacity: 0;
}

/* ---- the signal marks ---- */
[data-mesh]:not(.is-armed) .psig {
  display: none;
}

.psig {
  position: relative;
  display: inline-block;
  width: 34px;
  height: .6em;
  margin-left: 10px;
  vertical-align: baseline;
}

.psig i {
  position: absolute;
  display: block;
  background: var(--text-mid);
  opacity: 0;
}

/* identity: one node, then a second finds it */
.psig--identity i:nth-child(1),
.psig--identity i:nth-child(3) {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  top: 50%;
  margin-top: -2px;
}

.psig--identity i:nth-child(1) {
  left: 0;
}

.psig--identity i:nth-child(3) {
  left: 30px;
}

.psig--identity i:nth-child(2) {
  left: 4px;
  width: 26px;
  height: 1px;
  top: 50%;
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* exposure: a perimeter opening outward */
.psig--exposure i {
  left: 15px;
  top: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: none;
  border: 1px solid var(--text-mid);
}

/* trust: two points joined */
.psig--trust i:nth-child(1),
.psig--trust i:nth-child(3) {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  top: 50%;
  margin-top: -2px;
}

.psig--trust i:nth-child(1) {
  left: 0;
}

.psig--trust i:nth-child(3) {
  left: 30px;
}

.psig--trust i:nth-child(2) {
  left: 4px;
  width: 26px;
  height: 1px;
  top: 50%;
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* speed: a point crosses and is gone */
.psig--speed i:nth-child(1) {
  left: 0;
  right: 0;
  height: 1px;
  top: 50%;
  background: var(--line-strong);
}

.psig--speed i:nth-child(2) {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  top: 50%;
  margin-top: -2px;
  left: 0;
  background: var(--text);
}

.psig--speed i:nth-child(3) {
  display: none;
}

/* ---- hover ---- */
.pmesh__cell:hover {
  background: color-mix(in srgb, var(--text) 4%, var(--bg-elev));
}

.pmesh__cell .pcell__title {
  position: relative;
  display: inline-block;
}

.pmesh__cell .pcell__title::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 1px;
  background: var(--line-strong);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .5s var(--ease-out);
}

.pmesh__cell:hover .pcell__title::after {
  transform: scaleX(1);
}

.pmesh__cell .pcell__body {
  transition: transform .45s var(--ease-out);
}

.pmesh__cell:hover .pcell__body {
  transform: translateY(-3px);
}

/* =========================================================
   Armed states. Hidden instantly, revealed slowly: every
   transition and animation below belongs to the revealed
   state, never to the hidden one.
   ========================================================= */
.pmesh.is-armed .pmesh__r {
  transform: scaleX(0);
}

.pmesh.is-armed .pmesh__r--v {
  transform: scaleY(0);
}

[data-mesh].is-armed .mesh__label,
[data-mesh].is-armed .mesh__line>span,
[data-mesh].is-armed .pmesh__cell {
  opacity: 0;
}

[data-mesh].is-armed .mesh__label {
  transform: translateY(8px);
}

[data-mesh].is-armed .mesh__line>span {
  transform: translateY(100%);
}

[data-mesh].is-armed .pmesh__cell {
  transform: translateY(16px);
}

[data-mesh].is-armed .psig i {
  opacity: 0;
}

/* --- revealed --- */
[data-mesh].is-armed .mesh__label.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}

[data-mesh].is-armed .mesh__line.is-in>span {
  opacity: 1;
  transform: none;
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
  transition-delay: calc(var(--i) * 120ms + 120ms);
}

/* the rules draw outward from the section's left and top edges */
.pmesh.is-armed.is-drawn .pmesh__r {
  transform: scaleX(1);
  transition: transform .85s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 0ms);
}

.pmesh.is-armed.is-drawn .pmesh__r--top {
  transition-delay: 0ms;
}

.pmesh.is-armed.is-drawn .pmesh__r--mid {
  transition-delay: 140ms;
}

.pmesh.is-armed.is-drawn .pmesh__r--bot {
  transition-delay: 280ms;
}

.pmesh.is-armed.is-drawn .pmesh__r--v {
  transform: scaleY(1);
  transition: transform .85s var(--ease-out) 200ms;
}

/* the four points, in reading order, a beat apart */
[data-mesh].is-armed .pmesh__cell.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
  transition-delay: calc(var(--i) * 200ms);
}

/* each mark runs once when its own cell arrives, then holds */
[data-mesh].is-armed .pmesh__cell.is-in .psig i {
  animation-duration: .9s;
  animation-timing-function: var(--ease-out);
  animation-fill-mode: forwards;
  animation-delay: calc(var(--i) * 200ms + 420ms);
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--identity i:nth-child(1) {
  animation-name: psig-node;
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--identity i:nth-child(3) {
  animation-name: psig-node;
  animation-delay: calc(var(--i) * 200ms + 900ms);
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--identity i:nth-child(2) {
  animation-name: psig-join;
  animation-delay: calc(var(--i) * 200ms + 640ms);
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--exposure i {
  animation-name: psig-ring;
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--trust i:nth-child(1),
[data-mesh].is-armed .pmesh__cell.is-in .psig--trust i:nth-child(3) {
  animation-name: psig-node;
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--trust i:nth-child(2) {
  animation-name: psig-join;
  animation-delay: calc(var(--i) * 200ms + 700ms);
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--speed i:nth-child(1) {
  animation-name: psig-fade;
}

[data-mesh].is-armed .pmesh__cell.is-in .psig--speed i:nth-child(2) {
  animation-name: psig-cross;
  animation-duration: 1.4s;
}

@keyframes psig-node {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes psig-fade {
  from {
    opacity: 0;
  }

  to {
    opacity: .6;
  }
}

@keyframes psig-join {
  from {
    opacity: 1;
    transform: scaleX(0);
  }

  to {
    opacity: 1;
    transform: scaleX(1);
  }
}

@keyframes psig-ring {
  0% {
    opacity: 0;
    width: 2px;
    height: 2px;
    margin: -1px 0 0 -1px;
  }

  55% {
    opacity: 1;
  }

  100% {
    opacity: .45;
    width: 12px;
    height: 12px;
    margin: -6px 0 0 -6px;
  }
}

/* the point crosses once and is gone -- the window closing */
@keyframes psig-cross {
  0% {
    opacity: 0;
    left: 0;
  }

  12% {
    opacity: 1;
  }

  82% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    left: 32px;
  }
}

/* ---- the ring, once the four are in ---- */
.pmesh.is-armed.is-linked .pmesh__net {
  opacity: 1;
  transition: opacity .4s var(--ease);
}

.pmesh.is-armed.is-linked .pmesh__leg {
  animation: pmesh-leg .55s var(--ease-out) forwards;
}

.pmesh.is-armed.is-linked .pmesh__leg--t {
  animation-delay: 0ms;
}

.pmesh.is-armed.is-linked .pmesh__leg--r {
  animation-delay: 380ms;
}

.pmesh.is-armed.is-linked .pmesh__leg--b {
  animation-delay: 760ms;
  transform-origin: 100% 50%;
}

.pmesh.is-armed.is-linked .pmesh__leg--l {
  animation-delay: 1140ms;
  transform-origin: 50% 100%;
}

@keyframes pmesh-leg {
  to {
    transform: scale(1);
  }
}

/* one point round the ring, once, then gone */
.pmesh.is-armed.is-linked .pmesh__spark {
  animation: pmesh-spark 3.2s var(--ease) 1.5s forwards;
}

@keyframes pmesh-spark {
  0% {
    opacity: 0;
    left: 0;
    top: 0;
  }

  6% {
    opacity: .9;
  }

  25% {
    left: 100%;
    top: 0;
  }

  50% {
    left: 100%;
    top: 100%;
  }

  75% {
    left: 0;
    top: 100%;
  }

  94% {
    opacity: .9;
  }

  100% {
    opacity: 0;
    left: 0;
    top: 0;
  }
}

@media (max-width: 720px) {

  .pmesh {
    grid-template-columns: minmax(0, 1fr);
  }

  /* stacked there is no 2x2 to draw or to ring, and the middle rule
     has no middle to sit in: the stack rules itself instead */
  .pmesh__r--v,
  .pmesh__r--mid,
  .pmesh__net {
    display: none;
  }

  .pmesh__cell+.pmesh__cell {
    border-top: 1px solid var(--line-soft);
  }
}

/* ---------- Beats ----------
   Four short statements that carry the closing argument. Set larger
   than body copy and separated by hairlines, so they read as a count
   rather than as a list of features. */
.pbeats {
  display: grid;
  max-width: 46rem;
  margin-top: clamp(30px, 4.5vh, 48px);
}

.pbeats li {
  padding: clamp(14px, 2vh, 20px) 0;
  border-top: 1px solid var(--line-soft);
  font-size: clamp(1.05rem, 1.7vw, 1.32rem);
  letter-spacing: -.028em;
  color: var(--text);
}

.pbeats li:last-child {
  border-bottom: 1px solid var(--line-soft);
}

/* ---------- Premise ----------
   A quiet sequence: the label, then the heading a line at a time, then
   each paragraph as it arrives, then the question once its rule has
   drawn itself. Opacity and small vertical moves only -- no scaling, no
   glow, nothing that draws attention to the animation instead of the
   sentence. The distances are deliberately short; anything longer reads
   as a slide deck.

   The hidden states all sit behind .is-armed. The script adds it, and
   only when motion is welcome, so this cannot strand content. */
.prem__title {
  margin-top: 18px;
}

.prem__line {
  display: block;
  overflow: hidden;         /* the line rises out of its own box */
}

.prem__line>span {
  display: block;
}

.prem.is-armed .prem__label,
.prem.is-armed .prem__line>span,
.prem.is-armed .prem__p,
.prem.is-armed .prem__quote {
  opacity: 0;
  will-change: opacity, transform;
}

.prem.is-armed .prem__label {
  transform: translateY(8px);
}

.prem.is-armed .prem__line>span {
  transform: translateY(100%);
}

.prem.is-armed .prem__p {
  transform: translateY(18px);
}

/* The transitions live on the revealed state, never on the hidden one:
   a transition is read from the style being moved to, so this hides
   instantly and reveals slowly. Declared the other way round, arming
   animated the whole section out on load. */
.prem.is-armed .prem__label.is-in,
.prem.is-armed .prem__line.is-in>span,
.prem.is-armed .prem__p.is-in {
  opacity: 1;
  transform: none;
}

.prem.is-armed .prem__label.is-in {
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}

.prem.is-armed .prem__line.is-in>span {
  transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
  /* the stagger is the brief's 120ms, counted off the line index */
  transition-delay: calc(var(--i) * 120ms + 140ms);
}

.prem.is-armed .prem__p.is-in {
  transition: opacity 1.05s var(--ease-out), transform 1.05s var(--ease-out);
}

/* ---- the bold phrase ----
   A brightness that crosses the words left to right, rather than the
   whole phrase fading in together. Each word is dim until its turn.

   This began as a gradient clipped to the text, which is the tidier
   idea, but an inline box that wraps across two lines has no dependable
   positioning area for it: the dim end of the sweep rendered as dark as
   the text around it, so there was nothing left to reveal. */
.prem.is-armed .prem__bright .w {
  color: var(--text-mid);
}

.prem.is-armed .prem__bright.is-in .w {
  color: var(--text);
  transition: color .55s var(--ease-out);
  transition-delay: calc(var(--i) * 55ms);
}

/* ---- the question ----
   The rule draws itself down, and the words follow it. The border is a
   pseudo-element so its height can be animated independently of the
   text it marks. */
.prem__quote {
  position: relative;
  border-left: 0;
}

.prem__quote::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 100%;
  background: var(--line-strong);
  transform-origin: 50% 0;
}

.prem.is-armed .prem__quote::before {
  transform: scaleY(0);
}

.prem.is-armed .prem__quote {
  transform: translateY(10px);
}

.prem.is-armed .prem__quote.is-in {
  opacity: 1;
  transform: none;
  /* the words wait for the rule, then take their time */
  transition: opacity .85s var(--ease-out) .55s, transform .85s var(--ease-out) .55s;
}

.prem.is-armed .prem__quote.is-in::before {
  transform: scaleY(1);
  transition: transform .75s var(--ease-out);
}

/* the landing question sits centred under the diagram rather than on a
   left rail, so it reads as the point the whole convergence arrives at.
   Scoped to .prem so it beats the later .pquote border rule. */
.prem .prem__quote {
  margin-inline: auto;
  padding-left: 0;
  border-left: 0;
  max-width: 46rem;
  text-align: center;
}

.prem .prem__quote::before {
  display: none;
}

/* the premise now leads with one line, then shows the idea rather than
   spelling it out at length */
.prem__lede {
  max-width: 62ch;
  margin-top: clamp(22px, 3.5vh, 34px);
  color: var(--text-mid);
  font-size: clamp(1.02rem, 1.5vw, 1.2rem);
  line-height: 1.62;
  letter-spacing: -.01em;
}

/* ---------- Premise convergence ----------
   The argument the section makes, drawn: four separate signals on the
   left are pulled along converging lines into a single node -- one
   system -- on the right. The columns are fixed percentages and the
   wires are drawn in the same 0..100 space and stretched to fill, so a
   line always meets its node whatever the width. Below the breakpoint it
   folds to a stacked list with the resolved node beneath it. */
.pmerge {
  position: relative;
  display: grid;
  grid-template-columns: 30% 42% 28%;
  align-items: stretch;
  min-height: clamp(236px, 32vh, 330px);
  margin-top: clamp(30px, 5vh, 52px);
}

.pmerge__wire {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  z-index: 0;
}

.pmerge__wire line {
  stroke: var(--line-strong);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .55;
}

/* shiny dots that ride the lines and converge on the hub. Each dot
   travels in the same 0..100 space as the wires -- from its source at
   30%,y to the hub at 71%,50% -- so it rides its line exactly. All four
   share one timing, so a wave sets off together, reaches the hub and
   vanishes together; the rest of the cycle is empty, so the next wave
   starts only once the last has gone. */
.pmerge__flow {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.pmerge__dot {
  --x0: 30%;
  --x1: 71%;
  --y1: 50%;
  position: absolute;
  left: var(--x0);
  top: var(--y0);
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  color: var(--text);
  background: currentColor;
  box-shadow: 0 0 7px 1.5px currentColor;
  opacity: 0;
  animation: pmerge-flow 5.4s linear infinite;
}

@keyframes pmerge-flow {
  /* first half: travel source -> hub, then vanish on arrival */
  0% {
    left: var(--x0);
    top: var(--y0);
    opacity: 0;
  }

  6% {
    opacity: 1;
  }

  44% {
    opacity: 1;
  }

  50% {
    left: var(--x1);
    top: var(--y1);
    opacity: 0;
  }

  /* second half: gone, so there is a clear gap before the next wave */
  100% {
    left: var(--x1);
    top: var(--y1);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pmerge__flow {
    display: none;
  }
}

.pmerge__sources {
  grid-column: 1;
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-rows: repeat(4, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pmerge__src {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: clamp(10px, 1.2vw, 16px);
}

.pmerge__label {
  text-align: right;
  font-size: clamp(.86rem, 1.05vw, .98rem);
  letter-spacing: -.012em;
  color: var(--text-mid);
}

.pmerge__node {
  position: relative;
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--bg);
}

.pmerge__node::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--text-faint);
}

.pmerge__hub {
  grid-column: 3;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.4vw, 20px);
}

.pmerge__node--hub {
  width: 18px;
  height: 18px;
}

.pmerge__node--hub::after {
  inset: 4px;
  background: var(--text);
}

.pmerge__hubtitle {
  font-size: clamp(1.15rem, 1.9vw, 1.55rem);
  letter-spacing: -.03em;
  line-height: 1.08;
  color: var(--text);
  text-align: center;
}

.pmerge__hubnote {
  margin-top: .55em;
  margin-inline: auto;
  max-width: 30ch;
  font-size: clamp(.9rem, 1.05vw, 1rem);
  line-height: 1.5;
  color: var(--text-mid);
  text-align: center;
}

/* stacked: the signals as a short list, then the one system beneath */
@media (max-width: 820px) {
  .pmerge {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: clamp(22px, 5vw, 30px);
    min-height: 0;
  }

  .pmerge__wire,
  .pmerge__flow {
    display: none;
  }

  .pmerge__sources {
    display: flex;
    flex-direction: column;
    gap: clamp(12px, 3vw, 18px);
  }

  .pmerge__src {
    justify-content: flex-start;
    gap: clamp(12px, 3vw, 18px);
  }

  .pmerge__src .pmerge__node {
    order: -1;
  }

  .pmerge__label {
    text-align: left;
  }

  .pmerge__hub {
    padding-top: clamp(18px, 5vw, 26px);
    border-top: 1px solid var(--line);
  }
}

/* ---------- Review radial ----------
   One finding opening into five human questions. A central FINDINGS node
   with five thin rays that draw outward, one at a time, on entering the
   view -- a minimal, slightly irregular five-point radial. Text left,
   figure right; stacks on narrow screens. The SVG scales as one piece,
   so the labels keep their spacing at every size. */
.rev__viz {
  margin-top: clamp(32px, 5vh, 52px);
}

.revstar {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  color: var(--text);
  /* the soft bloom that lifts the figure off the flat background */
  --revglow: rgba(255, 255, 255, .26);
}

:root[data-theme="light"] .revstar {
  --revglow: rgba(0, 0, 0, .14);
}

/* atmosphere: a faint radial wash behind the node, so the composition
   sits in a little depth rather than on bare black */
.revstar__halo {
  opacity: 0;
  transition: opacity 1.3s var(--ease) .2s;
}

/* the central node: layered -- a faint outer ring, then the solid ring
   with a soft glow -- so it clearly leads without shouting */
.revstar__ring {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-opacity: .28;
  opacity: 0;
  transform: scale(.7);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity .7s var(--ease-out) .1s, transform .8s var(--ease-out) .1s;
}

.revstar__node {
  fill: var(--bg-elev);
  stroke: currentColor;
  stroke-width: 1.5;
  opacity: 0;
  transform: scale(.7);
  transform-box: fill-box;
  transform-origin: center;
  filter: drop-shadow(0 0 7px var(--revglow));
  transition: opacity .5s var(--ease-out), transform .55s var(--ease-out);
}

.revstar__nodelabel {
  fill: var(--text);
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: .22em;
  dominant-baseline: middle;
  opacity: 0;
  transition: opacity .5s var(--ease) .15s;
}

/* each ray draws from the node outward via a normalised dash (pathLength
   is 1, so one dash covers the whole line and the offset reveals it).
   The stroke is a gradient set inline, bright at the node and fading to
   the tip; the small bloom keeps it from looking like bare wire. */
.revstar__line {
  stroke-width: 1.2;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  filter: drop-shadow(0 0 3px var(--revglow));
  transition: stroke-dashoffset .66s var(--ease-out);
  transition-delay: calc(.4s + var(--seq) * .9s);
}

.revstar__end {
  fill: currentColor;
  opacity: 0;
  filter: drop-shadow(0 0 4px var(--revglow));
  transition: opacity .4s var(--ease);
  transition-delay: calc(.4s + var(--seq) * .9s + .5s);
}

/* a faint ring around each endpoint, so the tips read as small
   destinations rather than stray dots */
.revstar__endhalo {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-opacity: .35;
  opacity: 0;
  transform: scale(.5);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity .5s var(--ease), transform .5s var(--ease-out);
  transition-delay: calc(.4s + var(--seq) * .9s + .5s);
}

.revstar__label {
  fill: var(--text-mid);
  font-size: 15px;
  letter-spacing: -.01em;
  dominant-baseline: middle;
  opacity: 0;
  transition: opacity .55s var(--ease-out);
  transition-delay: calc(.4s + var(--seq) * .9s + .48s);
}

/* the drawn state, set once when the figure enters the view */
.revstar.is-drawn .revstar__halo {
  opacity: 1;
}

.revstar.is-drawn .revstar__ring,
.revstar.is-drawn .revstar__node {
  opacity: 1;
  transform: scale(1);
}

.revstar.is-drawn .revstar__nodelabel,
.revstar.is-drawn .revstar__end,
.revstar.is-drawn .revstar__label {
  opacity: 1;
}

.revstar.is-drawn .revstar__endhalo {
  opacity: 1;
  transform: scale(1);
}

.revstar.is-drawn .revstar__line {
  stroke-dashoffset: 0;
}

@media (min-width: 980px) {
  .psec.rev {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
    gap: clamp(40px, 5vw, 104px);
    align-items: center;
  }

  .rev__viz {
    margin-top: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .revstar__node {
    transform: none;
  }
}

/* ---------- Stepper ----------
   Four stages of prose is a page of copy. One panel at a time, with the
   rail above carrying the sequence, is the same content read as steps.

   The base state is the fallback: a stacked list with everything
   visible, which is what a reader without JS gets. .is-live is added by
   the script and switches to one-at-a-time. */
/* ---------- Method grid ----------
   The four steps of the reasoning as a 2x2 grid, each a tidy block: the
   step number and name, the claim, then the detail. A top rule sets each
   one off and the numbers keep the order. Steps fade in as reached; one
   column below the breakpoint. */
.pmethod {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(34px, 5vh, 54px) clamp(40px, 5vw, 104px);
  margin-top: clamp(36px, 5vh, 60px);
  padding: 0;
  list-style: none;
}

.pmethod__step {
  border-top: 1px solid var(--line);
  padding-top: clamp(20px, 2.6vh, 28px);
}

/* number, then name, on one line */
.pmethod__num {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0 0 clamp(14px, 2vh, 20px);
  font-size: clamp(1.5rem, 2.4vw, 2.05rem);
  letter-spacing: -.032em;
  line-height: 1.04;
  color: var(--text);
}

.pmethod__num span {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .16em;
  color: var(--text-faint);
  transform: translateY(-.15em);
}

.pmethod__claim {
  color: var(--text);
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  letter-spacing: -.022em;
  line-height: 1.34;
  margin: 0 0 .85em;
  max-width: 42ch;
}

.pmethod__text {
  color: var(--text-mid);
  line-height: 1.75;
  max-width: 52ch;
}

.pmethod__text + .pmethod__text {
  margin-top: .8em;
}

@media (max-width: 720px) {
  .pmethod {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(28px, 4vh, 40px);
  }
}

/* ---------- Pipeline rail ----------
   Sample to detection, read left to right. The nodes are evenly spaced
   and the connecting line is one element behind them, so the spacing
   comes from the grid rather than from per-item borders. A single point
   travels the line; under reduced motion it stops at the end, which
   leaves the rail reading as a finished path rather than an empty one. */
.pflow {
  position: relative;
  max-width: 46rem;
  margin-top: clamp(38px, 6vh, 64px);
  padding: clamp(30px, 4vh, 44px) 0 0;
}

.pflow__rail {
  position: absolute;
  top: calc(clamp(30px, 4vh, 44px) + 7px);
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line);
  overflow: hidden;
}

.pflow__pulse {
  position: absolute;
  top: 0;
  left: 0;
  width: 22%;
  height: 100%;
  background: linear-gradient(90deg, transparent, var(--text) 55%, transparent);
  opacity: .55;
  animation: pflow-run 5.6s var(--ease) infinite;
}

@keyframes pflow-run {
  0% {
    transform: translateX(-100%);
  }

  70%,
  100% {
    transform: translateX(455%);
  }
}

.pflow__steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 20px);
}

.pflow__step {
  display: grid;
  gap: 12px;
  align-content: start;
}

.pflow__node {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  position: relative;
}

.pflow__node::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: var(--text-faint);
  transition: background-color .4s var(--ease);
}

/* the two ends are what the section is about: a sample in, a defence out */
.pflow__step:first-child .pflow__node::after,
.pflow__step:last-child .pflow__node::after {
  background: var(--text);
}

.pflow__label {
  font-size: .86rem;
  line-height: 1.45;
  color: var(--text);
  letter-spacing: -.012em;
}

.pquote {
  max-width: 46rem;
  margin-top: clamp(30px, 4.5vh, 46px);
  padding: clamp(22px, 3vh, 32px) 0 clamp(22px, 3vh, 32px) clamp(20px, 3vw, 36px);
  border-left: 1px solid var(--line-strong);
  font-size: clamp(1.15rem, 2.1vw, 1.6rem);
  line-height: 1.4;
  letter-spacing: -.03em;
  color: var(--text);
}

.pflow__note {
  margin-top: clamp(26px, 4vh, 40px);
  max-width: 46rem;
  color: var(--text-mid);
  line-height: 1.72;
}

.pflow__note strong {
  color: var(--text);
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .pflow__pulse {
    animation: none;
    transform: translateX(455%);
  }
}

/* stacked and still, the resting place is the bottom of the rail */
@media (prefers-reduced-motion: reduce) and (max-width: 860px) {
  .pflow__pulse {
    transform: translateY(555%);
  }
}

/* Stacked, the rail turns vertical and runs down the left of the labels. */
@media (max-width: 860px) {
  .pflow {
    padding-top: 0;
  }

  .pflow__rail {
    top: 8px;
    bottom: 8px;
    left: 7px;
    right: auto;
    width: 1px;
    height: auto;
  }

  .pflow__pulse {
    width: 100%;
    height: 18%;
    background: linear-gradient(180deg, transparent, var(--text) 55%, transparent);
    animation-name: pflow-run-v;
  }

  @keyframes pflow-run-v {
    0% {
      transform: translateY(-100%);
    }

    70%,
    100% {
      transform: translateY(555%);
    }
  }

  .pflow__steps {
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
  }

  .pflow__step {
    grid-template-columns: 15px minmax(0, 1fr);
    gap: 16px;
    align-items: center;
  }
}

/* Read top to bottom at every width, not only when stacked. The rail is
   the page's closing figure, so it keeps its shape on a wide screen. */
.pflow--down {
  max-width: 34rem;
  padding-top: 0;
}

.pflow--down .pflow__rail {
  top: 8px;
  bottom: 8px;
  left: 7px;
  right: auto;
  width: 1px;
  height: auto;
}

.pflow--down .pflow__pulse {
  width: 100%;
  height: 15%;
  background: linear-gradient(180deg, transparent, var(--text) 55%, transparent);
  animation-name: pflow-run-v;
}

.pflow--down .pflow__steps {
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
}

.pflow--down .pflow__step {
  grid-template-columns: 15px minmax(0, 1fr);
  gap: 18px;
  align-items: center;
}

/* the sequence is a set of stages, so it is set like the other labels
   on these pages rather than as running text */
.pflow--down .pflow__label {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.pflow--down .pflow__step:first-child .pflow__label,
.pflow--down .pflow__step:last-child .pflow__label {
  color: var(--text);
}

@keyframes pflow-run-v-down {
  0% {
    transform: translateY(-100%);
  }

  70%,
  100% {
    transform: translateY(660%);
  }
}

.pflow--down .pflow__pulse {
  animation-name: pflow-run-v-down;
}

@media (prefers-reduced-motion: reduce) {
  .pflow--down .pflow__pulse {
    transform: translateY(660%);
  }
}

/* ---------- Pipeline rail, wide ----------
   The closing sequence read left to right across the whole measure: the
   line spans margin to margin, the seven stages sit along it (the first
   on the left edge, the last on the right), and a single shiny dot with
   a short tail travels the line like a moving arrowhead. Stacks to a
   vertical rail on narrow screens so the labels never crowd. */
.pflow--wide {
  max-width: none;
  color: var(--text);
}

.pflow--wide .pflow__rail {
  overflow: visible;
}

/* a shiny dot with a fading tail behind it -- reads as flow, not a band */
.pflow--wide .pflow__pulse {
  width: 10px;
  height: 10px;
  top: 50%;
  left: 0;
  border-radius: 50%;
  background: radial-gradient(circle, var(--text) 0%, var(--text) 34%, transparent 72%);
  box-shadow: 0 0 10px 2px var(--text);
  opacity: 0;
  transform: translate(-50%, -50%);
  animation: pflow-dot 5.6s var(--ease) infinite;
}

.pflow--wide .pflow__pulse::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 50%;
  width: 54px;
  height: 2px;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, var(--text));
  opacity: .45;
}

@keyframes pflow-dot {
  0% { left: 0; opacity: 0; }
  5% { opacity: 1; }
  60% { left: 100%; opacity: 1; }
  70% { left: 100%; opacity: 0; }
  100% { left: 100%; opacity: 0; }
}

.pflow--wide .pflow__steps {
  display: flex;
  justify-content: space-between;
  gap: 12px;
}

.pflow--wide .pflow__step {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  gap: 14px;
}

/* first and last sit on the margins, so their labels align inward */
.pflow--wide .pflow__step:first-child {
  justify-items: start;
}

.pflow--wide .pflow__step:last-child {
  justify-items: end;
}

.pflow--wide .pflow__label {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-mid);
  text-align: center;
  white-space: nowrap;
}

.pflow--wide .pflow__step:first-child .pflow__label {
  text-align: left;
}

.pflow--wide .pflow__step:last-child .pflow__label {
  text-align: right;
}

.pflow--wide .pflow__step:first-child .pflow__label,
.pflow--wide .pflow__step:last-child .pflow__label {
  color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
  .pflow--wide .pflow__pulse {
    animation: none;
    opacity: 0;
  }
}

/* narrow: fold to a vertical rail down the left of the labels. Matches
   the base .pflow mobile breakpoint (860) so the rail and the steps turn
   vertical together rather than one before the other. */
@media (max-width: 860px) {
  .pflow--wide {
    padding-top: 0;
  }

  .pflow--wide .pflow__rail {
    top: 8px;
    bottom: 8px;
    left: 7px;
    right: auto;
    width: 1px;
    height: auto;
    overflow: hidden;
  }

  .pflow--wide .pflow__pulse {
    left: 50%;
    top: 0;
    width: 100%;
    height: 16%;
    border-radius: 0;
    background: linear-gradient(180deg, transparent, var(--text) 55%, transparent);
    box-shadow: none;
    transform: translateX(-50%);
    animation: pflow-run-v-down 5.6s var(--ease) infinite;
  }

  .pflow--wide .pflow__pulse::before {
    display: none;
  }

  .pflow--wide .pflow__steps {
    flex-direction: column;
    align-items: stretch;
    gap: 20px;
  }

  .pflow--wide .pflow__step,
  .pflow--wide .pflow__step:first-child,
  .pflow--wide .pflow__step:last-child {
    grid-template-columns: 15px minmax(0, 1fr);
    justify-items: start;
    align-items: center;
    gap: 16px;
  }

  .pflow--wide .pflow__step .pflow__label,
  .pflow--wide .pflow__step:first-child .pflow__label,
  .pflow--wide .pflow__step:last-child .pflow__label {
    text-align: left;
  }
}

/* ---------- Intelligence finding ----------
   A worked example of what lands on a desk. It is labelled as an
   illustration on the page, because it is one. */
.pfind {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(28px, 5vw, 76px);
  align-items: start;
  margin-top: clamp(34px, 5vh, 56px);
}

/* the heading lives in the left column, so the card's top edge is level
   with the eyebrow rather than a heading's height below it */
.pfind {
  margin-top: 0;
}

.pfind__side>.psec__head {
  margin-bottom: 0;
  max-width: none;
}

.pfind__card {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: linear-gradient(168deg, var(--surface), var(--bg-elev) 92%);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}

.pfind__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  padding: clamp(18px, 2.1vw, 24px);
  border-bottom: 1px solid var(--line-soft);
}

.pfind__label {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.pfind__title {
  margin-top: 10px;
  font-size: clamp(1.08rem, 1.5vw, 1.3rem);
  letter-spacing: -.03em;
  max-width: 26ch;
}

.pfind__chip {
  flex: none;
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text);
  white-space: nowrap;
}

.pfind__rows {
  margin: 0;
}

.pfind__row {
  display: grid;
  grid-template-columns: minmax(0, 10rem) minmax(0, 1fr);
  gap: 6px clamp(14px, 2vw, 24px);
  padding: clamp(11px, 1.4vw, 14px) clamp(18px, 2.1vw, 24px);
  border-bottom: 1px solid var(--line-soft);
}

.pfind__row:last-child {
  border-bottom: 0;
}

.pfind__key {
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-mid);
  padding-top: .2em;
  margin: 0;
}

.pfind__val {
  margin: 0;
  font-size: .92rem;
  line-height: 1.6;
  color: var(--text);
}

.pfind__note {
  margin-top: clamp(18px, 2.6vh, 26px);
  font-size: .82rem;
  line-height: 1.6;
  color: var(--text-mid);
  max-width: 46ch;
}

@media (max-width: 1080px) {

  .pfind {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 720px) {

  .pfind__row {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- The gap ----------
   What every alert leaves to be done, against what a finding arrives
   with. The two are drawn differently on purpose: work in series down a
   rail, against a node with its answers already attached.

   The whole figure is decoration -- the keys and the words in it say
   everything it says. */
.pprob {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 27rem);
  gap: clamp(28px, 4vw, 76px);
  align-items: start;
}

.pgap {
  display: grid;
  gap: clamp(26px, 3.6vh, 40px);
}

.pgap__side {
  padding-top: clamp(14px, 2vh, 20px);
  border-top: 1px solid var(--line);
}

.pgap__side--live {
  border-top-color: var(--line-strong);
}

.pgap__key {
  margin: 0 0 clamp(16px, 2.2vh, 22px);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text);
}

/* ---- the node both sides start from ---- */
.pgap__node {
  display: inline-block;
  margin: 0;
  padding: .5em .9em;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg);
  font-size: .86rem;
  letter-spacing: .01em;
  color: var(--text);
}

.psec--quiet .pgap__node {
  background: var(--bg-elev);
}

/* three of them, because this happens again for every tool. Layered
   shadows rather than extra elements: each pair paints a card-coloured
   block and then its outline, one step further back. */
.pgap__node--many {
  box-shadow:
    4px 4px 0 -1px var(--bg), 4px 4px 0 0 var(--line-strong),
    8px 8px 0 -1px var(--bg), 8px 8px 0 0 var(--line);
}

/* ---- left: the work, in series ---- */
.pgap__chain {
  position: relative;
  margin: 0;
  padding: 4px 0 0 26px;
  list-style: none;
}

/* the rail that makes it a sequence rather than a list */
.pgap__chain::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0;
  bottom: 1.1em;
  width: 1px;
  background: var(--line-strong);
}

.pgap__chain li {
  position: relative;
  padding: .52em 0;
  font-size: .93rem;
  line-height: 1.45;
  color: var(--text-mid);
}

/* a ring: still to be answered */
.pgap__chain li::before {
  content: "";
  position: absolute;
  left: -26px;
  top: .92em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--bg);
}

.psec--quiet .pgap__chain li::before {
  background: var(--bg-elev);
}

/* the step down from the box into the chain */
.pgap__drop {
  width: 1px;
  height: 18px;
  margin-left: 4.5px;
  background: var(--line-strong);
}

.pgap__mul {
  margin: clamp(16px, 2.2vh, 22px) 0 0;
  font-size: .82rem;
  letter-spacing: .02em;
  color: var(--text-mid);
}

/* ---- right: the answers, already attached ---- */
.pgap__fan {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0 20px;
  align-items: center;
}

.pgap__out {
  position: relative;
  margin: 0;
  padding: 0 0 0 20px;
  list-style: none;
}

/* the spine the branches hang from */
.pgap__out::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.05em;
  bottom: 1.05em;
  width: 1px;
  background: var(--text);
}

/* and the arm reaching back to the node */
.pgap__out::after {
  content: "";
  position: absolute;
  left: -20px;
  top: 50%;
  width: 20px;
  height: 1px;
  background: var(--text);
}

.pgap__out li {
  position: relative;
  /* clear of the arm and the dot, which together reach 19px */
  padding: .45em 0 .45em 30px;
  font-size: .93rem;
  line-height: 1.45;
  color: var(--text);
}

/* each branch: a short arm and a filled dot */
.pgap__out li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.06em;
  width: 12px;
  height: 1px;
  background: var(--text);
}

.pgap__out li::after {
  content: "";
  position: absolute;
  left: 12px;
  top: .82em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text);
}

@media (max-width: 1080px) {
  .pprob {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(26px, 4vh, 40px);
  }

  .pgap {
    max-width: 34rem;
  }
}

/* ---------- Cadence ----------
   Point-in-time against continuous, and then the cycle that follows
   from it. The first two rows are coverage bars rather than lines: the
   periodic one is lit only where a check ran, the continuous one is lit
   throughout, and the unlit area is the argument.

   Every mark here is decoration -- the key, the claim and the line
   under each carry the meaning on their own. */
.pcad {
  margin-top: clamp(28px, 4vh, 44px);
  max-width: 64rem;
}

.pcad__lede {
  margin: 0 0 clamp(24px, 3.6vh, 36px);
  max-width: 56ch;
  font-size: 1.02rem;
  line-height: 1.68;
  color: var(--text-mid);
}

.pcad__row {
  display: grid;
  grid-template-columns: minmax(0, 10rem) minmax(0, 1fr);
  grid-template-areas:
    "key viz"
    "key claim"
    "key say";
  gap: 0 clamp(18px, 2.6vw, 38px);
  padding: clamp(22px, 3.2vh, 32px) 0;
  border-top: 1px solid var(--line-soft);
}

.pcad__row:last-of-type {
  border-bottom: 1px solid var(--line-soft);
}

.pcad__key {
  grid-area: key;
  align-self: start;
  margin: 0;
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.pcad__row--live .pcad__key {
  color: var(--text);
}

.pcad__claim {
  grid-area: claim;
  margin: clamp(16px, 2.2vh, 22px) 0 0;
  font-size: 1.02rem;
  line-height: 1.5;
  letter-spacing: -.012em;
  color: var(--text);
}

.pcad__say {
  grid-area: say;
  margin: .55em 0 0;
  font-size: .95rem;
  line-height: 1.66;
  color: var(--text-mid);
}

.pcad__viz {
  grid-area: viz;
}

/* ---- the coverage bars ---- */
.pcad__bar {
  position: relative;
  height: 22px;
  border-radius: 3px;
  background: var(--line-soft);
  overflow: hidden;
}

/* lit only where a check ran */
.pcad__bar--gapped {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.pcad__bar--gapped span {
  background: linear-gradient(90deg, var(--text-dim) 0 12%, transparent 12%);
}

/* lit throughout */
.pcad__bar--full::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--text);
  opacity: .58;
}

/* and a brighter pass running along it, so it reads as live */
.pcad__bar--full::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 22%;
  background: linear-gradient(90deg, transparent, var(--text), transparent);
  animation: pcad-run 4.6s var(--ease-in-out) infinite;
}

@keyframes pcad-run {
  0% {
    transform: translateX(-110%);
  }

  100% {
    transform: translateX(560%);
  }
}

/* what the lit and unlit parts of the first bar mean */
.pcad__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  font-size: .76rem;
  letter-spacing: .04em;
  color: var(--text-faint);
}

.pcad__legend li {
  display: flex;
  align-items: center;
  gap: 8px;
}

.pcad__legend li::before {
  content: "";
  width: 14px;
  height: 8px;
  border-radius: 2px;
  background: var(--line-soft);
}

.pcad__legend li:first-child::before {
  background: var(--text-dim);
}

/* ---- the cycle ---- */
.pcyc {
  position: relative;
  /* the full width of the column, so the loop reads at the same length
     as the bars in the rows above it */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 0;
  margin: 0;
  padding: 0 0 30px;
  list-style: none;
}

.pcyc li {
  font-size: .92rem;
  letter-spacing: -.01em;
  color: var(--text);
}

/* the step arrows */
.pcyc li+li::before {
  content: "";
  display: inline-block;
  width: .52rem;
  height: .52rem;
  margin-right: clamp(10px, 1.6vw, 24px);
  border-top: 1px solid var(--line-strong);
  border-right: 1px solid var(--line-strong);
  transform: rotate(45deg) translateY(-.06rem);
}

/* the path back from the last step to the first, which is what makes it
   a cycle rather than a list */
.pcyc::before {
  content: "";
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 10px;
  height: 14px;
  border: 1px solid var(--line-strong);
  border-top: 0;
  border-radius: 0 0 10px 10px;
}

.pcyc::after {
  content: "";
  position: absolute;
  left: 1px;
  bottom: 20px;
  width: .46rem;
  height: .46rem;
  border-top: 1px solid var(--line-strong);
  border-left: 1px solid var(--line-strong);
  transform: rotate(45deg);
}

/* the key sits above its diagram rather than beside it */
@media (max-width: 720px) {
  .pcad__row {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "key"
      "viz"
      "claim"
      "say";
    gap: 12px 0;
  }

  .pcad__claim {
    margin-top: 4px;
  }
}

/* nothing travels; the bars still say what they say */
@media (prefers-reduced-motion: reduce) {
  .pcad__bar--full::after {
    animation: none;
    opacity: 0;
  }
}

/* ---------- The shift ----------
   What changed, as a diagram rather than six paragraphs. Two panels
   carry the contrast the section is actually about -- the question then
   and the question now -- and the two lists underneath are the things
   the prose was enumerating inside its sentences.

   Everything is one column below the breakpoint, and the arrow between
   the panels turns to point down, because the contrast reads top to
   bottom there rather than left to right. */
.pshift {
  margin-top: clamp(28px, 4vh, 44px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  gap: clamp(26px, 3.6vw, 68px);
  align-items: start;
}

.pshift__main {
  min-width: 0;
}

.pshift__lede {
  margin: 0 0 clamp(22px, 3.4vh, 34px);
  max-width: 54ch;
  font-size: 1.02rem;
  line-height: 1.68;
  color: var(--text-mid);
}

/* ---- then / now ---- */
/* a column, not a row: the old question above the new one, which is the
   order the section argues them in */
.pshift__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(10px, 1.4vh, 16px);
}

.pshift__panel {
  padding: clamp(20px, 2.4vw, 30px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
}

.psec--quiet .pshift__panel {
  background: var(--bg);
}

/* the one that still applies is the one that is marked */
.pshift__panel--now {
  border-color: var(--line-strong);
}

.pshift__when {
  margin: 0 0 .7em;
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.pshift__panel--now .pshift__when {
  color: var(--text-mid);
}

.pshift__ask {
  margin: 0;
  font-size: clamp(1.1rem, 1.9vw, 1.42rem);
  line-height: 1.24;
  letter-spacing: -.026em;
  /* both questions read at full strength; the panel that still applies
     is marked by its border and its label, not by dimming the other */
  color: var(--text);
}

.pshift__panel--now .pshift__ask {
  color: var(--text);
}

.pshift__arrow {
  display: grid;
  place-items: center;
  color: var(--text);
}

.pshift__arrow span {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(135deg) scale(.62);
}

/* ---- the lists the prose was carrying ---- */
.pshift__group {
  margin-top: clamp(22px, 3.4vh, 34px);
}

.pshift__main>.pshift__group:first-of-type {
  margin-top: 0;
}

.pshift__cap {
  margin: 0 0 clamp(12px, 1.8vh, 18px);
  max-width: 54ch;
  font-size: .95rem;
  line-height: 1.66;
  color: var(--text-mid);
}

.pshift__cap strong {
  color: var(--text);
  font-weight: 600;
}

.pshift__set {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pshift__set li {
  padding: .5em .85em;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .9rem;
  line-height: 1.3;
  color: var(--text-mid);
  opacity: 0;
  transform: translateY(6px);
}

/* the marker that makes a path item read as a step, not a tag */
.pshift__set--path li {
  display: flex;
  align-items: center;
  gap: .6em;
  color: var(--text);
}

.pshift__set--path li::before {
  content: "";
  width: 5px;
  height: 5px;
  flex: none;
  border-radius: 50%;
  background: var(--text-dim);
}

/* they arrive in order once the section does */
.pshift.is-in .pshift__set li {
  opacity: 1;
  transform: none;
  transition: opacity .45s var(--ease-out), transform .45s var(--ease-out);
  transition-delay: calc(var(--i) * 45ms);
}

.pshift__close {
  margin: clamp(28px, 4vh, 42px) 0 0;
  padding-top: clamp(18px, 2.6vh, 26px);
  border-top: 1px solid var(--line-soft);
  max-width: 46rem;
  font-size: clamp(1.05rem, 1.7vw, 1.32rem);
  line-height: 1.32;
  letter-spacing: -.028em;
  color: var(--text);
}

/* the pair drops under the copy rather than beside it */
@media (max-width: 1080px) {
  .pshift {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(26px, 4vh, 40px);
  }

  .pshift__pair {
    max-width: 34rem;
  }
}

@media (prefers-reduced-motion: reduce) {

  .pshift__set li {
    opacity: 1;
    transform: none;
  }

  .pshift.is-in .pshift__set li {
    transition: none;
  }
}

/* ---------- Split section: a heading beside its detail ----------
   The heading holds the left column and the thing it introduces sits
   to its right, rather than the heading sitting over a column of prose.
   The two columns collapse to one before the right one gets too narrow
   to be worth keeping. */
.psec__split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 36rem);
  gap: clamp(28px, 4vw, 76px);
  align-items: center;
}

.psplit__lead {
  align-self: center;
}

.psplit__lead>.psec__head {
  margin-bottom: 0;
}

/* the four lines under the heading, one for each stage in the deck */
.pdeck__intro {
  margin-top: clamp(20px, 3vh, 30px);
  display: grid;
  gap: .9em;
  /* short enough that the card drawn out of the hand passes beside the
     copy rather than over it */
  max-width: 40ch;
}

.pdeck__intro p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.68;
  color: var(--text-mid);
}

.pdeck__intro strong {
  color: var(--text);
  font-weight: 600;
}

/* ---------- The deck ----------
   Four stages held as a hand of cards: the front one square-on and
   readable, the rest fanned up and to the right behind it. Every three
   seconds the front card tucks to the back and the next comes forward.

   Until the script has said it is live, the cards are a plain stacked
   list -- which is what a reader without JS gets, and what a reader who
   has asked for less movement gets too. */
.pdeck {
  display: grid;
  gap: clamp(14px, 2vh, 20px);
}

.pdeck.is-live {
  /* where each place in the hand is. The keyframe below lands on
     --fan-3, so these are declared once and used twice. */
  --fan-0: translate(0, 0) rotate(0deg) scale(1);
  --fan-1: translate(34px, -17px) rotate(4.6deg) scale(.968);
  --fan-2: translate(64px, -33px) rotate(9deg) scale(.938);
  --fan-3: translate(90px, -46px) rotate(13deg) scale(.908);
  --fan-4: translate(114px, -58px) rotate(16.4deg) scale(.882);
  /* where a card leaving the front ends up: the back of this hand */
  --fan-last: var(--fan-3);
  /* Where a card leaving the front is taken out to before it goes
     round. It has to clear the hand to read as a card being drawn, and
     it has to stop short of the copy in the left column, which it would
     otherwise cover -- so the distance follows the viewport. */
  --fan-out-x: clamp(-162px, -8.8vw, -98px);
  --fan-out: translate(var(--fan-out-x), 18px) rotate(-5.5deg) scale(1.02);
  gap: 0;
  /* the fan reaches up and right of the front card; that room is
     reserved here, because a transform does not take any -- and it has
     to allow for the corner a turned card swings out, not just the
     distance it is moved */
  padding: 52px 108px 0 0;
}

.pdeck__card {
  display: grid;
  align-content: start;
  gap: 10px;
  padding: clamp(24px, 2.4vw, 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
}

/* stacked into one cell, so the row is as tall as the longest stage and
   every card is that height -- a deck has to be one size */
/* a click anywhere on the hand sends the front card round */
.pdeck.is-live {
  cursor: pointer;
}

.pdeck.is-live .pdeck__card {
  grid-area: 1 / 1;
  box-shadow: var(--shadow-2);
  will-change: transform;
}

.pdeck__num {
  margin: 0;
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.pdeck__title {
  margin: 0;
  font-size: clamp(1.28rem, 1.9vw, 1.6rem);
  letter-spacing: -.02em;
  line-height: 1.16;
}

.pdeck__claim {
  margin: 0;
  font-size: 1rem;
  line-height: 1.5;
  letter-spacing: -.01em;
  color: var(--text);
}

.pdeck__body {
  margin: 0;
  font-size: .92rem;
  line-height: 1.66;
  color: var(--text-mid);
}

/* ---- the fan ----
   data-pos is the card's place in the hand, 0 being the front. The
   script rewrites it; nothing here depends on source order. */
.pdeck.is-live .pdeck__card[data-pos="0"] {
  transform: var(--fan-0);
  z-index: 4;
}

.pdeck.is-live .pdeck__card[data-pos="1"] {
  transform: var(--fan-1);
  z-index: 3;
}

.pdeck.is-live .pdeck__card[data-pos="2"] {
  transform: var(--fan-2);
  z-index: 2;
}

.pdeck.is-live .pdeck__card[data-pos="3"] {
  transform: var(--fan-3);
  z-index: 1;
}

/* a five-card hand reaches one step further, and needs the room for it */
.pdeck--5.is-live {
  padding: 64px 134px 0 0;
  --fan-last: var(--fan-4);
}

.pdeck--5.is-live .pdeck__card[data-pos="4"] {
  transform: var(--fan-4);
  z-index: 0;
}

/* Only the front card is meant to be read; the ones behind it are the
   shape of the rest of the hand, so their copy steps back with them. */
.pdeck.is-live .pdeck__card .pdeck__claim,
.pdeck.is-live .pdeck__card .pdeck__body {
  opacity: .34;
}

.pdeck.is-live .pdeck__card[data-pos="0"] .pdeck__claim,
.pdeck.is-live .pdeck__card[data-pos="0"] .pdeck__body {
  opacity: 1;
}

.pdeck.is-live .pdeck__card[data-pos="0"] {
  border-color: var(--line-strong);
}

/* The card leaving the front takes the long way round: out towards the
   reader, then up into the back of the fan. It stays drawn on top for
   almost all of that, so the movement is legible, and only slips behind
   the others once it is nearly home. */
@keyframes pdeck-tuck {
  0% {
    transform: var(--fan-0);
    z-index: 6;
  }

  /* out to the left, and held there a beat: without the pause the two
     halves run together and it reads as one diagonal sweep */
  38% {
    transform: var(--fan-out);
  }

  50% {
    transform: var(--fan-out);
  }

  88% {
    z-index: 6;
  }

  89% {
    z-index: 1;
  }

  100% {
    transform: var(--fan-last);
    z-index: 1;
  }
}

/* No fill: the keyframe ends exactly where the last place in the hand
   already puts the card, so once it is over the card simply holds its
   own place. A
   filled animation would pin it there instead, and a class left behind
   by a missed animationend would strand it at the back of the hand. */
.pdeck.is-live .pdeck__card.is-tuck {
  animation: pdeck-tuck 1.05s var(--ease-in-out);
}

/* Movement is armed a frame after the hand is first dealt, so the cards
   are already in place when the transition is attached and nothing
   animates in from nowhere on load. */
.pdeck.is-ready .pdeck__card {
  transition: transform .62s var(--ease-out), border-color .4s var(--ease);
}

.pdeck.is-ready .pdeck__card .pdeck__claim,
.pdeck.is-ready .pdeck__card .pdeck__body {
  transition: opacity .42s var(--ease);
}

/* One column: the heading sits above, and the deck unfans into the
   readable list it is underneath. This gives way at 1200px rather than
   at the narrower breakpoint the rest of the page uses, because below
   it there is no room between the copy and the deck for a card to be
   drawn out to the left. */
@media (max-width: 1200px) {
  .psec__split {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(26px, 4vh, 40px);
  }

  .psplit__lead>.psec__head {
    margin-bottom: 0;
  }

  .pdeck.is-live {
    gap: clamp(14px, 2vh, 20px);
    padding: 0;
  }

  .pdeck.is-live {
    cursor: auto;
  }

  /* [data-pos] to match the weight of the fan's own selectors */
  .pdeck.is-live .pdeck__card[data-pos] {
    grid-area: auto;
    box-shadow: none;
    transform: none;
    border-color: var(--line);
    animation: none;
  }

  .pdeck.is-live .pdeck__card[data-pos] .pdeck__claim,
  .pdeck.is-live .pdeck__card[data-pos] .pdeck__body {
    opacity: 1;
  }

  .pdeck.is-ready .pdeck__card[data-pos],
  .pdeck.is-ready .pdeck__card[data-pos] .pdeck__claim,
  .pdeck.is-ready .pdeck__card[data-pos] .pdeck__body {
    transition: none;
  }
}

/* Nothing is dealt and nothing cycles: four cards, one under the next,
   all four readable at once. */
@media (prefers-reduced-motion: reduce) {

  .pdeck.is-live {
    gap: clamp(14px, 2vh, 20px);
    padding: 0;
  }

  .pdeck.is-live {
    cursor: auto;
  }

  /* [data-pos] to match the weight of the fan's own selectors */
  .pdeck.is-live .pdeck__card[data-pos] {
    grid-area: auto;
    box-shadow: none;
    transform: none;
    border-color: var(--line);
    animation: none;
  }

  .pdeck.is-live .pdeck__card[data-pos] .pdeck__claim,
  .pdeck.is-live .pdeck__card[data-pos] .pdeck__body {
    opacity: 1;
  }

  .pdeck.is-ready .pdeck__card[data-pos],
  .pdeck.is-ready .pdeck__card[data-pos] .pdeck__claim,
  .pdeck.is-ready .pdeck__card[data-pos] .pdeck__body {
    transition: none;
  }
}

/* ---------- Event chain ----------
   Eight points on one rail, read left to right, every one marked the
   same way: the notes say which are the host behaving and which are
   Prava acting, so the nodes do not need to.

   A point travels the rail and rests on each in turn while that step's
   note is held. Below the breakpoint the rail turns vertical, because
   eight columns of label is not a thing a phone can show. */
.pchain {
  margin-top: clamp(34px, 5vh, 56px);
  max-width: 58rem;
  margin-inline: auto;
}

.pchain__track {
  position: relative;
  padding-top: 10px;
}

.pchain__rail {
  position: absolute;
  left: 0;
  right: 0;
  top: 19px;
  height: 1px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.pchain__steps {
  position: relative;
  display: grid;
  /* --n is however many points this page's chain has */
  grid-template-columns: repeat(var(--n, 8), minmax(0, 1fr));
  gap: clamp(4px, .8vw, 12px);
}

.pchain__step {
  display: grid;
}

.pchain__hit {
  display: grid;
  justify-items: center;
  gap: 12px;
  text-align: center;
  cursor: pointer;
  padding: 0;
}

/* every node identical -- a ring on the page ground with a white centre */
.pchain__node {
  width: 19px;
  height: 19px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: var(--bg);
  opacity: 0;
  transform: scale(.6);
}

.psec--quiet .pchain__node {
  background: var(--bg-elev);
}

.pchain__node::after {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text);
}

.pchain__label {
  font-size: .82rem;
  line-height: 1.4;
  letter-spacing: -.008em;
  color: var(--text-mid);
  opacity: 0;
  transform: translateY(6px);
  transition: color .4s var(--ease);
}

/* the step the point is resting on: its ring comes up to meet the
   travelling point, which is otherwise the same white as every centre
   and hard to pick out against them */
.pchain__step.is-on .pchain__label,
.pchain__hit:hover .pchain__label,
.pchain__hit:focus-visible .pchain__label {
  color: var(--text);
}

.pchain__step.is-on .pchain__node,
.pchain__hit:hover .pchain__node,
.pchain__hit:focus-visible .pchain__node {
  border-color: var(--text);
  transition: border-color .3s var(--ease);
}

/* the point that runs the rail */
.pchain__run {
  position: absolute;
  left: 0;
  top: 19px;
  width: 7px;
  height: 7px;
  margin: -3px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--text);
  opacity: 0;
  z-index: 2;
}

.pchain.is-live .pchain__run {
  transition: transform .55s var(--ease-out), opacity .3s var(--ease);
}

/* ---- the notes ----
   All eight occupy one cell, so the block never changes height as they
   swap and nothing below it moves. */
/* The note is set across the whole rail and centred under it, so it
   fills one line and turns onto a second rather than sitting in a
   narrow column off to the left. */
.pchain__say {
  display: grid;
  grid-template-areas: "note";
  margin-top: clamp(28px, 4.2vh, 40px);
  text-align: center;
}

.pchain__say .pchain__note {
  grid-area: note;
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.68;
  color: var(--text-mid);
}

.pchain__say .pchain__note strong {
  color: var(--text);
  font-weight: 600;
}

/* Until the script runs, every note is in flow and readable -- which is
   also what a reader who asked for less movement gets. */
.pchain.is-live .pchain__say .pchain__note {
  opacity: 0;
  visibility: hidden;
}

.pchain.is-live .pchain__say .pchain__note.is-on {
  opacity: 1;
  visibility: visible;
}

/* One leaves before the next arrives. Crossing them over meant both
   were painted at half strength on the same two lines, which reads as
   the text having gone wrong rather than as a transition. */
.pchain.is-live.is-ready .pchain__say .pchain__note {
  transition: opacity .16s var(--ease), visibility 0s linear .16s;
}

.pchain.is-live.is-ready .pchain__say .pchain__note.is-on {
  transition: opacity .3s var(--ease) .18s, visibility 0s;
}

/* ---- it draws in, left to right ---- */
.pchain.is-in .pchain__rail {
  transform: scaleX(1);
  transition: transform .9s var(--ease-out);
}

.pchain.is-in .pchain__node {
  opacity: 1;
  transform: none;
  transition: opacity .4s var(--ease-out), transform .4s var(--ease-out);
  transition-delay: calc(var(--i) * 90ms + 120ms);
}

.pchain.is-in .pchain__label {
  opacity: 1;
  transform: none;
  transition: opacity .5s var(--ease-out), transform .5s var(--ease-out);
  transition-delay: calc(var(--i) * 90ms + 180ms);
}

.pchain.is-in.is-live .pchain__run {
  opacity: 1;
}

/* The line that sets the chain up, above the rail rather than under it.
   Given the rail's own width so it runs to two lines instead of the
   three a reading measure would force. */
.pchain__lede {
  margin-top: clamp(24px, 3.6vh, 34px);
  max-width: 58rem;
  font-size: 1.02rem;
  line-height: 1.68;
  color: var(--text-mid);
}

.pchain__lede strong {
  color: var(--text);
  font-weight: 600;
}

/* Nothing travels and nothing swaps: the rail is drawn, every point is
   there, and all eight notes are readable at once. */
@media (prefers-reduced-motion: reduce) {

  .pchain__rail {
    transform: scaleX(1);
  }

  .pchain__node {
    opacity: 1;
    transform: none;
  }

  .pchain__label {
    opacity: 1;
    transform: none;
  }

  .pchain__run {
    display: none;
  }

  .pchain.is-in .pchain__rail,
  .pchain.is-in .pchain__node,
  .pchain.is-in .pchain__label {
    transition: none;
  }

  .pchain.is-live .pchain__say {
    display: block;
  }

  .pchain.is-live .pchain__say .pchain__note {
    opacity: 1;
    visibility: visible;
    transition: none;
  }

  .pchain.is-live .pchain__say .pchain__note+.pchain__note {
    margin-top: .7em;
  }
}

/* Stacked: eight columns of label is not something a phone can show, so
   the rail turns vertical and the notes sit under their own step. */
@media (max-width: 860px) {
  .pchain__steps {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(16px, 2.4vh, 24px);
  }

  .pchain__hit {
    grid-template-columns: 19px minmax(0, 1fr);
    justify-items: start;
    text-align: left;
    align-items: center;
    gap: 0 16px;
  }

  .pchain__say {
    text-align: left;
  }

  .pchain__rail {
    left: 9px;
    right: auto;
    top: 19px;
    bottom: 19px;
    width: 1px;
    height: auto;
    transform: scaleY(0);
    transform-origin: 50% 0;
  }

  .pchain.is-in .pchain__rail {
    transform: scaleY(1);
  }

  .pchain__run {
    display: none;
  }

  .pchain.is-live .pchain__say {
    display: block;
  }

  .pchain.is-live .pchain__say .pchain__note {
    opacity: 1;
    visibility: visible;
  }

  .pchain.is-live .pchain__say .pchain__note+.pchain__note {
    margin-top: .7em;
  }
}

@media (max-width: 720px) {
  .pchain {
    max-width: none;
  }
}
