﻿/* SimX landing page.
   Brand sampled from the logo asset: green #63C54D, charcoal #444142.
   Type: Newsreader for headlines (it is a news product, so a news face), Inter for UI.
   Structure: white hero, dark walkthrough because that is inside the simulation, then light again
   for the business. Dark earns its place rather than decorating.
   Motion: each beat animates only once it is on screen, and every beat animation is CSS. */

:root {
  --green: #63C54D;
  --green-deep: #4FAE3B;
  --green-dim: #8FD97E;
  --ink: #232122;
  --ink-brand: #444142;
  --ink-soft: #6A6668;
  --line: #E7E5E4;
  --paper: #FFFFFF;
  --tint: #F7F7F6;

  /* dark section */
  --night: #16181A;
  --night-2: #1E2124;
  --night-line: #2E3236;
  --night-ink: #F2F1F0;
  --night-soft: #9DA3A8;

  /* Tokenised out of the walkthrough so a light section can override them. These values
     are the originals, which is what the still-dark .inside section continues to use. */
  --well: #15181A;
  --avatar-bg: #2A2E31;
  --tag-warn-bg: rgba(255,255,255,0.08);
  --panel-shadow: 0 40px 80px -50px rgba(0,0,0,0.9);

  --cycle: 20s;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

*, *::before, *::after { box-sizing: border-box; }

/* No scroll snapping. The walkthrough works by pinning one stage and letting the feature list move
   past it, so the scroll stays continuous and nobody waits for a screen to release them. */
html { scroll-behavior: smooth; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.wrap { width: 100%; max-width: 1120px; margin: 0 auto; padding: 0 clamp(20px, 4vw, 56px); }
.wrap.narrow { max-width: 720px; }

h1, h2, h3 { font-family: Newsreader, Georgia, serif; font-weight: 300; letter-spacing: -0.018em; margin: 0 0 18px; }
h2 { font-size: clamp(28px, 3.4vw, 42px); line-height: 1.14; }
h3 { font-size: clamp(23px, 2.2vw, 30px); line-height: 1.18; }
p { margin: 0 0 16px; }

a { color: var(--green-deep); }
.quiet { color: var(--ink-soft); }
.small { font-size: 14px; }

.eyebrow {
  text-transform: uppercase; letter-spacing: 0.18em; font-size: 11px; font-weight: 600;
  color: var(--green-deep); margin-bottom: 14px;
}
.section-lede { color: var(--ink-soft); max-width: 56ch; font-size: clamp(16px, 1.1vw, 18px); }

/* ---------- reveal ---------- */

/* Only hide pending-reveal content when we know scripting is live to reveal it again. With JS off
   or motion.js blocked, everything is simply visible. */
.js .reveal { opacity: 0; transform: translateY(18px); transition: opacity 700ms var(--ease), transform 700ms var(--ease); }
.js .reveal.in { opacity: 1; transform: none; }

/* =========================== HERO =========================== */

.hero {
  min-height: 90svh; min-height: 90vh;
  display: flex; flex-direction: column;
  padding: 28px clamp(20px, 4vw, 56px) clamp(16px, 1.8vw, 26px);
  overflow: hidden;
}
.wordmark { display: inline-flex; align-items: center; text-decoration: none; flex: 0 0 auto; }
.wordmark img { display: block; width: auto; height: 30px; }

.hero-grid {
  flex: 1 1 auto; display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  align-items: center; gap: clamp(28px, 3.6vw, 56px);
  padding-top: clamp(14px, 3vh, 44px); min-height: 0;
}
.hero-copy { max-width: 42ch; }

h1 {
  margin: 0 0 24px; font-size: clamp(36px, 4.6vw, 60px); line-height: 1.07; color: var(--ink);
}
h1 .line { display: block; }
.lede { margin: 0 0 30px; max-width: 44ch; font-size: clamp(16px, 1.15vw, 18px); color: var(--ink-soft); }

.hero-actions { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; }
.link-quiet { color: var(--ink-soft); text-decoration: none; font-size: 15px; border-bottom: 1px solid var(--line); padding-bottom: 2px; transition: color 200ms, border-color 200ms; }
.link-quiet:hover { color: var(--ink); border-color: var(--ink-soft); }

/* glass button: frosted only where something moves behind it */
.btn-glass {
  position: relative; display: inline-flex; align-items: center; gap: 10px;
  padding: 15px 26px; border: 0; border-radius: 999px;
  font: inherit; font-size: 16px; font-weight: 500; text-decoration: none; cursor: pointer;
  color: #14300D;
  background: linear-gradient(180deg, rgba(99,197,77,0.94), rgba(79,174,59,0.94));
  -webkit-backdrop-filter: blur(14px) saturate(150%); backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid rgba(255,255,255,0.5);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), inset 0 -1px 0 rgba(0,0,0,0.06), 0 8px 24px -10px rgba(79,174,59,0.6);
  transition: transform 200ms ease, box-shadow 200ms ease;
}
.btn-glass:hover { transform: translateY(-1px); box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 12px 30px -10px rgba(79,174,59,0.7); }
.btn-glass:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 3px; }
.btn-arrow { width: 16px; height: 16px; transition: transform 200ms ease; }
.btn-glass:hover .btn-arrow { transform: translateX(2px); }

/* ---------- hero artifact: the pressure network ----------
   Ported from landing/proto/network-c.html. Floats on the page: no frame, no card,
   no border, no panel. Its custom properties are named --sx-* rather than --ink and
   --green because this file already defines those as hex, and the artifact needs them
   as RGB triplets for rgba(). ---------- */
.sx{
  --sx-ink:35,33,34;
  --sx-green:99,197,77;
  width:100%;
  max-width:760px;
  margin-inline:auto;
  position:relative;
  /* deliberately no background, no border, no radius, no shadow */
}

/* Only while the hero is two columns. Once it stacks the artifact fills the width, and a
   negative margin would push it off the left edge and make the page scroll sideways. */
@media (min-width: 901px){
  .sx{ margin-left:-5.9%; margin-right:-3.8%; }
}

/* The prototype carried an exercise stamp above the diagram reading
   "EXERCISE ONLY, Ammonia release Bayford Processing, Run 2, 12 participants".
   Cut on the client's call: once the running sheet below is naming the incident and
   timestamping what has landed, the stamp is saying the same thing twice, and a line of
   tracked caps sitting above the diagram was the closest this got to a header bar. */

/* The diagram field. Canvas draws edges, travelling signals and the core ring.
   The nodes themselves are real DOM so their type is crisp and they are
   focusable and readable by assistive tech. */
.sx-stage{
  position:relative;
  width:100%;
  aspect-ratio:560/470;
  touch-action:pan-y;      /* a swipe over the artifact still scrolls the page */
  cursor:default;
}
.sx-stage canvas{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  display:block;
}
/* One scale transform for the whole node layer, so every node can be
   positioned in stable design-space coordinates. */
.sx-layer{
  position:absolute;
  top:0;left:0;
  width:560px;height:470px;
  transform-origin:0 0;
  pointer-events:none;
}

/* ---------- a channel node: a glyph and a quiet label. no box. ---------- */
.sx-node{
  position:absolute;
  top:0;left:0;
  width:106px;
  margin:0;padding:0;
  border:0;background:none;   /* explicitly not a control-looking control */
  border-radius:0;
  font:inherit;color:inherit;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:7px;
  pointer-events:auto;
  touch-action:none;          /* dragging a node must not scroll the page */
  cursor:grab;
  -webkit-tap-highlight-color:transparent;
  will-change:transform;
}
.sx-node.is-drag{cursor:grabbing}
.sx-node:focus{outline:none}

.sx-node svg{
  width:27px;height:27px;
  display:block;
  overflow:visible;
  fill:none;
  stroke:rgba(var(--sx-ink),.44);
  stroke-width:1.25;
  stroke-linecap:round;
  stroke-linejoin:round;
  transition:stroke .34s cubic-bezier(.22,.61,.36,1);
}
.sx-node .sig path{
  stroke-dasharray:40;
  stroke-dashoffset:0;
  transition:stroke .34s cubic-bezier(.22,.61,.36,1);
}

.sx-lab{
  position:relative;
  font-size:10px;
  font-weight:500;
  letter-spacing:.15em;
  text-transform:uppercase;
  color:rgba(var(--sx-ink),.42);
  white-space:nowrap;
  transition:color .34s cubic-bezier(.22,.61,.36,1);
}
/* Focus indicator: a charcoal rule under the label. High contrast, and not a
   box drawn around anything. */
.sx-lab::after{
  content:"";
  position:absolute;
  left:50%;right:50%;
  bottom:-6px;
  height:1.5px;
  background:#232122;
  transition:left .3s cubic-bezier(.22,.61,.36,1),right .3s cubic-bezier(.22,.61,.36,1);
}
.sx-node:focus-visible .sx-lab::after{left:0;right:0}

/* Live state: this channel's own traffic comes forward. */
.sx-node.is-live svg{stroke:rgba(var(--sx-ink),.9)}
.sx-node.is-live .sx-lab{color:rgba(var(--sx-ink),.95)}
.sx-node.is-live .sig path{
  stroke:rgba(var(--sx-green),.95);
  animation:sx-draw 1.25s cubic-bezier(.4,0,.25,1) infinite;
}
.sx-node.is-live .sig path:nth-child(2){animation-delay:.14s}
.sx-node.is-live .sig path:nth-child(3){animation-delay:.28s}
.sx-node.is-live .sig path:nth-child(4){animation-delay:.42s}
@keyframes sx-draw{
  0%{stroke-dashoffset:40;opacity:.18}
  22%{opacity:1}
  58%{stroke-dashoffset:0;opacity:1}
  100%{stroke-dashoffset:0;opacity:1}
}
/* Everything that is not the live channel recedes. */
.sx-layer.has-live .sx-node:not(.is-live) svg{stroke:rgba(var(--sx-ink),.2)}
.sx-layer.has-live .sx-node:not(.is-live) .sx-lab{color:rgba(var(--sx-ink),.22)}

/* ---------- the core: the organisation under pressure ---------- */
.sx-core{
  position:absolute;
  top:0;left:0;
  width:132px;
  margin:0;padding:0;
  border:0;background:none;border-radius:0;
  font:inherit;color:inherit;
  display:flex;flex-direction:column;align-items:center;gap:5px;
  pointer-events:auto;
  touch-action:none;
  cursor:default;
  -webkit-tap-highlight-color:transparent;
}
.sx-core:focus{outline:none}
.sx-core-name{
  font-family:'Newsreader','Iowan Old Style','Palatino Linotype',Palatino,Georgia,serif;
  font-weight:300;
  font-size:17px;
  line-height:1;
  letter-spacing:-.005em;
  color:rgba(var(--sx-ink),.92);
}
.sx-core-clock{
  font-size:9.5px;
  font-weight:500;
  letter-spacing:.17em;
  color:rgba(var(--sx-ink),.4);
  font-variant-numeric:tabular-nums;
  transition:color .34s cubic-bezier(.22,.61,.36,1);
}
.sx-core.is-live .sx-core-clock{color:rgba(var(--sx-ink),.72)}
.sx-core:focus-visible .sx-core-clock{
  color:rgba(var(--sx-ink),.95);
  box-shadow:0 1.5px 0 0 #232122;
}
/* The core stops being a noun and becomes a readout. Time since the last
   statement is the number crisis comms people are actually judged on, and
   there has not been a statement, so the count of things nobody has answered
   is the honest version of it. The word does the work: no chip, no pill, no
   traffic light, no colour. Ink alone rises with the number.
   Sized against the ring, not by eye. The longest state this can reach is
   "99 UNANSWERED": at 8.5px and .1em that sets about 81px wide, and the third
   line of the core stack sits about 24.6px below centre, where the ring's own
   inner width is about 99px at rest. Roughly 9px of white each side, and the
   ring only ever swells outward from there. */
.sx-core-state{
  font-size:8.5px;
  font-weight:500;
  letter-spacing:.1em;
  white-space:nowrap;
  color:rgba(var(--sx-ink),.78);
  font-variant-numeric:tabular-nums;
}

/* ---------- the caption line: one fixed typographic anchor ---------- */
/* Sits under the running sheet, and far enough under it to change register.
   At 6px the 15px italic read as a fourth row of the sheet; at 15px it reads
   as the sentence that closes the block. */
.sx-cap{
  margin-top:15px;
  min-height:44px;
  padding-left:17%;
  padding-right:3.8%;
}
.sx-cap-line{
  font-family:'Newsreader','Iowan Old Style','Palatino Linotype',Palatino,Georgia,serif;
  font-size:15px;
  font-style:italic;
  font-weight:300;
  line-height:1.45;
  color:rgba(var(--sx-ink),.72);
  transition:opacity .22s ease;
  min-height:1.45em;
}
.sx-cap-hint{
  margin-top:7px;
  font-size:10px;
  font-weight:500;
  letter-spacing:.13em;
  text-transform:uppercase;
  color:rgba(var(--sx-ink),.3);
  transition:opacity .6s ease;
}
.sx-cap-hint.is-gone{opacity:0}

/* ---------- the running sheet ----------
   Not a feed, not a log window, not a notification stack. A printed running
   sheet: entry number, time, what came in. It has been going since before you
   arrived, which is why it starts at 44 rather than 1.

   Two decisions keep it out of feed territory. First, nothing slides: the
   three slots never move, only the words in them change, so there is no
   scroll direction and no clipped edge. Second, ink is fixed by slot, so an
   entry visibly ages as it is pushed up rather than being deleted. The eye
   reads a record being kept, not items arriving. */
.sx-sheet{
  /* Pulled up too: the lowest node bottoms out at 427 of 470 design units, so the stage
     carries an empty band underneath it that read as a gap between diagram and record. */
  margin-top:-5%;
  padding-left:17%;
  padding-right:3.8%;
}
.sx-row{
  --o:1;
  opacity:var(--o);
  padding:2.5px 0;
  transition:opacity .34s cubic-bezier(.22,.61,.36,1);
}
.sx-row.r0{--o:.78}
.sx-row.r1{--o:.86}
.sx-row.r2{--o:1}
/* The animated wrapper is inside the row, so the swap dip multiplies with the
   slot's ink instead of fighting it. */
.sx-row .in{
  display:flex;
  gap:12px;
  align-items:baseline;
}
/* Fixed column widths, not auto. Each row is its own flex line, so content
   sizing would never line the columns up between rows. Both columns are
   tabular figures of a known digit count, so a fixed width is exact. */
.sx-row .n{
  flex:0 0 17px;
  text-align:right;
  font-size:9px;
  letter-spacing:.04em;
  color:rgba(var(--sx-ink),.66);
  font-variant-numeric:tabular-nums;
}
.sx-row .t{
  flex:0 0 47px;
  font-size:9.5px;
  font-weight:500;
  letter-spacing:.09em;
  color:rgba(var(--sx-ink),.82);
  font-variant-numeric:tabular-nums;
}
.sx-row .x{
  flex:1 1 auto;
  min-width:0;
  font-size:12.5px;
  line-height:1.45;
  color:rgba(var(--sx-ink),.84);
  white-space:nowrap;
  overflow:hidden;
}
/* The source tag is part of the entry, not a column label. It is ragged on
   purpose: Twitcher has none, because a bare lowercase line with no source is
   already unmistakably a post. */
.sx-row .p{
  font-family:'Inter',system-ui,-apple-system,'Segoe UI',Helvetica,Arial,sans-serif;
  font-style:normal;
  font-size:9.5px;
  font-weight:500;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(var(--sx-ink),.44);
  margin-right:8px;
}
/* Only the newsroom gets the serif, because only the newsroom is published
   copy. That makes the face a fact about the source rather than decoration. */
.sx-row .x.ed{
  font-family:'Newsreader','Iowan Old Style','Palatino Linotype',Palatino,Georgia,'Times New Roman',serif;
  font-style:italic;
  font-size:13.5px;
}
/* Hovering a channel does not rewrite the sheet. It shows you which of the
   lines already on it came from that channel. */
.sx-sheet.has-live .sx-row{--o:.4}
.sx-sheet.has-live .sx-row.is-live{--o:1}

@keyframes sx-swap{
  0%{opacity:1}
  34%{opacity:0}
  100%{opacity:1}
}
.sx-row .in.sw{animation:sx-swap .52s cubic-bezier(.4,0,.25,1)}
.sx-row.r1 .in.sw{animation-delay:.04s}
.sx-row.r2 .in.sw{animation-delay:.07s}

/* Small screens: the labels come off and the diagram becomes glyphs. The
   caption names whatever you touch, so nothing is lost. */
@media (max-width:520px){
  .sx-lab{opacity:0}
  .sx-cap-line{font-size:14px}
}

/* Below about 660px the entry no longer fits on one line beside its columns,
   so the sheet takes its second form: the time sits over the entry, the entry
   gets a fixed two-line box so the block height never changes as lines swap,
   and the oldest slot drops out to keep the whole thing short. */
@media (max-width:660px){
  .sx-row .in{flex-wrap:wrap}
  .sx-row .n{display:none}
  .sx-row .x{
    flex:1 0 100%;
    white-space:normal;
    height:2.9em;
    font-size:12px;
    margin-top:1px;
  }
  .sx-row .x.ed{font-size:12.5px}
  .sx-row.r0{display:none}
}

.sx-sr{
  position:absolute;width:1px;height:1px;
  margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);
  white-space:nowrap;border:0;
}

@media (prefers-reduced-motion:reduce){
  .sx-node.is-live .sig path{animation:none;stroke-dashoffset:0;opacity:1}
  .sx-row .in.sw{animation:none}
}


.call-label { font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--green-deep); font-weight: 600; }
.call-who { font-size: 16px; font-weight: 500; }

/* =========================== WHAT SIMX IS =========================== */

/* Deliberately the plainest thing on the page. It follows an artifact that moves and comes
   before a walkthrough that moves, so its whole job is to stop for a moment and say what
   this is. No card, no rule, no columns, no accompanying graphic. A measure of type on the
   same white the hero sits on, so the two read as one continuous opening rather than two
   sections that happen to touch. */
/* It had 10px of padding on top against 60 to 100 everywhere else, which is not clean, it
   is cramped. The section above it is the trust strip and the one below is the pinned
   workstation, so it needs air on both sides or it reads as something wedged between two
   larger things. */
.statement { position: relative; z-index: 1; padding: clamp(40px, 5vw, 68px) 0 clamp(48px, 6vw, 76px); }
/* Every other section on the page is 1120. At 900 this one sat as a narrower slab inside a
   wider page and read as belonging to a different grid. The measure is now controlled on
   the paragraphs themselves, which is where it belongs, rather than by squeezing the
   column they sit in. */
/* Centred, and the container IS the measure. A 67-character column left-aligned inside a
   1120px page reads as something that got cut off; the same column centred reads as a
   deliberate statement. The calc adds the wrap padding back so the text keeps its
   measure instead of the padding eating into it. */
.statement .wrap { max-width: calc(34em + 2 * clamp(20px, 4vw, 56px)); }
/* No cap of its own now that the column is the cap; a second constraint inside a centred
   column just makes the heading narrower than the text under it. */
.statement h2 { margin-bottom: 28px; }
.statement-lede {
  font-size: clamp(17px, 1.25vw, 19px);
  line-height: 1.68;
  color: var(--ink-soft);
  /* Measure set in em, not ch, and the difference is not pedantry. ch is the width of a
     ZERO, and Inter's zero is much wider than its average letter: 76ch measured 85 to 89
     characters a line and 62ch still measured 81. An em tracks the font size, so this holds
     at 68 characters whether the clamp resolves to 17px or 19px. Comfortable is 60 to 75,
     and long measure was most of why this section read as a wall. It was never the writing. */
  max-width: 32em;
  margin-bottom: 22px;
}
/* The last paragraph is the assessment, which is the product rather than the setup, so it
   comes forward to full ink and closes the section. */
.statement-lede:last-child { margin-bottom: 0; color: var(--ink); }
/* The positioning paragraph is the argument the section exists to make, so it carries full
   ink even though a quieter note now follows it and takes the last-child rule away. */
.statement-lede:first-of-type { color: var(--ink); }
/* The practicalities: what kind of crisis, where my people sit, whether anything escapes,
   who you are. Deliberately set below the argument rather than beside it. These are answers
   to objections, and an objection answered at the same weight as the argument reads like the
   page expects to be doubted. */
.statement-note {
  font-size: clamp(15px, 1.05vw, 16.5px);
  line-height: 1.62;
  color: var(--ink-soft);
  max-width: 32em;
  margin: 26px 0 0;
}

/* =========================== WHAT SIMX DOES ===========================
   No boxes, no numbers, no step markers, no hints. Type on white with three lines
   running through the whole strip. The only thing that says where you are is that the
   beat nearest the middle is at full ink and the rest recede. */
.flow{position:relative}

.flow-pin{
  position:sticky;top:0;
  height:88vh;
  overflow:hidden;
}

/* Canvas draws the lines. It is pinned to the frame and paints in strip
   coordinates offset by the pan, which is what keeps the curves continuous
   instead of restarting per screen. */
.flow-canvas{
  position:absolute;inset:0;
  width:100%;height:100%;
  display:block;
  pointer-events:none;
}

/* The strip. Wider than the viewport by a long way; translated on X. */
.flow-track{
  position:absolute;top:0;left:0;height:100%;
  will-change:transform;
}

/* One feature. Absolutely placed along the strip, vertically staggered so the
   lines have a reason to rise and fall between them and the eye has to travel
   rather than read a row. */
.beat{
  position:absolute;
  width:390px;
  opacity:.12;
  transition:opacity 620ms cubic-bezier(.22,.61,.36,1);
}
.beat.near{opacity:1}

.beat h3{
  font-family:'Newsreader','Iowan Old Style','Palatino Linotype',Palatino,Georgia,serif;
  font-weight:300;
  font-size:clamp(26px,2.5vw,34px);
  line-height:1.14;
  letter-spacing:-.014em;
  margin:0 0 14px;
  color:#232122;
}
.beat p{
  margin:0;
  font-size:15.5px;
  line-height:1.62;
  /* #6A6668 on white is 5.66:1. */
  color:#6A6668;
}

/* The closing beat is the assessment, which is the product rather than another
   capability, so it is the one that comes forward. */
.beat.last h3{color:#232122}
.beat.last p{color:#3F3D3E}

.flow-sr{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* ---------- reduced motion, and the no-script case ----------
   Nothing pans, nothing draws itself. The strip becomes what it always was
   underneath: six pieces of writing, in order, fully legible. */
@media (prefers-reduced-motion:reduce){
  .flow-pin{position:static;height:auto;overflow:visible}
  .flow-canvas{display:none}
  .flow-track{position:static;transform:none!important;height:auto;width:100%!important;padding:0 clamp(20px,5vw,56px)}
  .beat{position:static;opacity:1;width:100%;max-width:52ch;margin:0 0 clamp(34px,5vh,58px)}
}
.no-js .flow-pin{position:static;height:auto;overflow:visible}
.no-js .flow-canvas{display:none}
.no-js .flow-track{position:static;transform:none;height:auto;width:100%;padding:0 clamp(20px,5vw,56px)}
.no-js .beat{position:static;opacity:1;width:100%;max-width:52ch;margin:0 0 44px}

@media (prefers-reduced-motion:reduce){
  .flow-pin{position:static;height:auto;overflow:visible}
  .flow-canvas{display:none}
  .flow-track{position:static;transform:none!important;height:auto;width:100%!important;padding:0 clamp(20px,5vw,56px)}
  .beat{position:static;opacity:1;width:100%;max-width:52ch;margin:0 0 clamp(34px,5vh,58px)}
}
@media (max-width:820px){
  .flow-pin{position:static;height:auto;overflow:visible}
  .flow-canvas{display:none}
  .flow-track{position:static;transform:none!important;height:auto;width:100%!important;padding:0 clamp(20px,5vw,40px)}
  .beat{position:static;opacity:1;width:100%;max-width:52ch;margin:0 0 40px}
}

/* No top padding, because the ribbon band below is flush to the top edge of the section.
   The space it used to provide is now the band's own bottom margin. */
.inside { background: var(--night-2); color: var(--night-ink); padding: 0 0 clamp(48px, 6vw, 76px); }

/* The ribbon. Full bleed, so it is a child of the section rather than of .wrap.

   The clip's own black floor measures about 8/255 while the section is #1E2124, so the
   bottom edge would otherwise be a visible line where one black meets a lighter one. The
   gradient dissolves the band into the section instead. The TOP edge is left hard, because
   there the dark meets the white page above and a clean line is the right answer. */
.inside-ribbon {
  position: relative;
  height: clamp(148px, 19vw, 264px);
  margin-bottom: clamp(40px, 5vw, 68px);
  overflow: hidden;
  background: #000;
}
.inside-ribbon video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.inside-ribbon::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to bottom, transparent 52%, var(--night-2));
}
.inside h2 { color: var(--night-ink); }
.inside .section-lede { color: var(--night-soft); }
.dark-eyebrow { color: var(--green-dim); }
.inside-frame {
  position: relative; margin-top: 32px; aspect-ratio: 16 / 9;
  border: 1px solid var(--night-line); border-radius: 14px; overflow: hidden; background: var(--night);
}
@supports not (aspect-ratio: 16 / 9) { .inside-frame { height: 0; padding-bottom: 56.25%; } }
.inside-frame img, .inside-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; display: block; }
.inside-empty { position: absolute; inset: 0; display: none; flex-direction: column; align-items: center; justify-content: center; gap: 6px; color: var(--night-soft); }
.inside-frame.empty .inside-empty { display: flex; }
.inside-empty p { margin: 0; }
.inside-empty-note { font-size: 13px; }

/* =========================== LIGHT SECTIONS =========================== */

.section { padding: clamp(48px, 6vw, 76px) 0; }
.section-tint { background: var(--tint); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }

/* pricing */
.prices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin: 34px 0 22px; }
.price { padding: 26px 24px; border: 1px solid var(--line); border-radius: 14px; background: var(--paper); }
.price.feature { border-color: rgba(99,197,77,0.55); box-shadow: 0 20px 50px -35px rgba(79,174,59,0.55); }
.price-n { font-family: Newsreader, Georgia, serif; font-size: 40px; line-height: 1; margin: 0; }
.price-l { margin: 2px 0 16px; font-size: 14px; color: var(--ink-soft); }
.price-v { margin: 0; font-size: 22px; font-weight: 500; }
.price-note { margin: 10px 0 0; font-size: 13px; color: var(--green-deep); }

/* quote */
.quote { margin: 34px 0 0; padding: 0 0 0 22px; border-left: 2px solid var(--green); max-width: 60ch; }
.quote p { font-family: Newsreader, Georgia, serif; font-size: clamp(19px, 1.8vw, 24px); line-height: 1.45; margin: 0 0 12px; }
.quote footer { font-size: 14px; color: var(--ink-soft); }

/* form */
.form { margin-top: 30px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field { margin-bottom: 18px; }
.field label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
.field input, .field textarea {
  width: 100%; padding: 12px 14px; font: inherit; font-size: 16px; color: var(--ink);
  background: var(--paper); border: 1px solid #CFCBC9; border-radius: 8px;
}
.field input:focus, .field textarea:focus { outline: 2px solid var(--green-deep); outline-offset: 1px; border-color: var(--green-deep); }
.field textarea { resize: vertical; }
.hidden-field { position: absolute; left: -9999px; }

/* footer */
.site-footer { border-top: 1px solid var(--line); padding: 34px 0; }
.footer-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.footer-logo { height: 24px; width: auto; display: block; }
.footer-inner p { margin: 0; }

/* =========================== RESPONSIVE =========================== */

@media (max-width: 980px) {
  /* Still a revolver, just stacked: the stage above, the chambers turning below it. */
  /* No room for 1:1 on a phone, so scale is unavoidable here; give it as much height as we can. */
  .sim-host { height: 46vh; }
  .stage-panel { top: 0; transform: none; position: relative; }
  .stage-panel:not(.active) { position: absolute; }
  .stage-panel.active { transform: none; }
  .feat { padding: 14px 0; }
  .deck { height: 180px; }
}
@media (max-width: 900px) {
  .hero { min-height: 0; padding-bottom: 44px; }
  .hero-grid { grid-template-columns: 1fr; gap: 40px; padding-top: 40px; }
  .hero-copy { max-width: none; }
  .prices { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .deck { height: 240px; }
}

/* =========================== REDUCED MOTION ===========================
   Show every end state rather than hiding content behind an animation that will never run. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Nothing dims and nothing slides; the active panel still swaps, it just does so instantly. */
  .feat { opacity: 1 !important; transition: none !important; }
  .stage-panel { transition: none !important; }
  .pip, .live-dot, .dots i, .wave i, .caret { animation: none !important; }
  .item, .chip, .msg.in, .people li, .findings li, .seal { opacity: 1 !important; transform: none !important; animation: none !important; }
  .prompt-text { width: auto !important; animation: none !important; white-space: normal; }
  .genbar span { width: 100% !important; animation: none !important; }
  .typing { display: none; }
  .card { position: relative; opacity: 1 !important; transform: none !important; animation: none !important; margin-bottom: 10px; }
  .deck { height: auto; }
  .reveal { opacity: 1; transform: none; }
}

/* =========================== THE WORKSTATION ===========================
   Lifted from landing/proto/features-a.html. Every selector here is wk-* prefixed, so
   it cannot collide with anything above. The platform UI the screens display lives in
   workstation.css. */


/* ==========================================================================
   0.  PAGE
   Nothing here is dark. The whole landing page is white and this section is
   part of it, so the only tonal moves available are hairlines, ink weights and
   one scarce green. Colours are declared once and never re-typed, because the
   last three prototypes failed on contrast by drifting a hex value.

   Measured against white (#FFFFFF), sRGB, WCAG 2.x:
     --ink   #232122  16.04:1   all headings and body copy
     --ink2  #6A6668   5.66:1   secondary copy, ticks, inactive steps
     --grnT  #2E7D22   5.16:1   the only green allowed to carry text
     --grn   #63C54D   2.18:1   marks and rules only, never a letterform
     --grnD  #4FAE3B   2.82:1   marks and rules only, never a letterform
   ========================================================================== */
:root{
  --ink:#232122;
  --ink2:#6A6668;
  --hair:#E7E5E4;
  --grn:#63C54D;
  --grnD:#4FAE3B;
  --grnT:#2E7D22;
  --paper:#FFFFFF;

  /* Named in the stack only. Neither family is ever fetched, so every fallback
     after the first name is the one that will actually be used on most
     machines, and both lists are ordered so that happens gracefully. */
  --serif:'Newsreader','Iowan Old Style','Palatino Linotype',Palatino,'Book Antiqua',Georgia,'Times New Roman',serif;
  --sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
}

*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; }
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.62;
  -webkit-font-smoothing:antialiased;
  /* The devices deliberately run past the left and right edges of the viewport.
     That crop is the composition, so the page must never offer a sideways
     scrollbar to go and look at what is off it. */
  overflow-x:hidden;
}

/* Focus is visible everywhere, including on the transport controls inside the
   section, because the whole sequence is operable from the keyboard. */
:focus-visible{ outline:2px solid var(--ink); outline-offset:3px; border-radius:2px; }

.sr{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ==========================================================================
   2.  THE WORKSTATION
   Two screens, angled slightly, cropped by the edges of the viewport, with the
   heading between them. The client's reference does exactly this; what it does
   not do is put anything real on the screens, and that is the whole job here.
   ========================================================================== */
/* Tall only to buy scroll. The frame inside is pinned, so this height is spent on the
   opening line and the screens arriving rather than on the page moving. */
.wk{ height:250vh; }
.wk-pin{
  position:sticky; top:0;
  height:100vh;
  display:flex; align-items:center;
  overflow:hidden;
}
/* Pinning is a desktop idea. Below the stacking breakpoint it goes back to being a
   normal block that scrolls past, with the line simply present above it. */
@media (max-width:700px){
  .wk{ height:auto; padding:clamp(28px,4vw,56px) 0 clamp(32px,4.5vw,64px); }
  .wk-pin{ position:static; height:auto; display:block; overflow:visible; }
}
@media (prefers-reduced-motion:reduce){
  .wk{ height:auto; padding:clamp(28px,4vw,56px) 0; }
  .wk-pin{ position:static; height:auto; display:block; overflow:visible; }
}

/* The rig is full-bleed on purpose. The side columns start at x=0 and x=100vw,
   so a device that overhangs its own column by 28% is guaranteed to be cut by
   the viewport rather than by a container edge, at every width, without a
   single media query doing the cropping. */
.wk-rig{
  position:relative;
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:clamp(16px,2vw,34px);
  align-items:center;
  perspective:820px;
  perspective-origin:50% 46%;
  /* The crop is the composition, so it is enforced here rather than left to the
     document. clip rather than hidden so the vertical axis stays visible and the
     devices keep their drop shadow; body's overflow-x:hidden is the fallback for
     engines that predate overflow:clip. */
  overflow-x:clip;
  overflow-y:visible;
}
.wk-side{ min-width:0; }

/* --------------------------------------------------------------------------
   THE FITTING PROBLEM, SOLVED ONCE.

   The extracted components are authored for a fixed-width platform window: the
   live Twitcher window is 800x550 and its 25/75 grid gets cramped below about
   700. Squeezing one into a small responsive box is what produced the
   "poorly formatted images" verdict last time, because at 380px the trend rail
   collapses, the tweet body reflows to four words a line, and the whole thing
   reads as broken software.

   So nothing here is squeezed. .wk-devi is a rigid 792px-wide drawing of a
   monitor whose screen is exactly 764x596, holding a 736px-wide app window: all
   three are fixed pixel values that never reflow. The only thing that responds
   is --s, a single scale factor written by JS from the measured column width.
   The component therefore always lays out at its design width and is then
   photographed larger or smaller, which is what a screen across a room actually
   does.

   Everything that would overflow is cropped by an edge that is real in the
   drawing: the window's own bottom edge, or the bezel, or the viewport. There
   is no crop through the middle of a sentence anywhere in this file.
   -------------------------------------------------------------------------- */
.wk-dev{
  --s:.78;
  /* 633 of monitor plus 22 of stand. The monitor is 5:4, which is a shape a
     real desktop display actually comes in, and the extra height over a 16:9
     panel is spent entirely on the feed: Twitcher's own chrome (logo bar, blue
     banner, stats row) takes a fixed 241px whatever the window is, so every
     pixel of window height above that is a pixel of readable feed. */
  /* Lid only. The 22px this used to reserve for a monitor foot is gone. */
  --devh:618;
  height:calc(var(--devh) * var(--s) * 1px);
  position:relative;
}
.wk-devi{
  width:964px;
  transform-origin:top left;
  transform:scale(var(--s));
}

/* The angle is 6 degrees, not 15. Enough that the pair reads as two objects on
   a desk rather than two flat rectangles, small enough that no glyph inside the
   real UI is resampled hard enough to soften. Each screen turns its outer edge
   away, so the part the viewport crops is the part that is already foreshortened
   and least legible, and the inner edge nearest the heading is the largest.

   The overhang is on the layout box, never on the 792px drawing: the drawing is
   the one thing in this section that is allowed no responsive behaviour at all.
   The right-hand seat is set 5.5% smaller and 26px lower so the pair reads as a
   lead and a supporting screen rather than as two equals shouting at once. */
.wk-side--l .wk-dev{ width:120%; margin-left:-20%; transform:translateX(calc(var(--out,0) * -1px)) translateZ(58px) rotateY(-26deg) rotateX(5deg); transform-origin:right center; }
.wk-side--r .wk-dev{ width:120%; margin-right:-20%; transform:translateX(calc(var(--out,0) * 1px)) translateY(26px) translateZ(58px) scale(.945) rotateY(26deg) rotateX(5deg); transform-origin:left center; }

.wk-bez{
  /* Even bezel with a slightly deeper chin. That proportion is what reads as a laptop
     lid rather than a picture frame, and it is the only thing below the screen now. */
  padding:12px 12px 18px;
  border:0;
  border-radius:17px;
  /* Brushed silver: light along the top edge, cooler through the middle, picking up a
     little bounce again at the bottom the way a curved aluminium lip does. */
  background:
    /* a soft sheen running across the lid, which is what gives it a surface */
    linear-gradient(76deg, rgba(255,255,255,.55) 0%, rgba(255,255,255,0) 26%,
                            rgba(255,255,255,.30) 52%, rgba(255,255,255,0) 74%),
    /* the tone curve. The narrow bright band at 48 to 56 percent is the specular
       highlight, and the speed it falls away on either side is what reads as metal */
    linear-gradient(172deg,
      #FFFFFF 0%, #F2F4F7 4%,
      #D3D7DE 14%, #BEC3CC 30%,
      #CBCFD7 44%, #F4F6F9 52%, #D6DAE1 60%,
      #B7BCC6 78%, #A9AEB9 92%, #C6CAD2 100%);
  box-shadow:
    /* the machined seam. Without it a silver lid on a white page has no edge at all */
    0 0 0 1px rgba(35,33,34,.16),
    0 1px 0 rgba(255,255,255,.95) inset,
    0 -1px 0 rgba(35,33,34,.10) inset,
    0 44px 74px -34px rgba(35,33,34,.40),
    0 6px 16px -8px rgba(35,33,34,.18);
}
/* The camera housing, hanging from the top bezel into the screen. Same black as the
   gasket, so it reads as part of the same moulding rather than a sticker on the glass. */
.wk-cam{
  position:absolute; left:var(--cam,32%); top:0; transform:translateX(-50%);
  width:178px; height:24px;
  background:#141416;
  border-radius:0 0 12px 12px;
  z-index:7; pointer-events:none;
}
/* Mirrored: each lid carries its camera toward its own outer edge. */
.wk-side--r .wk-cam{ --cam:68%; }

.wk-scr{
  /* 16:10, which is what a laptop panel actually is. Widened rather than shortened:
     the height is where the feed lives, and cropping that was the first complaint
     this section ever got. */
  width:940px; height:588px;
  position:relative;
  overflow:hidden;               /* the bezel is the crop, and it is a real edge */
  background:#E9E7E6;
  border-radius:6px;
  /* The thin black gasket between aluminium and glass. On a silver lid this is the only
     thing separating the screen from the frame, so it does the work the dark bezel used
     to do and stops light platform UI running straight into the page. */
  box-shadow:0 0 0 1px rgba(35,33,34,.55) inset, 0 0 0 3px rgba(35,33,34,.92);
}

/* ---- the SimX shell inside the screen ---------------------------------- */
.wk-wall{
  position:absolute; inset:0;
  background:#E9E7E6;
  display:flex; align-items:center; justify-content:center;
}
.wk-wall img{ width:132px; height:auto; opacity:.13; }
.wk-os{ position:absolute; inset:0; font-family:var(--sans); }


/* ---------- the live caret ----------
   Her cursor, sitting in the text where she is actually up to, with her name on a tag
   above it. Inline rather than absolutely positioned, so it moves with the words as they
   arrive and follows them round a line break without a single measurement. */
.wk-live{
  display:none; position:relative;
  width:2px; height:13px; vertical-align:-2px; margin-left:1px;
  background:#6B46C1;
}
.wk-live.on{ display:inline-block; }
.wk-live::after{
  content:attr(data-name);
  position:absolute; left:-1px; bottom:calc(100% + 3px);
  padding:2px 6px 3px;
  /* square on the bottom left, so it reads as flying from the caret rather than
     floating near it */
  border-radius:3px 3px 3px 0;
  background:#6B46C1; color:#fff;
  font:600 9.5px/1.2 var(--sans); letter-spacing:.03em;
  white-space:nowrap;
}


/* ---------- the opening line ----------
   Sits between the lids, types itself once, and leaves as they converge. Deliberately
   the only sentence in the section: it says what the two screens are showing before you
   have worked it out, and then stops taking up room. */
.wk-line{
  /* BELOW THE SCREENS, not between them. It sat dead centre, over the join, which worked
     while the two lids slid in around it. With them held centred and still, David asked on
     10 August for the sentence to sit underneath instead. top:100% puts it just past the
     rig's own box, so it follows the devices at every width without a media query. */
  position:absolute; left:50%; top:calc(100% + clamp(10px,2.2vh,30px));
  transform:translateX(-50%);
  z-index:8; pointer-events:none;
  /* Sized for the sentence that is actually in it. The previous measure was cut for "Work
     cooperatively throughout the crisis simulation", 51 characters over two lines. David's
     replacement is 89, and at 19ch that became six ragged lines stacked between the screens. */
  width:min(46ch,78vw); text-align:center;
  font-family:var(--serif); font-weight:300;
  font-size:clamp(30px,3.3vw,46px);
  line-height:1.08;
  letter-spacing:-.022em;
  color:#232122;
  opacity:0;
  /* It sits over two lit screens, so it needs to be lifted off them rather than
     competing at the same depth. A wide soft white halo, no edge, no box. */
  text-shadow:0 0 26px #fff, 0 0 14px #fff, 0 0 6px rgba(255,255,255,.9);
}

/* A SHORT SCREEN IS AS REAL A CONSTRAINT AS A NARROW ONE, and until now only width was
   being answered.

   The rig is sized from the width it is given, so a laptop that is wide and short - 1366
   by 660 with the taskbar taken off, which is what Lachy is on - makes the screens tall
   enough that the sentence underneath them lands past the fold. .wk-pin is exactly 100vh
   with overflow:hidden, so past the fold means clipped, and what you see is the line
   stopping mid-word: "...critical information that needs to be". It reads as text running
   under the next section. It is the pin cutting it off, and it happens at every scroll
   position rather than at a particular one.

   Measured at 1366x660: rig 509 tall starting at y=75, line 97 tall, total 696 against a
   660 viewport, 36px over. Both are pulled in here rather than only one, because shrinking
   the type alone leaves the screens dominating a short window and shrinking only the rig
   leaves 45px type on a 660px screen.

   Keyed on height with a width floor so it cannot reach the phone layout, which stacks
   below 700px and has no pin to overflow. */
@media (min-width:701px) and (max-height:780px){
  .wk-rig{ max-width:88%; margin-left:auto; margin-right:auto; }
  .wk-line{ font-size:clamp(22px,2.5vw,34px); }
}
/* The caret is a stroke of the face, not a marker: same ink as the words, the weight of
   a stem, sitting on the baseline the type sits on. */
.wk-line i{
  display:inline-block; width:.055em; height:.72em;
  vertical-align:-.04em; margin-left:.07em;
  background:#232122;
  animation:wk-blink 1.06s step-end infinite;
}
@keyframes wk-blink{ 50%{ opacity:0; } }
@media (prefers-reduced-motion:reduce){ .wk-line i{ animation:none; } }
.wk-line.done i{ display:none; }
/* THE GHOST TAIL. The part of the sentence that has not been typed yet, kept in the box and
   only made invisible. visibility:hidden still takes part in line breaking, so the line box
   is its finished size from the very first frame: the sentence does not grow its own
   container as it writes, which on the desktop stopped it creeping upward as it re-centred,
   and on a phone stops it shoving the screen below it down the page mid-read. */
.wk-line .ghost,
.gn-line .ghost{ visibility:hidden; }
@media (prefers-reduced-motion:reduce){
  /* No typing and no fading out: it is simply there, above the screens. */
  .wk-line{ opacity:1!important; }
  .wk-line i{ display:none; }
}


/* ---------- the incoming call ----------
   Sits on the second seat, over its desktop, the way a system call panel does. It is
   the only thing in either screen that is not part of an application window, which is
   the point: the crisis reaches this person through the machine rather than through
   the app they were looking at. */
.wk-call{
  position:absolute; right:14px; top:52px; width:236px;
  padding:13px 14px 12px;
  border-radius:12px;
  background:rgba(28,29,32,.90);
  -webkit-backdrop-filter:blur(14px) saturate(150%); backdrop-filter:blur(14px) saturate(150%);
  box-shadow:0 18px 40px -18px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.09) inset;
  color:#fff; font-family:var(--sans);
  z-index:9; pointer-events:none;
  opacity:0; transform:translateY(-12px) scale(.97);
  transition:opacity .34s cubic-bezier(.22,.61,.36,1), transform .34s cubic-bezier(.22,.61,.36,1);
}
.wk-call.on{ opacity:1; transform:none; }

.wk-call-top{ display:flex; align-items:center; gap:10px; }
.wk-call-av{
  width:34px; height:34px; border-radius:50%; flex:none;
  background:#4A5568; color:#fff;
  display:grid; place-items:center;
  font:600 13px/1 var(--sans);
}
.wk-call-who{ min-width:0; }
.wk-call-name{ font:600 13px/1.25 var(--sans); }
.wk-call-org{ font:400 11px/1.3 var(--sans); color:rgba(255,255,255,.62); }
.wk-call-state{
  margin:9px 0 0; font:500 10.5px/1.3 var(--sans);
  letter-spacing:.11em; text-transform:uppercase; color:rgba(255,255,255,.66);
}
/* Answered: the label goes green and starts counting, which is the only change. No
   waveform, no bouncing bars. */
.wk-call.live .wk-call-state{ color:#8FD97E; }

.wk-call-btns{ display:flex; gap:8px; margin-top:11px; }
.wk-call-btns i{
  flex:1 1 0; height:26px; border-radius:7px;
  display:grid; place-items:center;
  font:600 10.5px/1 var(--sans); letter-spacing:.06em;
  font-style:normal;
}
/* White on this grey is 8.1:1; white on the green is 3.6:1, which clears the 3:1 that
   applies to text this size and weight. */
.wk-call-btns .dec{ background:rgba(255,255,255,.15); color:rgba(255,255,255,.92); }
.wk-call-btns .acc{ background:#2FA84F; color:#fff; }
/* Once it is answered there is nothing left to accept. */
.wk-call.live .wk-call-btns{ display:none; }

/* ---------- the dock ----------
   Sits on the glass, along the bottom, the way it does on the machines these people
   actually use. Translucent over the wallpaper rather than a solid bar, because a
   solid bar reads as part of the app rather than part of the desktop. */
.wk-dock{
  position:absolute; left:50%; bottom:9px; transform:translateX(-50%);
  display:flex; align-items:flex-end; gap:5px;
  padding:5px 7px;
  border-radius:9px;
  background:rgba(255,255,255,.42);
  -webkit-backdrop-filter:blur(9px) saturate(150%); backdrop-filter:blur(9px) saturate(150%);
  box-shadow:0 0 0 1px rgba(35,33,34,.10), 0 6px 18px -8px rgba(35,33,34,.35),
             0 1px 0 rgba(255,255,255,.7) inset;
  z-index:6; pointer-events:none;
}
.wk-dock i{
  display:block; width:19px; height:19px; border-radius:5px;
  background:rgba(35,33,34,.34);
  box-shadow:0 0 0 .5px rgba(255,255,255,.35) inset;
}
/* The running app gets the dot underneath, same as a real dock. */
.wk-dock i.run{ position:relative; }
.wk-dock i.run::after{
  content:""; position:absolute; left:50%; bottom:-4px; margin-left:-1px;
  width:2px; height:2px; border-radius:50%; background:rgba(35,33,34,.45);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  .wk-dock{ background:rgba(255,255,255,.82); }
}
.wk-osbar{
  height:32px; display:flex; align-items:center; gap:2px;
  padding:0 10px; background:#F3F2F1;
  border-bottom:1px solid #DAD6D4;
  position:relative; z-index:4;
}
.wk-osmark{ height:13px; width:auto; display:block; margin-right:12px; opacity:.92; }
.wk-tab{
  font:500 11px/1 var(--sans); letter-spacing:.005em;
  color:var(--ink2); background:none; border:0; border-radius:3px;
  padding:6px 9px; cursor:default; white-space:nowrap;
}
.wk-tab.on{ color:var(--ink); background:#FFFFFF; box-shadow:0 0 0 1px #DAD6D4; }
.wk-osright{
  margin-left:auto; display:flex; align-items:center; gap:10px;
  font:400 11px/1 var(--sans); color:var(--ink2); white-space:nowrap;
}
.wk-osright b{ color:var(--ink); font-weight:500; }

/* ---- the app window ----------------------------------------------------- */
.wk-win{
  position:absolute; left:102px; top:44px; width:736px; height:474px;
  background:#fff;
  border:1px solid #C9C5C2;
  border-radius:4px;
  box-shadow:0 10px 26px -14px rgba(35,33,34,.34);
  display:flex; flex-direction:column;
  z-index:3;
}
.wk-winbar{
  height:26px; flex:none; display:flex; align-items:center; gap:8px;
  padding:0 9px; background:#F1EFEE; border-bottom:1px solid #D8D4D2;
  border-radius:3px 3px 0 0;
  font:600 11px/1 var(--sans); color:var(--ink); letter-spacing:.01em;
}
.wk-winbtns{ margin-left:auto; display:flex; gap:9px; }
.wk-winbtns i{ display:block; width:9px; height:9px; border:1px solid #A8A3A0; border-radius:1px; }
.wk-winbtns i.m{ border:0; border-bottom:1px solid #A8A3A0; height:5px; border-radius:0; }
.wk-winbody{ flex:1 1 auto; overflow:hidden; position:relative; }

/* The component sits at its natural 734px inside a window that is exactly that
   wide, so the platform's own grid is never asked to reflow. Vertical overflow
   is cropped by the window body, which is the window's real bottom edge, and
   the feed is scrolled inside it by transform the way the platform scrolls it
   with overflow-y. */
.wk-comp{ width:734px; padding-bottom:34px; }

/* ---------- the regulator's email ----------
   David asked for an email from a regulator to come onto the screen, and flow card 03 has
   promised "journalists, residents and regulators" since the page was written while no regulator
   has ever appeared on it.

   It is a SECOND WINDOW rather than a third monitor or a tile. Mail does not arrive in its own
   piece of furniture; it lands on top of whatever you were already doing, and a window opening
   over FaceSpace says that in a way another panel never could. It also costs the composition
   nothing: the two-screen shot is the page's signature and this does not touch it.

   Offset down and right of the window beneath, far enough that the one underneath is still
   legible as a separate thing. Overflowing the right edge of the screen is deliberate and is how
   everything else in this drawing behaves: the crop is the bezel, which is a real edge. */
.wk-win-mail{
  left:150px; top:96px; width:700px; height:426px;
  z-index:4;
  /* Hidden rather than absent, so it costs nothing to show and the layout never reflows when it
     arrives. visibility as well as opacity, or it would still take pointer events and still be
     read out by a screen reader while invisible. */
  opacity:0; visibility:hidden;
  transform:translateY(12px) scale(.982);
  transform-origin:50% 0;
  transition:opacity .34s var(--ease), transform .34s var(--ease);
}
.wk-win-mail.on{ opacity:1; visibility:visible; transform:none; }
/* 698 against the window's 700, the same two pixel inset .wk-comp has against its own window.
   The height is not decoration either: without it the reader lays out to its full 652px inside a
   426px window and is simply clipped, so it has nothing to scroll and the message below the fold
   can never be reached. Constrained, it behaves like the real client does, which is what makes
   reading it a thing that can be shown. 400 is the window's 426 less its 26px title bar. */
.wk-comp-mail{
  width:698px; padding-bottom:0;
  height:400px;
  display:flex; flex-direction:column;
}
.wk-comp-mail .email-window{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
.wk-comp-mail .window-content{ flex:1 1 auto; min-height:0; }
/* min-height:0 on every flex ancestor of the scroller, or the default min-height:auto lets the
   content push the box open again and the overflow never happens. */
.wk-comp-mail .window-content > [layout="row"]{ height:100%; min-height:0; }
.wk-comp-mail .email-reader{ overflow-y:auto; min-height:0; }
.wk-comp-mail #mail-list{ overflow-y:auto; min-height:0; }
@media (prefers-reduced-motion:reduce){
  .wk-win-mail{ transition:none; }
}
/* The last few pixels soften rather than ending on a hard edge. A feed always has more
   below it; a straight cut says the image was trimmed. */
.wk-winbody{
  -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 22px),transparent 100%);
  mask-image:linear-gradient(180deg,#000 calc(100% - 22px),transparent 100%);
}
.wk-scroll{ will-change:transform; }

/* ---- cursor, click ring, co-presence ------------------------------------ */
.wk-cur{
  position:absolute; left:0; top:0; width:19px; height:26px;
  z-index:9; pointer-events:none;
  transform:translate3d(-9999px,-9999px,0);
}
.wk-cur svg{ display:block; width:19px; height:26px; }
.wk-ring{
  position:absolute; width:34px; height:34px; margin:-17px 0 0 -17px;
  border:1.5px solid rgba(35,33,34,.55); border-radius:50%;
  z-index:8; pointer-events:none; opacity:0;
  transform:translate3d(-9999px,-9999px,0) scale(.35);
}

/* SimX's own co-presence strip. It is SimX chrome, not Twitcher chrome, so it
   is set in the page's typeface and the page's ink rather than pretending to be
   part of the platform it sits inside. The green square is 5px and it does not
   move; the sentence is doing the work. */
.wk-pres{
  /* Was a white notification strip with a border, which is the 'someone is typing' bar
     every chat app has. The reference does it better: the person's own colour marks the
     place in the document where they are working, and their name is on a small tag
     attached to it. You are not told that someone is typing, you can see where. */
  display:none; position:relative; align-items:baseline; gap:7px;
  margin:0; padding:10px 12px 10px 15px;
  font:500 12.5px/1.35 var(--sans);
}
.wk-pres.on{ display:flex; }

/* Their caret, in the margin, at their cursor. */
.wk-pres::before{
  content:""; position:absolute; left:0; top:6px; bottom:6px; width:2px;
  background:var(--who);
}
/* The name tag sitting on the caret. Square on the bottom left so it reads as attached
   to the caret rather than floating next to it. */
.wk-pres i{
  flex:none; font-style:normal;
  padding:2px 7px 3px; border-radius:3px 3px 3px 0;
  background:var(--who); color:#fff;
  font:600 10.5px/1.25 var(--sans); letter-spacing:.03em;
}
.wk-pres em{ font-style:normal; font-weight:400; color:var(--ink2); }
.wk-pres .dots{ display:inline-block; width:14px; }

/* Two people, two colours, and the difference between them is the point of the section.
   Amber is the public: an AI character out on Twitcher who does not work for you. Violet
   is your own side: a colleague at another desk in the same exercise. */
#tw-pres{ --who:#B45309; background:rgba(180,83,9,.06); color:#7C3A06; }
#fb-pres{ --who:#6B46C1; background:rgba(107,70,193,.06); color:#4C2E8E; margin:2px 0 6px; }

/* Anything the timeline reveals starts hidden and arrives with a short rise.
   No element is inserted or removed at runtime, so the sequence is a pure
   function of one number and seeking to any point is exact. */
.wk-rev{ display:none; }
.wk-rev.on{ display:block; }

/* ---- the centre column -------------------------------------------------- */
.wk-mid{ position:relative; z-index:6; text-align:left; }
.wk-eyebrow{
  font:500 11px/1 var(--sans); letter-spacing:.17em; text-transform:uppercase;
  color:var(--grnT); margin:0 0 16px;
}
.wk-mid h2{
  font-family:var(--serif); font-weight:300;
  font-size:clamp(28px,2.7vw,41px); line-height:1.1;
  letter-spacing:-.012em; margin:0 0 16px; color:var(--ink);
}
.wk-lede{ margin:0 0 26px; color:var(--ink2); font-size:16px; line-height:1.6; }

.wk-steps{ list-style:none; margin:0 0 18px; padding:0; border-top:1px solid var(--hair); }
.wk-steps li{ border-bottom:1px solid var(--hair); }
.wk-step{
  display:grid; grid-template-columns:26px 1fr; gap:10px; align-items:baseline;
  width:100%; text-align:left; background:none; border:0; cursor:pointer;
  padding:11px 0; font-family:var(--sans); color:var(--ink2);
  font-size:14.5px; line-height:1.45;
}
.wk-step .n{ font-size:11.5px; font-weight:600; letter-spacing:.06em; color:var(--ink2); }
.wk-step .t{ display:block; font-size:11.5px; color:var(--ink2); margin-top:3px; letter-spacing:.05em; }
li[data-on="1"] .wk-step{ color:var(--ink); }
li[data-on="1"] .wk-step .n{ color:var(--grnT); }
li[data-on="1"] .wk-step .t{ color:var(--ink2); }
.wk-step:hover{ color:var(--ink); }

.wk-bar{ height:1px; background:rgba(35,33,34,.13); position:relative; margin-bottom:18px; }
.wk-bar i{
  position:absolute; left:0; top:0; height:1px; width:0;
  background:rgba(35,33,34,.62);
}
.wk-transport{ display:flex; align-items:center; gap:16px; }
.wk-btn{
  font:500 12.5px/1 var(--sans); color:var(--ink);
  background:none; border:1px solid var(--hair); border-radius:2px;
  padding:8px 13px; cursor:pointer;
}
.wk-btn:hover{ border-color:#C9C5C2; }
.wk-note{ font-size:12.5px; color:var(--ink2); margin:0; }

.wk-seat{
  margin:16px 0 0; font:500 12px/1.4 var(--sans); color:var(--ink2);
  letter-spacing:.02em;
}
.wk-seat b{ color:var(--ink); font-weight:600; }
.wk-side--r .wk-seat{ margin-top:42px; }

/* ==========================================================================
   3.  THE INDEX
   The six features, whole, under the demonstration. Deliberately not cards and
   deliberately not a three-across grid of icons: numbers hanging in the margin,
   a serif heading, hairlines between. A contents page, which is the thing a
   buyer actually reads after they have watched the screens.
   ========================================================================== */
.wk-index{ margin-top:clamp(64px,8vw,108px); border-top:1px solid var(--hair); }
.wk-item{
  display:grid;
  grid-template-columns:60px minmax(0,1fr) minmax(0,.85fr);
  gap:clamp(20px,3vw,52px);
  padding:clamp(26px,3.2vw,40px) 0;
  border-bottom:1px solid var(--hair);
  align-items:start;
}
.wk-item .num{
  font:500 12px/1 var(--sans); letter-spacing:.1em; color:var(--ink2);
  padding-top:9px;
}
.wk-item h3{
  font-family:var(--serif); font-weight:400; font-size:clamp(21px,2vw,27px);
  line-height:1.18; letter-spacing:-.008em; margin:0 0 10px; color:var(--ink);
}
.wk-item p{ margin:0; color:var(--ink2); font-size:16px; line-height:1.62; }
.wk-item p.emph{ margin-top:12px; color:var(--ink); }
.wk-ticks{ list-style:none; margin:0; padding:0; padding-top:5px; }
.wk-ticks li{
  position:relative; padding-left:20px; margin-bottom:9px;
  color:var(--ink2); font-size:14.5px; line-height:1.5;
}
.wk-ticks li::before{
  content:""; position:absolute; left:0; top:.52em;
  width:9px; height:5px; border-left:1.5px solid var(--grnD);
  border-bottom:1.5px solid var(--grnD); transform:rotate(-45deg);
}
.wk-item.shown .num{ color:var(--grnT); }
.wk-item .seen{
  display:inline-block; margin-left:10px; font:500 10.5px/1 var(--sans);
  letter-spacing:.11em; text-transform:uppercase; color:var(--grnT);
  vertical-align:2px;
}

/* ==========================================================================
   4.  RESPONSE
   One breakpoint takes the rig from flanking to stacked. Below it the devices
   still never reflow: they are simply photographed at a different scale, and
   they are cropped a little harder so the app inside stays legible on a phone
   instead of shrinking to nothing.
   ========================================================================== */
@media (max-width:1120px){
  .wk-rig{ grid-template-columns:minmax(0,1fr); gap:clamp(28px,5vw,48px); }
  /* Stacked, the centre column is no longer held off the edges by the screens
     either side of it, so it has to bring its own margin. Same value the rest of
     the page uses, so the heading lines up with the index below it. */
  .wk-mid,
  .wk-seat{ padding-left:clamp(20px,4vw,48px); padding-right:clamp(20px,4vw,48px); }
  .wk-mid{ order:-1; max-width:calc(56ch + 96px); }
  /* Reduced, not removed. Throwing the rotation away below 1120px meant the depth
     vanished entirely on a smaller window, which is where most people will see it. */
  .wk-side--l .wk-dev{ transform:translateZ(30px) rotateY(-18deg) rotateX(4deg); width:114%; margin-left:-7%; }
  .wk-side--r .wk-dev{ transform:translateZ(30px) rotateY(18deg) rotateX(4deg); width:114%; margin-left:-7%; }
  .wk-side--r .wk-seat{ margin-top:16px; }
  .wk-item{ grid-template-columns:44px minmax(0,1fr); }
  .wk-ticks{ grid-column:2; padding-top:14px; }
}
@media (max-width:700px){
  body{ font-size:16px; }

  /* ---------- the phone sequence: screen, sentence, screen ----------
     Three things in a column, each arriving on its own, rather than the desktop's pinned
     tableau with the pin taken out. Source order is already left, line, right, so ordering
     the line between the two sides is all the sequencing this needs.

     --enter runs 1 (on its way in) to 0 (settled) and is the only thing JS writes. Every
     angle and offset below is composed from it, so CSS keeps ownership of the geometry and
     there is no inline transform left stranded on the element when the window grows back
     past this breakpoint. It defaults to 0, the settled state, so the section is complete
     and readable with no JS running at all. */
  .wk-rig{ perspective:640px; perspective-origin:50% 50%; }
  .wk-side--l{ order:1; }
  .wk-side--r{ order:3; }

  .wk-side--l .wk-dev,
  .wk-side--r .wk-dev{
    width:150%; margin-left:-25%;
    opacity:calc(1 - var(--enter,0) * .9);
  }
  /* Comes in from the left, turning as it lands. The resting angle is steeper than the
     desktop's because there is only one screen on the line here, so nothing is competing
     with it and it can afford to be more of an object. */
  .wk-side--l .wk-dev{
    transform:
      translateX(calc(var(--enter,0) * -13%))
      translateY(calc(var(--enter,0) * 20px))
      rotateY(calc(-20deg - var(--enter,0) * 16deg))
      rotateX(calc(5deg + var(--enter,0) * 4deg));
    transform-origin:right center;
  }
  /* And this one from below, which is where it is coming from on a phone: it is the next
     thing down the page, so it should arrive the way the page is moving. */
  .wk-side--r .wk-dev{
    transform:
      translateY(calc(var(--enter,0) * 76px))
      rotateY(calc(20deg + var(--enter,0) * 14deg))
      rotateX(calc(5deg + var(--enter,0) * 9deg));
    transform-origin:left center;
  }

  /* The sentence stops being an overlay and becomes the paragraph between the two screens,
     which is what it is on a phone. Left set, not centred: it is being typed, and typing
     grows from a left margin. */
  .wk-line{
    position:static; order:2;
    /* position:static drops left/top, but NOT the transform: translate(-50%,-50%) is a
       percentage of the element's own box, so it survives the switch to normal flow and
       hangs the sentence half its own width off the left edge of the page. Measured at
       390px: 194px outside the rig. It has to be cancelled explicitly. */
    transform:none;
    /* 30ch rather than 24. David's replacement sentence is 89 characters against the old 51,
       and at 24ch it stacked into six lines on a phone. */
    width:auto; max-width:30ch;
    /* Deliberately lopsided, and more below than above. The sentence belongs to the screen
       it is describing, so it sits close under it; the space below is what stops the second
       screen crowding the last words of it as it swings in. */
    margin:clamp(20px,5vw,34px) 0 clamp(26px,7vw,46px);
    padding:0 clamp(20px,5vw,32px);
    text-align:left;
    font-size:clamp(27px,7.4vw,38px);
    /* No halo: it is on paper here, not lying over two lit screens. */
    text-shadow:none;
  }

  .wk-item{ grid-template-columns:1fr; gap:14px; }
  .wk-ticks{ grid-column:1; padding-top:2px; }
  .wk-item .num{ padding-top:0; }
}

/* ==========================================================================
   5.  REDUCED MOTION
   Nothing runs. The sequence is rendered at its last beat, which is a complete
   and honest picture: the statement posted, the public reply landed, the second
   seat's comment in the thread. The cursor is removed rather than frozen, since
   a stalled cursor implies a machine that has hung. The step buttons still work
   and still seek, they just do it instantly.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  .wk-cur,.wk-ring{ display:none !important; }
  .wk-bar i{ transition:none !important; }
}



/* =========================== TRUST STRIP ===========================
   Under the hero, because that is where someone looks for evidence that anyone else has
   done this. Deliberately the quietest thing on the page: proof does not need to shout,
   and a row of logos that shouts reads as a page that is trying to convince you.

   The marks drift rather than step. A carousel with slides and dots asks to be operated;
   a slow drift is just something the page is doing, and it also means the strip works
   with four logos or with twenty without any layout decision changing. */
.trust {
  padding: clamp(34px, 5vh, 60px) 0 clamp(38px, 5.5vh, 68px);
  border-bottom: 1px solid var(--line);
}
.trust-lead {
  margin: 0 0 clamp(18px, 2.4vh, 26px);
  text-align: center;
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  /* #6A6668 on white is 5.66:1 */
  color: var(--ink-soft);
}

/* The window crops the drift. The mask is what stops a logo appearing out of nothing at
   the edges: they fade in and out of the page instead of being chopped by it. */
.trust-window {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}
.trust-track { display: flex; width: max-content; animation: trust-drift 46s linear infinite; }
.trust-row {
  display: flex; align-items: center;
  gap: clamp(38px, 5vw, 76px);
  margin: 0; padding: 0 clamp(19px, 2.5vw, 38px); list-style: none;
}
.trust-row li {
  flex: 0 0 auto;
  /* One box for every mark, whatever shape it is. */
  width: clamp(104px, 9.5vw, 146px);
  height: 64px;
  display: flex; align-items: center; justify-content: center;
}
/* One height for every mark, so a wide lockup and a square one carry the same weight.
   Greyed and held back, because these belong to other people and the page is not trying
   to look like their website. */
.trust-row img {
  display: block;
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain;
  /* Four of these are JPEGs, which cannot carry transparency, so they arrive as logos in
     white boxes. Multiply drops white to nothing against a white page, which makes those
     four sit on the paper like the transparent ones instead of floating in tiles. */
  mix-blend-mode: multiply;
  filter: grayscale(1);
  opacity: 0.68;
}
/* The fallback: no file, so the name is set in type instead of showing a broken image.
   This is what the whole strip looks like if David cannot clear any logos, and it is a
   perfectly good version of it. */
.trust-row li.no-logo {
  font-family: Newsreader, Georgia, serif;
  font-size: 17px; letter-spacing: -0.005em;
  color: var(--ink-soft);
  white-space: nowrap;
}

@keyframes trust-drift { to { transform: translateX(-50%); } }

/* Stop on hover so a name can actually be read, and stop entirely for anyone who has
   asked the browser for less movement. Under reduced motion the row wraps and sits
   still, which shows every mark at once rather than hiding most of them. */
.trust:hover .trust-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .trust-track { animation: none; width: 100%; }
  .trust-row { flex-wrap: wrap; justify-content: center; row-gap: 22px; }
  .trust-row + .trust-row { display: none; }
  .trust-window { -webkit-mask-image: none; mask-image: none; }
}
@media (max-width: 700px) {
  .trust-row li { width: clamp(88px, 26vw, 116px); height: 50px; }
  .trust-row li.no-logo { font-size: 15px; }
}

/* =========================== THE VOICES ===========================
   Five AI characters who ring your people during an exercise. Click one, hear it.

   The name is the only label. An earlier version carried the role and the line they say;
   both went, because reading the words before you hear them spoils the thing the section
   exists to do.

   The spheres are driven by a Web Audio analyser rather than a keyframe loop. --amp is
   written per frame from the low two thirds of the spectrum, so they move when the voice
   moves and go still in the gaps between sentences. A sphere that pulses on a timer is
   decoration and anyone who watches it twice can tell. */
.vx { padding: clamp(60px, 8vh, 104px) 0 clamp(64px, 9vh, 116px); }
/* The head now carries a line under the heading, so the measure moves onto the children:
   24ch was set for a heading and would break a paragraph into five words a line. */
.vx-head { text-align: center; margin: 0 auto clamp(38px, 5.5vh, 62px); max-width: 46ch; }
.vx-head h2 { margin: 0 auto; max-width: 24ch; }
.vx-lede {
  margin: 15px auto 0; max-width: 44ch;
  font-size: 15.5px; line-height: 1.6; color: var(--ink-soft);
}

.vx-stage {
  position: relative;
  height: 268px;
  margin: 0 auto;
  max-width: 760px;
}

/* The arrows stand where the outermost spheres used to, so the row reads as a carousel
   with its own controls rather than a row of orbs with buttons underneath it. */
.vx-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; padding: 0; z-index: 12;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--paper); color: var(--ink-soft);
  font: 400 17px/1 Inter, sans-serif; cursor: pointer;
  transition: border-color 200ms, color 200ms, transform 200ms var(--ease);
}
.vx-arrow.prev { left: 0; }
.vx-arrow.next { right: 0; }
.vx-arrow:hover { border-color: var(--ink-soft); color: var(--ink); }
.vx-arrow.prev:hover { transform: translateY(-50%) translateX(-2px); }
.vx-arrow.next:hover { transform: translateY(-50%) translateX(2px); }
.vx-arrow:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 3px; }

.vx-orb {
  /* Written per frame by the analyser and inherited by .vx-ball. It MUST be declared
     here and nowhere below, or the ball shadows it and never moves. */
  --amp: 0;
  position: absolute; left: 50%; top: 50%;
  width: 200px; height: 200px;
  padding: 0; border: 0; background: none;
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 640ms var(--ease), opacity 640ms var(--ease);
}
.vx-orb:focus { outline: none; }
.vx-orb:focus-visible .vx-ball{ box-shadow:0 0 0 3px var(--paper), 0 0 0 6px var(--grnT); }

/* The sphere is drawn by voices.js: shaded per pixel from a real surface normal, with a
   Fresnel rim, a window reflection, a specular and a starfield that turns inside the
   glass. Stacked gradients were what made the first version look flat, so there are none
   here on purpose. The element only needs to be a round box of the right size. */
.vx-ball {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  border-radius: 50%;
}

.vx-play {
  position: absolute; left: 50%; top: 50%;
  width: 64px; height: 64px; margin: -32px 0 0 -32px;
  border-radius: 50%;
  background: rgba(255,255,255,0.92);
  display: grid; place-items: center;
  opacity: 0; transform: scale(0.9);
  transition: opacity 300ms var(--ease), transform 300ms var(--ease);
}
.vx-orb.is-on .vx-play { opacity: 1; transform: none; }
/* The row movement wants a long ease; the pulse wants none of it. While an orb is the one
   speaking, its transform snaps so the sphere tracks the voice instead of lagging it. */
.is-playing .vx-orb.is-on { transition: transform 70ms linear, opacity 640ms var(--ease); }
.vx-orb.is-on:hover .vx-play { background: #fff; }
.vx-play::before {
  content: "";
  width: 0; height: 0;
  border-left: 18px solid #17181A;
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
  margin-left: 4px;
}
.is-playing .vx-orb.is-on .vx-play::before {
  border: 0;
  width: 15px; height: 18px; margin-left: 0;
  background: linear-gradient(90deg, #17181A 0 5px, transparent 5px 10px, #17181A 10px 15px);
}

.vx-under { text-align: center; margin-top: clamp(16px, 2.4vh, 28px); }
.vx-name {
  margin: 0;
  font-family: Newsreader, Georgia, serif; font-weight: 300;
  font-size: clamp(25px, 2.5vw, 34px); line-height: 1.1; letter-spacing: -0.015em;
  color: var(--ink);
}
/* The pressure wave of whatever is speaking. Sits under the name at a fixed height so the
   block never reflows between a clip playing and not. */
.vx-wave {
  display: block;
  width: min(420px, 76%); height: 44px;
  margin: 16px auto 0;
}
.vx-note { margin: 14px 0 0; font-size: 13px; color: var(--ink-soft); display: none; }
.is-unavailable .vx-note { display: block; }

@media (prefers-reduced-motion: reduce) {
  .vx-orb, .vx-ball { transition: none; }
  .vx-ball::after { animation: none; }
  .vx-ball { transform: none !important; }
}
@media (max-width: 700px) {
  .vx-stage { height: 214px; max-width: none; }
  .vx-orb { width: 152px; height: 152px; }
  .vx-arrow { width: 40px; height: 40px; }
  .vx-arrow.prev { left: 6px; }
  .vx-arrow.next { right: 6px; }
}


/* ====================== GENERATING AN EXERCISE ======================
   One line and one screen. The line is typed, exactly as the workstation's is, and it is
   the only copy in the section: a heading with paragraphs under it would say in three
   sentences what the screen beside it is already showing.

   The device drawing is the workstation's own .wk-dev / .wk-devi / .wk-bez / .wk-scr,
   reused rather than reimplemented. Same product, same machine, and a second set of bezel
   styles would drift from the first within a week.

   ON THE ANGLE. The panel is 940x588, which is 16:10, but the first version of this drew
   at about 1.35:1 and read as a square. rotateY foreshortens width and does nothing to
   height, so a hard turn under a short perspective quietly squares off a wide screen. 15
   degrees under a long perspective keeps it wide, and a wider column keeps it large.

   The client UI on the screen lives in extracted/generate-ui.css and is driven by
   generate.js. ================================================================== */
/* Tall, with a sticky child. The height IS the scroll budget: 220vh against a 100vh pin
   gives one and a fifth screens of scrolling, all of it spent in this section, and that is
   what the line and the arriving screen are drawn against. */
.gn{ position:relative; height:220vh; }
.gn-pin{
  position:sticky; top:0;
  height:100vh;
  display:flex; align-items:center;
  /* The right edge of the window is the crop, and it is meant to cut the screen. */
  overflow:hidden;
}
/* The copy keeps the page's own measure and left edge. The screen is no longer in here
   with it: see .gn-stage. */
.gn-grid{
  width:100%;
  max-width:1120px; margin:0 auto;
  padding:0 clamp(20px,4vw,56px);
  position:relative; z-index:2;
}

/* The line. Same face, weight and treatment as the workstation's, so the two read as one
   voice; left aligned rather than centred because it has a screen to sit beside rather
   than two to sit over. */
.gn-line{
  margin:0;
  /* Its own measure now that it is not sharing a grid row with the screen. Widened, and the
     size eased, for the sentence that is actually in it: David's replacement is 86 characters
     against the previous 40, and at 19ch it stacked five deep at 41px, which is a wall rather
     than a line. 26ch and a gentler cap bring it back to three. */
  max-width:min(26ch,38vw);
  font-family:var(--serif); font-weight:300;
  font-size:clamp(25px,2.7vw,38px);
  line-height:1.06;
  letter-spacing:-.022em;
  color:#232122;
}
/* The workstation's caret, to the character: same size, same offset, same black. Two
   carets on one page disagreeing about their colour is the sort of thing nobody can name
   but everybody feels. */
.gn-line i{
  display:inline-block; width:.055em; height:.72em;
  vertical-align:-.04em; margin-left:.07em;
  background:#232122;
  animation:gn-line-caret 1.06s step-end infinite;
}
.gn-line.done i{ display:none; }
@keyframes gn-line-caret{ 0%,100%{ opacity:1 } 50%{ opacity:0 } }

/* Anchored to the pin, which is the full width of the window, rather than to the grid,
   which is capped at 1120 and centred. That distinction is the whole bug: as a percentage
   of the column the screen was cut by 205px at a 1265 window and by nothing at all past
   about 1500, so on a wide monitor it simply sat there with clear page beside it. Against
   the pin, -9vw means the same thing at every size.

   Long perspective, because a short one is what squared the screen off. */
.gn-stage{
  position:absolute;
  right:-9vw; top:50%;
  /* Three constraints, and the third is the one that was missing. The device's height is a
     computed multiple of its WIDTH (618 x scale, scale = layout width / 964), while the pin
     it sits in is a viewport HEIGHT. So on a wide but short window the screen quietly grew
     taller than its own frame and the pin clipped the bezel off the top and bottom: at
     1617 x 850 it worked out to 876px tall in an 850px pin.

     92vh caps it against the short axis too. Drawn height lands at about 0.79 of the
     viewport height whatever the window, so there is always real clearance above and below
     rather than a bezel running off the edge. The right edge is anchored separately, so
     narrowing this moves the LEFT edge inward and the hug against the right is untouched. */
  width:min(1180px,63vw,92vh);
  /* 1150 rather than 1600. Perspective STRENGTH is the ratio of the element width to
     the perspective distance, not the distance alone: the workstation is 744/820 = 0.91
     and this was 1060/1600 = 0.66, a much flatter lens on a screen turned less than half
     as far. Matching both makes the two sections photographs of one room. */
  perspective:1150px;
  /* MATCHED BY RESULT, NOT BY NUMBER. The workstation turns 26deg and draws at 1.44. Copying
     26deg here drew at 1.16, because that device carries scale(.945) and a 26px offset of its
     own and so loses drawn height, while this one loses none: the same turn squares this
     screen off and leaves the other alone. Swept perspective against angle and measured;
     1150px with 12deg lands on 1.44 exactly. Match what two things DRAW as, not what their
     transforms say.

  /* 50% 46%, the workstation's own origin, and this is the term that actually decides how
     square the screen looks. perspective-origin is where the viewer stands. At 14% the
     vanishing point sat off the left edge, so a screen on the right was seen at a steep
     oblique and foreshortened to 0.71 of its width; the workstation, viewed from 50%, keeps
     0.87. Same angle, same lens, different place to stand, and a 1.11 aspect against 1.44. */
  perspective-origin:50% 46%;
  z-index:1;
}
/* The camera belongs on the right, because this is the right-hand machine. 32% is the
   default and it is the LEFT machine's position; the workstation's right screen already
   uses 68% and this matches it. */
.gn-stage .wk-cam{ --cam:68%; }
/* The window, larger than the workstation's because there is only one of them here and
   nothing to share the room with. */
/* The workstation window's geometry, to the pixel: 102 either side, 44 above, 70 of
   desktop below it before the dock. Mine was 40/40/16/38, which hugged the edges and
   crowded the dock, and two windows on one page disagreeing about their margins is
   exactly what makes them look like two different machines. */
.gn-win{ left:102px; top:44px; width:736px; height:474px; }
/* --out is written by generate.js from the scroll position: the distance the screen still
   has to travel, in pixels, from off the right of the window to where it settles. No
   transition, because the scroll IS the timing. With scripting off it is simply 0 and the
   screen is where it belongs. */
.gn-stage .wk-dev{
  --s:.55;
  /* Wider than the stage it sits in, and the stage already ends past the right edge of
     the window, so the overhang is the stage's 9vw plus this. rotateY foreshortens from
     the left origin, which eats some of it back, hence 134 rather than something tidier. */
  width:134%;
  /* translateY(-50%) does the vertical centring, which the stage cannot do with margins
     because the device's height is a computed multiple of its scale. It sits above
     .wk-scr in the tree, so it never touches the cursor's coordinate space. */
  transform:translateY(-50%) translateX(calc(var(--out,0) * 1px)) translateZ(30px) rotateY(12deg) rotateX(5deg);
  transform-origin:left center;
}

.gn-cur{
  position:absolute; left:0; top:0; width:19px; height:26px;
  z-index:9; pointer-events:none;
  transform:translate3d(-9999px,-9999px,0);
}
.gn-cur svg{ display:block; width:19px; height:26px; }
.gn-ring{
  position:absolute; width:34px; height:34px; margin:-17px 0 0 -17px;
  border:1.5px solid rgba(35,33,34,.55); border-radius:50%;
  z-index:8; pointer-events:none; opacity:0;
  transform:translate3d(-9999px,-9999px,0) scale(.35);
}

/* Stacked, and the turn flattened almost away. At this width the screen is the whole
   column and 15 degrees would throw away text that is already small. */
@media (max-width:900px){
  /* Stacked: the screen goes back into the flow under the line, because there is no room
     beside it and a screen overlapping the sentence is worse than a screen below it. */
  /* The section height IS the scroll budget for the pin, and with the pin gone it is just
     empty space. Left in, it reserved 220vh for 497px of content: 1289px of nothing on a
     phone, a screen and a half of scrolling past an empty page. */
  .gn{ height:auto; }
  .gn-pin{ height:auto; position:static; padding:48px 0 56px; }
  .gn-line{ max-width:none; }
  .gn-stage{
    position:static; width:112%; margin:32px 0 0 -6%;
    transform:none;
  }
  .gn-stage .wk-dev{
    transform:translateX(calc(var(--out,0) * .5px)) rotateY(7deg) rotateX(2deg);
  }
}

@media (prefers-reduced-motion:reduce){
  .gn-cur,.gn-ring{ display:none !important; }
}


/* ---- the voices carousel on a narrow screen ----------------------------------------
   The carousel offsets its neighbours by a flat 176px, which is a desktop number and does
   not scale. Below about 560px there is physically nowhere for them to go: on a 375 phone
   the two neighbours land at -33 and 408, so each one is a third off the screen AND sitting
   underneath an arrow button, which is both ugly and a tap target on top of a tap target.

   560 is not a guess. The stage is the viewport less 20 either side, an arrow is 40 wide
   against that edge, the centre orb is 152 and a neighbour is 152 x 0.58 = 88. A neighbour
   clears the arrow only when vw/2 - 176 - 44 > 60, which is vw > 560.

   Below that, only the current orb is shown. !important because place() writes opacity
   inline on every orb every time it runs, and an inline style beats a stylesheet. */
@media (max-width:560px){
  /* Gated on .js because is-on is written by place(), which only runs if scripting does.
     Without the gate, a page with JS off would have no orb carrying is-on and this would
     hide the entire carousel rather than just its neighbours. */
  .js .vx-orb:not(.is-on){ opacity:0 !important; pointer-events:none !important; }
}


/* ======================== PRICING: THE VALUE SCALE ========================
   Replaces the three-column table. Same three numbers, but the bars carry the cost of a
   SINGLE exercise at each tier, so the discount is the shape you see rather than a
   division the reader has to perform and mostly will not. From landing/proto/pricing.html
   option B, which is the one he picked. ====================================== */
.vs{ margin-top:34px; display:grid; gap:2px; }
.vs-row{
  display:grid; grid-template-columns:132px 1fr 108px;
  align-items:center; gap:20px;
  padding:20px 0; border-bottom:1px solid var(--line);
}
.vs-row:first-child{ border-top:1px solid var(--line); }
.vs-q{ display:flex; align-items:baseline; gap:9px; }
.vs-q b{ font-family:var(--serif); font-size:32px; font-weight:300; line-height:1; }
.vs-q span{ color:var(--ink-soft); font-size:14px; }
.vs-track{ position:relative; height:34px; }
.vs-track i{
  position:absolute; left:0; top:9px; height:16px; border-radius:8px;
  background:linear-gradient(90deg,var(--green-dim),var(--green));
}
.vs-track em{
  /* top:-1px centres an 18px line box on the 16px bar. At top:8px more than half of
     every numeral hung below the bar, in white, onto white paper. And ink rather than
     white because white on --green-dim measures 1.69:1; see the table at the top of this
     file, which already forbids exactly this. */
  position:absolute; top:-1px; font-style:normal; font-size:12px; color:var(--ink);
  font-weight:600; line-height:18px; padding-left:11px; letter-spacing:.02em;
}
.vs-each{ text-align:right; font-variant-numeric:tabular-nums; }
.vs-each b{ display:block; font-size:19px; font-weight:500; }
.vs-each span{ display:block; font-size:12px; color:var(--ink-soft); margin-top:1px; }
/* --grnT, not --green-deep. The table at the top of this file records #4FAE3B at 2.82:1
   and says marks and rules only, never a letterform; these are 19px/500 so AA wants 4.5. */
.vs-row.best .vs-each b{ color:var(--grnT); }
.vs-cap{ margin-top:20px; display:flex; justify-content:space-between; gap:20px; flex-wrap:wrap; }
.vs-cap p{ margin:0; }

/* ======================== CREDIBILITY: THE NUMBER ========================
   Fifty is the strongest fact on the page and it was the middle clause of a sentence. Set
   at the size the fact deserves, with the explanation beside it rather than under it, so
   the number is read first. From landing/proto/about.html option B.

   No rule above the body text: he singled that green line out, and he is right that with
   the eyebrows gone it was the last bit of decoration left standing on its own.
   ====================================== */
.nm{
  display:grid; grid-template-columns:minmax(0,auto) minmax(0,1fr);
  gap:clamp(30px,6vw,86px); align-items:center;
}
.nm-n{
  font-family:var(--serif); font-weight:300;
  font-size:clamp(140px,20vw,268px); line-height:.82; letter-spacing:-.05em;
  margin:0; color:var(--ink);
}
.nm-u{ margin:8px 0 0; font-size:14px; letter-spacing:.18em; text-transform:uppercase; color:var(--ink-soft); }
.nm-body p{ margin:0 0 15px; max-width:32em; color:var(--ink-soft); font-size:17px; line-height:1.66; }
.nm-body p:last-child{ margin-bottom:0; color:var(--ink); }

@media (max-width:820px){
  .vs-row{ grid-template-columns:96px 1fr; row-gap:8px; }
  .vs-each{ grid-column:2; text-align:left; }
  .nm{ grid-template-columns:1fr; gap:22px; }
}


/* ==================== WHAT SIMX DOES: THE STRIPS ====================
   Six things that happen in ORDER. They were a 3x2 grid, and a grid is a matrix: it says
   these six are parallel and interchangeable, which is the opposite of what they are. A
   numbered stack reads top to bottom, which is the order they happen in.

   That also disposes of the tone ramp. The escalating fills existed to carry sequence
   through a layout that could not express it; numbers carry it for free, so there is no
   ramp, no black panel, and no colour here the rest of the page does not already use.

   Rules rather than boxes: one hairline per row and nothing else. The page has no cards
   anywhere else and this section was the only place inventing them.
   ================================================================ */
.st-list{ margin-top:clamp(26px,3.4vw,44px); }
.st{
  display:grid;
  grid-template-columns:clamp(44px,5vw,72px) minmax(0,1fr) clamp(150px,17vw,230px);
  align-items:center;
  gap:clamp(18px,3vw,48px);
  padding:clamp(24px,3vw,38px) 0;
  border-top:1px solid var(--line);
}
.st:last-child{ border-bottom:1px solid var(--line); }

.st-n{
  margin:0; align-self:start;
  font-family:var(--serif); font-weight:300;
  font-size:clamp(20px,1.7vw,26px); line-height:1;
  color:var(--ink-soft);
  font-variant-numeric:tabular-nums;
}
.st-l{
  margin:0 0 7px; font-size:12px; font-weight:600;
  letter-spacing:.10em; text-transform:uppercase; color:var(--grnT);
}
.st-h{
  margin:0 0 9px; font-family:var(--serif); font-weight:400;
  font-size:clamp(21px,2.1vw,30px); line-height:1.14; letter-spacing:-.018em;
  max-width:22ch;
}
.st-p{ margin:0; color:var(--ink-soft); font-size:15.5px; line-height:1.62; max-width:52ch; }

.st-art{ display:flex; align-items:center; justify-content:flex-end; color:var(--ink); }
.st-art svg{ width:100%; max-width:210px; height:auto; display:block; }

/* One stroke language across all six drawings. currentColor throughout, which is what let
   these survive the move off the cards without a single edit. */
.d-s{ fill:none; stroke:currentColor; stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round; }
.d-f{ fill:currentColor; }
.d-q{ opacity:.34; }
.d-caret{ animation:st-blink 1.06s step-end infinite; }
@keyframes st-blink{ 0%,100%{opacity:1} 50%{opacity:0} }

/* The drawing is the first thing to go when there is no room for it: it is decoration for
   the sentence beside it, and at this width the sentence is what matters. */
@media (max-width:860px){
  .st{ grid-template-columns:clamp(36px,7vw,52px) minmax(0,1fr); gap:16px; }
  .st-art{ display:none; }
  .st-h{ font-size:clamp(20px,4.4vw,24px); }
}

@media (prefers-reduced-motion:reduce){
  .d-caret{ animation:none; }
}


/* ====================== THE NAV: TWO GLASS PILLS ======================
   Not a bar. A bar over a photograph looks like a bar bolted to a photograph, and worse, a
   bar has to pick one background colour while the frame underneath it never stops changing.

   THE READABILITY PROBLEM THIS SOLVES. Over the video hero the links were light text, and
   the top of that clip is full of bright bokeh, so light landed on light and the menu
   disappeared. Glass carries its own contrast with it: the pill darkens whatever is behind
   it before the text is drawn, so the text is legible over bokeh, over the phone, over
   anything.

   Fixed rather than sticky, because sticky reserves 64px of layout and the hero needs to
   start at the very top of the window with the pills floating on it. Pages that are not
   the hero get .nav-clear on their first section to make that room back.
   ================================================================== */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:60;
  background:none; border:0; backdrop-filter:none; -webkit-backdrop-filter:none;
  pointer-events:none;                 /* the gap between the pills is not a click target */
}
.nav-inner{
  width:100%; max-width:1120px; margin:0 auto;
  padding:clamp(12px,1.8vh,20px) clamp(20px,4vw,56px);
  /* STRETCH, NOT CENTRE. David: "Can you make all the shapes the same depth. The one on the left
     is slightly deeper than the one on the right." He was right and it measured 65px against 59.
     The byline under the wordmark added height the other pill had no reason to match, and
     centring let each one size itself. Stretched, the row settles on one depth and both pills
     take it, so the pair reads as two halves of the same object rather than two objects. */
  height:auto; display:flex; align-items:stretch; gap:clamp(10px,1.5vw,18px);
}
/* THE WHITE STRIP BEHIND THE STATUS BAR, ON iOS.
   Diagnosed off photos of the device. iOS Safari fills the strip behind the status bar with the
   CANVAS background. On a site whose page is green at the top, the strip is green and looks like
   part of the page; here the canvas is --paper, so a white band sat above a #0B0D0F hero and
   never moved, because the strip is outside the scrolling viewport.

   Two rules, and the second one is not optional. READ THIS BEFORE TOUCHING EITHER.

   The canvas background is taken from the ROOT element. When <html> has no background of its
   own, the browser propagates <body>'s up to the canvas instead, and a propagated background
   paints the whole canvas no matter how tall the body box actually is. That propagation was
   doing a lot of unnoticed work here, because `html, body { height:100% }` pins the body box to
   ONE viewport height against a document nine times that tall.

   So giving <html> a background stops the propagation, and body's --paper immediately shrinks
   to the first screen. Everything below it that does not set its own background falls through to
   whatever <html> now is. Setting only the first rule turns the page dark from the first screen
   down. It was shipped that way once. Hence the second rule: let the body box grow to the
   content so its background covers the document on its own merit rather than by propagation.

   min-height rather than height, so the original "at least a full screen" intent survives. The
   only percentage heights in this file are on absolutely positioned children of their own
   containers, so nothing was chaining off body's exact height. Verified after the change: body
   box 800px -> 9272px against a 9272px document, with document height, hero height, nav padding
   and wordmark position all unchanged.

   Scoped to the page that opens on the dark hero. pricing.html opens on paper and its white
   strip is correct. */
html.page-hero{ background:#0B0D0F; }
html.page-hero body{ height:auto; min-height:100%; }

/* THE NOTCH. Reverted, on purpose, and worth recording so it is not retried blind.
   The theory was that without viewport-fit=cover, iOS insets the page inside the safe area and
   fills what is left with the BODY background, which is #FFFFFF here, putting a white strip
   above a near-black hero. That theory fits everything measurable from a desktop: no cover on
   either page, no env() anywhere in this file, white body, black hero.
   It was still wrong, or at least incomplete. With cover plus safe-area padding the band did not
   go away, it became fixed and followed the page down. Nobody has yet seen the actual device, so
   the next attempt waits on a screenshot rather than on another inference. */
/* Their own contents stay centred inside whatever depth the row lands on. */
.nav-pill{ display:flex; align-items:center; }
.nav-pill{
  pointer-events:auto;
  display:flex; align-items:center;
  border-radius:999px;
  transition:background .35s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease);
}
.wordmark.nav-pill{ padding:9px 18px; }
/* THREE PILLS, NOT TWO. David: "move the phone number to its own area further to the right."
   It had its own area already, but only as a ruled-off segment inside the menu pill, which is
   why it still read as a fourth nav link. Given its own pill it is genuinely separate, and it
   does move right, because it leaves the run of links behind.

   What it does NOT do is go to the far right edge. The demo button has to be the last thing in
   the bar: it is the terminal action, and a phone number sitting after it would be the final
   word in the header while being the weaker of the two asks. So the split is by purpose rather
   than by position. Pill two is navigation, pill three is the two ways to make contact. */
.nav-menu{ margin-left:auto; padding:7px clamp(14px,1.8vw,22px); }
.nav-contact{ gap:clamp(10px,1.4vw,16px); padding:7px 7px 7px clamp(14px,1.8vw,22px); }
/* Bigger, and with the attribution under it, both at David's request. The mark went from 22px
   to 28px: enough to read as deliberate rather than as a favicon, and still short enough that the
   pill does not become the tallest thing in the bar. The byline is the quietest type on the page
   because it is a credit, not a claim. */
.nav .wordmark img{ height:28px; width:auto; display:block; }
.wordmark-stack{ display:block; }

/* THE MARK, SPLIT OFF FROM THE WORD.
   simx-logo.png was one bitmap holding "SIM" and the circle. The circle occupies exactly the
   last 100px of its 298px width, so cropping at 198 gives the word with its trailing gap intact
   and the mark can be redrawn as vector beside it. Butted together at the same height the pair
   measures 83px, which is what the single bitmap measured, so nothing else in the bar moves.
   The mark is vector now because it has to animate, and because at 28px a raster circle with
   two 2px serifs inside it was the softest thing in the header. */
.wordmark-row{ display:flex; align-items:center; }
.nav .wordmark-row .wm-word{ height:28px; width:auto; display:block; }
.sx-mark{ height:28px; width:28px; display:block; flex:none; overflow:visible; }
.sx-disc{ fill:var(--green); }
.sx-bar, .sx-serif{ fill:#fff; }

/* RESTING STATE IS THE FINISHED LOGO. Everything below only applies under .sx-play, which
   mark.js adds and removes. A missing or broken mark.js leaves a correct mark, not a blank
   circle. */
.sx-mark.sx-play .sx-disc{ animation:sxDiscIn .55s var(--ease) both; transform-box:fill-box; transform-origin:50% 50%; }
.sx-mark.sx-play .sx-wipe-r{ animation:sxWipe .55s .28s var(--ease) both; }
.sx-mark.sx-play .sx-serif{ transform-box:fill-box; }
.sx-mark.sx-play .sx-serif-a{ animation:sxSerifIn .34s .62s var(--ease) both; transform-origin:50% 100%; }
.sx-mark.sx-play .sx-serif-b{ animation:sxSerifIn .34s .72s var(--ease) both; transform-origin:50% 0%; }

@keyframes sxDiscIn{
  from{ transform:scale(.25); opacity:0; }
  60%{ transform:scale(1.06); opacity:1; }
  to{ transform:scale(1); opacity:1; }
}
/* The bar is clipped by a rect parked above the artwork and slid down over it, so the stroke
   appears to draw from its top terminal to its bottom one. A dash animation would have given it
   round or square caps; this keeps the flat horizontal terminals the original has. */
@keyframes sxWipe{
  from{ transform:translateY(-101px); }
  to{ transform:translateY(0); }
}
@keyframes sxSerifIn{
  from{ transform:scale(.2); opacity:0; }
  to{ transform:scale(1); opacity:1; }
}
@media (prefers-reduced-motion:reduce){
  .sx-mark.sx-play .sx-disc,
  .sx-mark.sx-play .sx-wipe-r,
  .sx-mark.sx-play .sx-serif-a,
  .sx-mark.sx-play .sx-serif-b{ animation:none; }
}
/* MEASURED TO THE MARK. Set in tracked uppercase at 9.5px it came out 120px wide against a
   logo of 83, so the credit was setting the width of the pill and the pill was wider than the
   thing it exists to hold. Sentence case at 9px with normal tracking measures 83px exactly: the
   uppercase and the letter-spacing were what made it wide, not the size, so dropping those keeps
   it readable rather than shrinking it into nothing. */
.wordmark-by{
  display:block; margin-top:2px;
  font-size:9px; letter-spacing:normal;
  color:var(--ink-soft); white-space:nowrap;
}
.nav.over-dark .wordmark-by{ color:rgba(246,245,243,.62); }

/* Light glass, for the paper pages. A hairline and a whisper of white, so it reads as an
   object on the page rather than a floating rectangle of nothing. */
.nav-pill{
  background:rgba(255,255,255,.72);
  -webkit-backdrop-filter:blur(16px) saturate(160%); backdrop-filter:blur(16px) saturate(160%);
  border:1px solid rgba(35,33,34,.10);
  box-shadow:0 10px 30px -18px rgba(35,33,34,.45), inset 0 1px 0 rgba(255,255,255,.7);
}
.nav-links{ display:flex; align-items:center; gap:clamp(12px,1.6vw,24px); }
.nav-links a{
  color:var(--ink2); text-decoration:none; font-size:14.5px; white-space:nowrap;
  transition:color .18s var(--ease);
}
.nav-links a:hover{ color:var(--ink); }
.nav-links a[aria-current=page]{ color:var(--ink); font-weight:500; }
/* David's mobile, top right, at his request. Set like a nav link rather than like a second
   button, because two buttons side by side make neither one the thing to press. Tabular figures
   so the digits sit evenly, which is the difference between a phone number and a string of
   numerals. */
.nav-tel{
  /* --grnT, which is the only green on this page that carries 4.5:1 and is therefore the only
     one allowed to be a letterform. The brand green is 2.18:1 and would fail as text. Green
     rather than ink so the number reads as something to do rather than as a fourth nav link,
     without becoming a second button competing with Request a demo.

     Set apart from the links to its left, per David: its own space, a handset beside it, and the
     number in international form so it works for someone reading this outside Australia. */
  display:inline-flex; align-items:center; gap:7px;
  /* The rule and the indent that used to hold it apart from the links are gone: it sits in its
     own pill now, and the gap between pills separates it far better than a 1px border did. */
  color:var(--grnT); text-decoration:none; font-size:14.5px; white-space:nowrap;
  font-variant-numeric:tabular-nums; letter-spacing:.01em; font-weight:500;
  transition:color .18s var(--ease);
}
.nav-tel svg{ width:14px; height:14px; flex:none; display:block; }
.nav-tel:hover{ color:var(--ink); }
/* GREEN OVER THE HERO TOO, AND BRIGHTER.
   This was rgba(246,245,243,.74): a dimmed white, which measured 9.01:1 against the dark pill
   while the nav links sitting right next to it are full-strength and measure 16.93:1. The number
   was carrying about half the contrast of the links beside it, so the one item in the bar that
   is a call to action read as the faintest thing in it.

   --green-dim rather than the brand green: it measures 10.89:1 here against the brand green's
   8.46:1, so it is brighter than what it replaces rather than merely different, and it stays
   clearly green. Note this does NOT contradict the "never a letterform" rule recorded against
   --grn further up: that rule is about light backgrounds, where the brand green is 2.18:1. On
   this pill the arithmetic is the other way round.

   The handset is currentColor, so it comes with it. */
.nav.over-dark .nav-tel{ color:var(--green-dim); }
.nav.over-dark .nav-tel:hover{ color:#F6F5F3; }
/* Below this the pill is carrying a wordmark, three links, a number and a button, and the
   number is the one thing also reachable from the footer and the enquiry form. */
/* When the number goes, the contact pill is holding a button and nothing else, so it drops the
   left inset it only had to give the number room and becomes a pill the size of its button. */
@media (max-width:1080px){
  .nav-tel{ display:none; }
  .nav-contact{ padding:7px; }
}

.btn-glass.btn-sm{ padding:9px 18px; font-size:14px; }

/* Dark glass, for while the hero is behind it. */
/* .72 and full-strength text, not .42 and .80, and the numbers are the reason. The glass
   has to hold up when the video puts something BRIGHT behind it, which this clip does: the
   top of frame is full of out-of-focus highlights, and that is exactly where the menu sits.
   Worked out against a pure white backdrop, the worst the footage can do: .42 glass with
   80% text gives 2.14:1, which is the unreadable menu he reported. .72 with full-strength
   text gives 6.77:1. Over the dark parts of the clip it is 10.8:1.

   The blur is what stops that reading as a black box: the pill still samples and smears
   what is behind it, so it reads as glass rather than as a panel. */
.nav.over-dark .nav-pill{
  background:rgba(18,20,23,.72);
  border-color:rgba(255,255,255,.22);
  box-shadow:0 14px 40px -22px rgba(0,0,0,.85), inset 0 1px 0 rgba(255,255,255,.20);
}
.nav.over-dark .nav-links a{ color:#F6F5F3; }
.nav.over-dark .nav-links a:hover{ color:#F6F5F3; }

/* Two logo files rather than a filter. invert(1) on the wordmark also inverts the brand
   green in the X: #63C54D becomes #9c3ab2, which is magenta. The white asset already
   existed. */
.nav .wordmark .on-dark{ display:none; }
.nav.over-dark .wordmark .on-light{ display:none; }
.nav.over-dark .wordmark .on-dark{ display:block; }

/* Pages whose first section is not a full-bleed hero have to make room for a fixed nav. */
.nav-clear{ padding-top:calc(clamp(12px,1.8vh,20px) * 2 + 40px + clamp(48px,6vw,76px)); }

/* ---------- the phone menu ----------
   Above 720px this does not exist: the links are in the bar where they belong and a button to
   reveal what is already visible would be noise. Below it, the bar cannot hold them, and until
   this went in there was no route to Pricing on a phone at all. */
.nav-burger{
  display:none;
  align-items:center; justify-content:center;
  width:44px;                     /* 44px is the smallest thing a thumb reliably hits */
  padding:0; border-radius:999px;
  background:rgba(255,255,255,.72); cursor:pointer;
  -webkit-appearance:none; appearance:none;
}
.nav-burger-lines{ display:block; width:18px; height:12px; position:relative; }
.nav-burger-lines i{
  position:absolute; left:0; right:0; height:1.6px; border-radius:2px;
  background:var(--ink);
  transition:transform .26s var(--ease), opacity .18s var(--ease);
}
.nav-burger-lines i:nth-child(1){ top:0; }
.nav-burger-lines i:nth-child(2){ top:5.2px; }
.nav-burger-lines i:nth-child(3){ top:10.4px; }
.nav.over-dark .nav-burger-lines i{ background:#F6F5F3; }

/* Three lines fold into a cross, so the button says what pressing it again will do. */
.nav-burger[aria-expanded=true] .nav-burger-lines i:nth-child(1){ transform:translateY(5.2px) rotate(45deg); }
.nav-burger[aria-expanded=true] .nav-burger-lines i:nth-child(2){ opacity:0; }
.nav-burger[aria-expanded=true] .nav-burger-lines i:nth-child(3){ transform:translateY(-5.2px) rotate(-45deg); }

.nav-sheet{
  position:absolute; left:0; right:0;
  margin:8px clamp(20px,4vw,56px) 0;
  padding:10px;
  border-radius:22px;
  background:rgba(255,255,255,.92);
  -webkit-backdrop-filter:blur(18px) saturate(160%); backdrop-filter:blur(18px) saturate(160%);
  border:1px solid rgba(35,33,34,.10);
  box-shadow:0 24px 60px -28px rgba(35,33,34,.55);
  /* Shut by default: collapsed and untouchable, but still in the document. */
  transform-origin:top center;
  transform:translateY(-8px) scale(.98);
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .2s var(--ease), transform .26s var(--ease), visibility .26s;
}
.nav-sheet.is-open{ transform:none; opacity:1; visibility:visible; pointer-events:auto; }

.nav-sheet-links{ display:flex; flex-direction:column; }
.nav-sheet-links a{
  padding:13px 14px; border-radius:14px;
  color:var(--ink); text-decoration:none; font-size:16px;
}
.nav-sheet-links a:active{ background:rgba(35,33,34,.06); }
.nav-sheet-links a[aria-current=page]{ font-weight:500; }

.nav-sheet-tel{
  display:flex; align-items:center; gap:9px;
  margin-top:4px; padding:13px 14px;
  border-top:1px solid rgba(35,33,34,.10);
  color:var(--grnT); text-decoration:none;
  font-size:16px; font-variant-numeric:tabular-nums; font-weight:500;
}
.nav-sheet-tel svg{ width:15px; height:15px; flex:none; display:block; }

@media (prefers-reduced-motion:reduce){
  .nav-sheet, .nav-burger-lines i{ transition:none; }
}

@media (max-width:720px){
  /* The links are the pill's only occupant, so the pill goes with them. They reappear in the
     sheet below rather than disappearing from the site. */
  .nav-links{ display:none; }
  .nav-menu{ display:none; }
  .nav-burger{ display:flex; margin-left:auto; }
  /* The burger now does the pushing, so the contact pill sits straight after it. */
  .nav-contact{ margin-left:0; }
  .nav-inner{ padding-top:10px; padding-bottom:10px; }
}

/* Above the breakpoint the sheet must never appear, whatever state JS left it in: rotating a
   phone to landscape mid-menu would otherwise drop a floating panel onto the desktop layout. */
@media (min-width:721px){
  .nav-sheet{ display:none; }
}

/* ====================== THE VIDEO HERO ======================
   The phone clip full bleed with the type over it. The pressure network it replaced is
   parked whole at proto/hero-network-backup/section.html and hero.js still drives it. */
.vh{
  position:relative;
  min-height:100svh; min-height:100vh;
  display:flex; flex-direction:column;
  overflow:hidden;
  background:#0B0D0F;
}
.vh video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
/* EASED OFF, AND THE AMOUNT WAS MEASURED RATHER THAN EYEBALLED.
   David's note on the first cut was that the hero looked dark and sinister. The footage was the
   obvious culprit, at mean luma 42 to 54, and the replacement sits around 145. But compositing
   this scrim over the new clip put it most of the way back: the band the headline occupies came
   out at mean 31. The scrim was doing as much of the darkening as the video was.

   It cannot just be removed. With no scrim at all that band is p99 234, which is 1.11:1 against
   #F6F5F3 and completely unreadable. So the question is how far it can ease off while the words
   stay legible, and that has an answer rather than an opinion:

     before    p99  84   7.01:1
     this      p99 100   5.48:1   passes AA for body text
     lighter   p99 118   4.20:1   large text only, so the lede fails

   Measured on the real poster frame, across the band the copy actually occupies (x 10% to 64%,
   vertically centred), taking the 99th percentile so a single specular highlight does not decide
   it. The lede is clamp(16px,1.15vw,18px), which is body text and needs the full 4.5 rather than
   the 3.0 large text is allowed. That is what rules the lighter setting out. */
.vh-scrim{
  position:absolute; inset:0; pointer-events:none;
  background:
    linear-gradient(90deg, rgba(11,13,15,.93) 0%, rgba(11,13,15,.84) 28%,
                           rgba(11,13,15,.47) 57%, rgba(11,13,15,.09) 86%),
    linear-gradient(0deg, rgba(11,13,15,.60) 0%, rgba(11,13,15,0) 41%);
}
.vh-inner{
  position:relative; z-index:2;
  width:100%; max-width:1120px; margin:auto auto;
  padding:clamp(40px,7vh,90px) clamp(20px,4vw,56px);
}
.vh-copy{ max-width:min(64ch,56vw); }
/* THE ORPHANED "it". The two lines here are hand set, and the first was wrapping so that
   "it" landed alone on a line of its own. At 1280 the longest line needs 587px in a 622px
   column, so it fits at that width and fails on a wider one: the type grows with the
   viewport while the column is capped, and before the webfont arrives the fallback serif is
   wider again. Tuning the clamp to one width only moves the failure somewhere else.

   Three things instead, so an orphan cannot happen at any width: a wider column, a gentler
   vw factor so the type never outruns it, and text-wrap:balance so that if a line ever does
   have to wrap it splits evenly rather than dropping a single word. */
.vh h1{
  font-family:var(--serif); font-weight:300;
  font-size:clamp(32px,4.05vw,64px); line-height:1.04; letter-spacing:-.028em;
  margin:0; color:#F6F5F3;
}
.vh h1 .line{ display:block; text-wrap:balance; }
.vh-lede{
  margin:22px 0 0; max-width:46ch;
  font-size:clamp(16px,1.15vw,18px); line-height:1.62; color:rgba(246,245,243,.78);
}
.vh-actions{ display:flex; align-items:center; gap:clamp(14px,2vw,26px); margin-top:30px; flex-wrap:wrap; }
.vh .link-quiet{ color:rgba(246,245,243,.72); }
.vh .link-quiet:hover{ color:#F6F5F3; }
@media (max-width:820px){
  .vh-copy{ max-width:none; }
  .vh-scrim{ background:
    linear-gradient(0deg, rgba(11,13,15,.90) 0%, rgba(11,13,15,.55) 45%, rgba(11,13,15,.35) 100%); }
}
@media (prefers-reduced-motion:reduce){ .vh video{ display:none; } }

/* ---- the pricing page ---- */
.price-head h1{
  font-family:var(--serif); font-weight:300;
  font-size:clamp(30px,4vw,50px); line-height:1.1; letter-spacing:-.022em; margin:0;
  max-width:18ch;
}
.price-head{ padding-bottom:0; }
.price-cta h2{ max-width:20ch; }
.inc{
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(20px,3vw,44px); margin-top:30px;
}
.inc-item h3{
  margin:0 0 10px; font-family:var(--serif); font-weight:400;
  font-size:21px; line-height:1.2;
}
.inc-item p{ margin:0; color:var(--ink2); font-size:15.5px; line-height:1.6; }
@media (max-width:820px){ .inc{ grid-template-columns:1fr; gap:26px; } }

/* The rest of what Socom does, which the page has never mentioned. It sat as one grey line about
   facilitated training; David replaced it with three services, and three services set as a line
   of small print read like a disclaimer rather than like things you can buy. */
.inc-more{
  margin-top:clamp(34px,4vw,52px);
  padding-top:clamp(24px,3vw,34px);
  border-top:1px solid var(--hair);
  max-width:64ch;
}
.inc-more h3{
  margin:0 0 14px; font-family:var(--serif); font-weight:400;
  font-size:21px; line-height:1.2;
}
.inc-more ul{ margin:0 0 16px; padding:0; list-style:none; }
.inc-more li{
  position:relative; padding-left:22px; margin-bottom:9px;
  color:var(--ink2); font-size:15.5px; line-height:1.6;
}
/* The tick is the accessible green, the only one allowed to carry a mark this small. */
.inc-more li::before{
  content:""; position:absolute; left:2px; top:.58em;
  width:9px; height:5px; border-left:1.6px solid var(--grnT); border-bottom:1.6px solid var(--grnT);
  transform:rotate(-45deg);
}
.inc-more p{ margin:0; }

/* THE INTRODUCTORY OFFER. A discount with a deadline is the only urgent thing on this page, so
   it is the one block allowed to look like an announcement. Tinted rather than outlined, because
   a box with a border round it on a page of prices reads as a caveat. */
.price-offer{
  margin:22px 0 0; max-width:62ch;
  padding:16px 20px;
  background:rgba(99,197,77,.10);
  border-left:3px solid var(--grnT);
  border-radius:0 6px 6px 0;
  font-size:15.5px; line-height:1.62; color:var(--ink);
}
.price-offer b{ font-weight:600; }
