/* ── Neurotic design tokens ───────────────────────────────────────────────
   Palette and chrome taken from modern.ai; the wordmark follows the lockup
   on neurotic.co (Inter 600, 20px, -0.5px tracking). Dark only. */
:root {
  --bg:            #0B0614;
  --surface:       #120A22;
  --surface-2:     #191030;
  --surface-3:     #221542;
  --border:        #2A1D45;
  --border-bright: #3D2C63;

  /* modern.ai's own bar values */
  --bar-bg:     rgba(5, 3, 10, .72);
  --bar-border: #2C1A49;
  --bar-height: 65px;

  --text:          #FFFFFF;
  --text-muted:    #B9A9D6;
  --text-dim:      #7D6B9E;

  --violet:        #A78BFA;
  --violet-bright: #B69CFF;
  --violet-deep:   #6943E6;
  --green:         #69E6A3;
  --brand-badge:   #EEE5FA;
  --danger:        #FF7A8A;

  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --radius:      12px;
  --radius-lg:   20px;
  --radius-pill: 999px;

  --gutter: 20px;   /* the brand's indent from the left edge */
  --measure: 68ch;

  /* Every full-width block on the site shares this, so their left edges line
     up down the page. They used to be 72rem, 76rem and 48rem, and the seam
     between a section heading and the section under it was visible. */
  --shell: 80rem;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(60rem 40rem at 50% -10%, rgba(105, 67, 230, .28), transparent 70%),
    radial-gradient(40rem 30rem at 85% 15%, rgba(167, 139, 250, .12), transparent 70%);
}

body > * { position: relative; z-index: 1; }

a { color: var(--violet-bright); text-decoration: none; }
a:hover { color: var(--text); }

h1, h2, h3 {
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: -.025em;
  line-height: 1.1;
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ── Brand lockup ───────────────────────────────────────────────────────── */
/* Matches neurotic.co: 32px mark, Inter 600 at 20px, tracking -0.5px. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text);
}
.brand:hover { color: var(--text); }

/* The mark sits on a light purple badge, with two knobs:
     --badge      how big the whole lockup mark is
     --mark-fill  how much of that the helmet takes, so the rest reads as badge
   The badge stays a circle whatever the mark's 197x187 ratio does. */
.brand__badge {
  --badge: 34px;
  --mark-fill: .6;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--badge);
  height: var(--badge);
  background: var(--brand-badge);
  border-radius: 50%;
}

.brand__logo {
  width: calc(var(--badge) * var(--mark-fill));
  height: auto;
  display: block;
}

.brand__name {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 20px;
  letter-spacing: -.5px;
  line-height: 1;
  text-transform: uppercase;
  margin: 0;
}

/* ── Top bar (modern.ai chrome) ─────────────────────────────────────────── */
.topbar {
  position: sticky;
  top: 0;
  z-index: 500;
  height: var(--bar-height);
  background: var(--bar-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--bar-border);
}

/* The shell plus its gutters, not the shell.
 *
 * Everything else on a page is `--shell` wide with the gutter outside it: the
 * page, the bands, the footer, the ticker. This bar put the gutter inside a box
 * that was already `--shell`, so once the window was wider than the shell the
 * brand sat one gutter - twenty pixels - to the right of every heading beneath
 * it. Below that width the two happened to agree, which is why it survived.
 */
.topbar__inner {
  position: relative;
  height: 100%;
  max-width: calc(var(--shell) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: 2rem;
}

.topbar__nav { display: flex; align-items: center; gap: 1.5rem; }

.topbar__nav a {
  font-size: 14px;
  font-weight: 400;
  color: var(--text);
}
.topbar__nav a:hover { color: var(--violet-bright); }
.topbar__nav a.is-active { color: var(--violet-bright); }

.topbar__end {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-size: 14px;
}

.topbar__muted { color: var(--text-muted); font-size: 14px; }

.topbar__end form { margin: 0; }
.topbar__end button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--text-muted);
}
.topbar__end button:hover { color: var(--violet-bright); }

@media (max-width: 46rem) {
  .topbar__inner { gap: 1rem; }
  .topbar__nav { gap: 1rem; }
  .topbar__nav a { font-size: 13px; }
}

/* ── Discreet footer (minimal pages) ────────────────────────────────────── */
.thin-footer {
  flex: none;
  padding: .9rem var(--gutter) 1.1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .35rem 1rem;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .04em;
  color: rgba(125, 107, 158, .5);
}

.thin-footer__offices {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem .55rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.thin-footer__offices li { display: flex; align-items: center; gap: .55rem; }
.thin-footer__offices li + li::before { content: "\00b7"; opacity: .5; }
.thin-footer a { color: inherit; }
.thin-footer a:hover { color: var(--text-muted); }

/* In the thin footer the trigger has to disappear into the line of text. */
.office-item--thin .office__trigger {
  display: inline;
  width: auto;
  font-family: inherit;
  letter-spacing: inherit;
}
.office-item--thin .office__city { display: inline; font-size: inherit; color: inherit; }
.office-item--thin .office__trigger:hover .office__city { color: var(--text-muted); }

/* ── Page shell (signed in) ─────────────────────────────────────────────── */
/* No `align-items: center` here. On a column flex container that is a
   cross-axis rule, and it makes every child shrink to fit its content unless
   the child remembers to say `width: 100%`. All of them do today, and each one
   is one edit away from not doing - and the failure it produces is not a
   subtle one: a column collapses to its widest word and the copy sets one word
   per line. Stretch is the default and the safe default; the things that want
   to be centred already centre themselves with `margin-inline: auto`. */
.shell {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: clamp(2.5rem, 7vw, 5rem) var(--gutter);
}

.container {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.hero { text-align: center; max-width: 40rem; width: 100%; }

.hero__title {
  font-size: clamp(2rem, 6vw, 3.25rem);
  letter-spacing: -.035em;
  margin: 0;
}

.hero__subhead {
  margin: 1rem 0 0;
  font-size: clamp(.95rem, 2.2vw, 1.1rem);
  color: var(--violet-bright);
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 500;
}

.hero__actions { margin-top: 2.25rem; }

/* ── Buttons & chips ────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  border-radius: var(--radius-pill);
  padding: .7rem 1.4rem;
  font-size: .95rem;
  font-weight: 600;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: var(--font-body);
  transition: filter .18s ease, background .18s ease, border-color .18s ease;
}

.btn--primary { background: linear-gradient(135deg, var(--violet-deep), var(--violet)); color: #fff; }
.btn--primary:hover { filter: brightness(1.12); color: #fff; }

.btn--ghost { background: rgba(255, 255, 255, .04); border-color: var(--border-bright); color: var(--text); }
.btn--ghost:hover { background: rgba(255, 255, 255, .09); color: var(--text); }

.btn--danger { background: none; border-color: rgba(255, 122, 138, .4); color: var(--danger); }
.btn--danger:hover { background: rgba(255, 122, 138, .12); color: var(--danger); }

.btn--sm { padding: .45rem 1rem; font-size: .85rem; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-bright);
  background: rgba(255, 255, 255, .03);
  padding: .3rem .8rem;
  font-size: .78rem;
  color: var(--text-muted);
}

.chip--on  { border-color: rgba(105, 230, 163, .35); color: var(--green); }
.chip--off { border-color: var(--border); color: var(--text-dim); }

/* ── Blog ───────────────────────────────────────────────────────────────── */
.page { width: 100%; max-width: var(--shell); margin-inline: auto; }

/* A page that is only prose. Narrower on purpose: 80rem of running text is
   not readable. */
.page--read { max-width: 48rem; }

.page__eyebrow {
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--violet);
  margin: 0 0 .75rem;
}

.page__title { font-size: clamp(2rem, 5.5vw, 3rem); margin-bottom: 1rem; }

.post-list { list-style: none; margin: 2.5rem 0 0; padding: 0; display: grid; gap: 1rem; }

.post-card {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .6);
  padding: 1.5rem;
  color: var(--text);
  text-align: left;
  transition: border-color .18s ease, background .18s ease;
}

.post-card:hover { border-color: var(--border-bright); background: rgba(25, 16, 48, .8); color: var(--text); }
.post-card h2 { font-size: 1.3rem; margin-bottom: .4rem; }
.post-card p { margin: 0; color: var(--text-muted); font-size: .95rem; }

.post-meta {
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--text-dim);
  letter-spacing: .06em;
  margin-top: .75rem;
}

.prose { max-width: var(--measure); color: var(--text-muted); }
.prose p { margin: 0 0 1.15rem; }
.prose h2, .prose h3 { color: var(--text); margin: 2rem 0 .75rem; }
.prose a { text-decoration: underline; text-underline-offset: 3px; }

.empty {
  border: 1px dashed var(--border-bright);
  border-radius: var(--radius-lg);
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--text-dim);
}

/* ── Full footer (signed in) ────────────────────────────────────────────── */
.site-footer {
  border-top: 1px solid var(--border);
  background: rgba(11, 6, 20, .7);
  padding: 2.25rem 0 1.75rem;
  margin-top: auto;
}

.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 1.75rem 2rem;
}

.footer-col h3 {
  font-family: var(--font-mono);
  font-size: .64rem;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(125, 107, 158, .65);
  margin: 0 0 .8rem;
}

.office-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; }

.office__city { display: block; font-size: .82rem; color: var(--text-dim); }

.office__phone {
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--text-dim);
  letter-spacing: .02em;
}
.office__phone:hover { color: var(--violet-bright); }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1rem;
  justify-content: space-between;
  align-items: center;
  margin-top: 2rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--border);
  font-size: .72rem;
  color: rgba(125, 107, 158, .65);
}

.footer-links { display: flex; gap: 1.1rem; flex-wrap: wrap; align-items: center; }
.footer-links a { color: inherit; }
.footer-links a:hover { color: var(--violet-bright); }

/* button_to renders a form + button; make it read as one more footer link. */
.footer-links__form { margin: 0; }
.footer-links button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: inherit;
  color: inherit;
}
.footer-links button:hover { color: var(--violet-bright); }

/* ── Flash ──────────────────────────────────────────────────────────────── */
.flash {
  max-width: 76rem;
  margin: 1rem auto 0;
  padding: .8rem 1.2rem;
  border-radius: var(--radius);
  border: 1px solid var(--border-bright);
  background: var(--surface-2);
  font-size: .9rem;
}

.flash--alert { border-color: rgba(255, 122, 138, .4); color: var(--danger); }
.flash--notice { border-color: rgba(105, 230, 163, .35); color: var(--green); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Admin back office ──────────────────────────────────────────────────── */
body.admin { display: block; }
body.admin::before {
  background: radial-gradient(70rem 30rem at 50% -20%, rgba(105, 67, 230, .16), transparent 70%);
}

.admin-main { max-width: 76rem; margin-inline: auto; padding: 2.5rem var(--gutter) 4rem; }

.admin-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
}
.admin-head h1 { font-size: 1.9rem; }
.admin-head p { margin: .35rem 0 0; color: var(--text-dim); font-size: .9rem; }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1rem;
  margin-bottom: 2.5rem;
}

.stat {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .6);
  padding: 1.35rem;
}
.stat__value { font-size: 2rem; font-weight: 600; line-height: 1; letter-spacing: -.03em; }
.stat__label {
  font-family: var(--font-mono); font-size: .7rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-dim); margin-top: .6rem;
}
.stat--warn .stat__value { color: var(--danger); }

.panel {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .6);
  overflow: hidden;
}
.panel + .panel { margin-top: 1.5rem; }
.panel__head {
  padding: 1rem 1.35rem;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.panel__head h2 { font-size: 1rem; letter-spacing: 0; }

.table { width: 100%; min-width: 42rem; border-collapse: collapse; font-size: .9rem; }
.table th {
  text-align: left; font-family: var(--font-mono); font-weight: 500;
  font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); padding: .8rem 1.35rem; border-bottom: 1px solid var(--border);
}
.table td { padding: .9rem 1.35rem; border-bottom: 1px solid rgba(42, 29, 69, .5); color: var(--text-muted); vertical-align: middle; }
.table tr:last-child td { border-bottom: 0; }
.table td strong { color: var(--text); font-weight: 600; }
.table td .num { white-space: nowrap; }
.table .num { font-family: var(--font-mono); font-size: .82rem; }
.table .actions { text-align: right; white-space: nowrap; }
.table .actions form { display: inline; margin: 0; }
.table-scroll { overflow-x: auto; }

.form { max-width: 44rem; display: grid; gap: 1.25rem; }
.form label { display: block; font-size: .82rem; color: var(--text-muted); margin-bottom: .45rem; font-weight: 500; }
.form input[type=text], .form input[type=email], .form input[type=password],
.form input[type=tel], .form input[type=url], .form input[type=number],
.form input[type=datetime-local], .form textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  padding: .7rem .9rem;
  color: var(--text);
  font-family: var(--font-body);
  font-size: .95rem;
}
.form textarea { min-height: 16rem; resize: vertical; line-height: 1.65; }
.form input:focus, .form textarea:focus {
  outline: none; border-color: var(--violet); box-shadow: 0 0 0 3px rgba(167, 139, 250, .15);
}
.form .hint { font-size: .78rem; color: var(--text-dim); margin-top: .4rem; }
.form__actions { display: flex; gap: .75rem; align-items: center; }

.errors {
  border: 1px solid rgba(255, 122, 138, .4);
  background: rgba(255, 122, 138, .08);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  color: var(--danger);
  font-size: .9rem;
}
.errors ul { margin: .5rem 0 0; padding-left: 1.2rem; }

.filter-bar { display: flex; gap: .4rem; flex-wrap: wrap; }

/* Centred card used by the admin sign-in and password screens. */
.auth-card { max-width: 24rem; margin: 5rem auto 0; }
.auth-card .brand { margin-bottom: .35rem; }
.auth-card__lede {
  font-size: .78rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-dim); margin: 0 0 2.25rem;
}

/* ── Footer brand + links ───────────────────────────────────────────────── */
.footer-brand { display: flex; align-items: center; gap: .5rem; opacity: .65; }
.footer-brand .brand__badge { --badge: 22px; }
.footer-brand span {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: -.2px;
  text-transform: uppercase;
  color: var(--text-dim);
}

.footer-link { font-size: .82rem; color: var(--text-dim); }
.footer-link:hover { color: var(--violet-bright); }

/* Office rows are buttons that open a dialog, but should read as plain text. */
.office__trigger {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-body);
  /* A <button> would otherwise take the browser's black buttontext. */
  color: inherit;
  border-radius: 6px;
}
.office__trigger:hover .office__city { color: var(--violet-bright); }

/* ── Modal ──────────────────────────────────────────────────────────────── */
.modal {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text);
  max-width: min(28rem, calc(100vw - 2rem));
  width: 100%;
}

.modal::backdrop {
  background: rgba(5, 3, 10, .72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.modal__panel {
  position: relative;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  padding: 1.9rem;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
}

.modal__close {
  position: absolute;
  top: .9rem;
  right: 1rem;
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-dim);
  padding: .2rem .4rem;
  border-radius: 6px;
}
.modal__close:hover { color: var(--text); background: rgba(255, 255, 255, .06); }

.modal__eyebrow {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--violet);
  margin: 0 0 .4rem;
}

.modal__title { font-size: 1.6rem; margin: 0 0 1.5rem; letter-spacing: -.03em; }

.modal__rows { margin: 0; display: grid; gap: 1.1rem; }
.modal__row { display: grid; gap: .3rem; }

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

.modal__row dd { margin: 0; font-size: .95rem; color: var(--text-muted); }
.modal__row dd a { color: var(--text-muted); }
.modal__row dd a:hover { color: var(--violet-bright); }
.modal__row dd span { display: block; }
.modal__mono { font-family: var(--font-mono); font-size: .88rem; letter-spacing: .02em; }

/* ── Client area ────────────────────────────────────────────────────────── */
.app-grid {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

.app-card {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .6);
  padding: 1.5rem;
  color: var(--text);
  text-align: left;
  transition: border-color .18s ease, background .18s ease;
}
.app-card:hover { border-color: var(--violet); background: rgba(25, 16, 48, .85); color: var(--text); }
.app-card h2 { font-size: 1.15rem; }
.app-card p { margin: 0; font-size: .9rem; color: var(--text-muted); }
.app-card__go { margin-top: auto; padding-top: .75rem; font-size: .8rem; color: var(--violet); }

/* Checkbox lists used to grant app access in the back office. */
.check-list {
  display: grid;
  gap: .6rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  max-height: 18rem;
  overflow-y: auto;
}
.check-list label {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0;
  font-size: .92rem;
  color: var(--text);
  cursor: pointer;
}
.check-list input[type=checkbox] { accent-color: var(--violet); width: 16px; height: 16px; }
.check-list__empty { color: var(--text-dim); font-size: .88rem; }

/* Subdomain input with its fixed apex pinned alongside. */
.subdomain-field {
  display: flex;
  align-items: center;
  gap: .5rem;
}
.subdomain-field input { flex: 1; min-width: 0; }
.subdomain-field__suffix {
  flex: none;
  font-family: var(--font-mono);
  font-size: .9rem;
  color: var(--text-dim);
}

.app-card__host {
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--text-dim);
  letter-spacing: .02em;
}

/* Radio cards for choosing where an app runs. */
.radio-cards { display: grid; gap: .6rem; }
.radio-cards label {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  margin: 0;
  padding: .9rem 1.05rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color .18s ease, background .18s ease;
}
.radio-cards label:hover { background: rgba(255, 255, 255, .03); }
.radio-cards label:has(input:checked) { border-color: var(--violet); background: rgba(167, 139, 250, .08); }
.radio-cards input { accent-color: var(--violet); width: 16px; height: 16px; margin-top: .15rem; flex: none; }
.radio-cards strong { display: block; color: var(--text); font-size: .95rem; font-weight: 600; }
.radio-cards em { display: block; font-style: normal; font-size: .82rem; color: var(--text-dim); margin-top: .2rem; }

.form code { font-family: var(--font-mono); font-size: .85em; color: var(--text-muted); }

/* ── Marketing nav ──────────────────────────────────────────────────────── */
.sitenav { align-self: stretch; display: flex; }

.sitenav__list {
  display: flex;
  align-items: stretch;
  gap: .35rem;
  list-style: none;
  margin: 0;
  padding: 0;
  height: 100%;
}

.sitenav__item { position: relative; display: flex; align-items: center; }

.sitenav__top {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  background: none;
  border: 0;
  padding: .5rem .7rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 400;
  color: var(--text);
  white-space: nowrap;
}

.sitenav__top:hover { color: var(--violet-bright); }
.sitenav__top.is-current { color: var(--violet-bright); }
span.sitenav__top { color: var(--text-dim); cursor: default; }

.sitenav__caret {
  width: 0;
  height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: .55;
}

.sitenav__panel {
  --panel-gap: .4rem;
  position: absolute;
  top: calc(100% + var(--panel-gap));
  left: 0;
  z-index: 600;
  background: var(--surface-2);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  padding: 1.1rem;
}

.sitenav__panel[hidden] { display: none; }

/* Overlaps the bar's bottom border by a few pixels so there is no uncovered
   row between the nav item and the panel for the cursor to fall through. */
.sitenav__panel::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * var(--panel-gap) - 4px);
  /* Runs a little way into the panel too, so the rounded top corners do not
     leave a sliver the cursor can fall through. */
  height: calc(var(--panel-gap) + 4px + 14px);
}

/* The mega panel spans the bar's container rather than hanging off its own
   nav item, which would push it past the left edge of the screen. */
.sitenav__item--industries { position: static; }

.sitenav__panel--industries {
  left: 0;
  right: 0;
  width: auto;
  padding: 1.6rem;
}

/* Six industry tiles: what you get, and the systems it sits in. */
.megamenu--industries {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

@media (max-width: 78rem) {
  .megamenu--industries { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.industry-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(18, 10, 34, .5);
  padding: 1rem 1.1rem 1.1rem;
  transition: border-color .18s ease, background .18s ease;
}
.industry-tile:hover { border-color: var(--violet); background: rgba(25, 16, 48, .85); }

.industry-tile__title { display: block; font-size: .95rem; font-weight: 600; transition: color .18s ease; }

.industry-tile__outcome {
  display: block;
  margin: .4rem 0 .8rem;
  font-size: .84rem;
  line-height: 1.45;
  color: var(--text-muted);
}

/* Third-party names, set as text rather than logo files. We have no permission
   to reproduce these marks, and a name states the same fact. */
.vendors {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}

.vendor {
  padding: .18rem .5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .04em;
  color: var(--text-dim);
  white-space: nowrap;
}

/* .vendors carries no top margin because everywhere else it sits inside a card
   whose padding already provides one. On a page it follows a heading directly,
   and butted straight against one it reads as part of it. */
.vendors--page { margin-top: 1.25rem; }
.vendors--page .vendor { font-size: .72rem; padding: .3rem .7rem; }

.dropdown { list-style: none; margin: 0; padding: 0; display: grid; gap: .15rem; min-width: 15rem; }

/* A described entry: the name, and the line that says what it is. Wide enough
   for the line to sit on two rows at most. */
.dropdown--described { min-width: 24rem; max-width: 26rem; }

.dropdown__entry {
  display: grid;
  gap: .15rem;
  padding: .55rem .6rem;
  border-radius: var(--radius);
  color: var(--text);
}
.dropdown__entry:hover { background: var(--surface-3); color: var(--text); }

.dropdown__name { font-size: .9rem; font-weight: 600; }
.dropdown__desc { font-size: .8rem; line-height: 1.45; color: var(--text-muted); }

.dropdown__entry--pending { cursor: default; }
.dropdown__entry--pending .dropdown__name,
.dropdown__entry--pending .dropdown__desc { color: rgba(125, 107, 158, .8); }

.dropdown__overview {
  display: block;
  padding: .3rem .6rem .6rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--violet);
}

/* The Technology Operator label does not tell anyone we run their department,
   so the panel says it before anything else. */
.dropdown__header {
  margin: 0 0 .7rem;
  padding: 0 .6rem .7rem;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .14em;
  color: var(--violet);
}

/* Under the link, opening to the right, the same as Products. Anchored to its
   right edge instead, a panel this wide reached back across Products and
   Industries and appeared to belong to one of them. */
.sitenav__panel--operator { left: 0; right: auto; }

/* Earlier than the layout breakpoints on purpose: the six nav items plus the
   sign-in links stop fitting well before the page itself needs to stack. */
@media (max-width: 68rem) {
  .sitenav { display: none; }
}

/* ── Marketing pages ────────────────────────────────────────────────────── */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  font-size: .78rem;
  color: var(--text-dim);
  margin-bottom: 1.1rem;
}
.crumbs__link { color: var(--text-dim); }
.crumbs__link:hover { color: var(--violet-bright); }
.crumbs__sep { opacity: .4; }
.crumbs__here { color: var(--text-muted); }

.page__lede {
  margin: .9rem 0 0;
  font-size: 1.05rem;
  color: var(--text-muted);
  max-width: var(--measure);
}

.placeholder {
  margin-top: 2.5rem;
  border: 1px dashed var(--border-bright);
  border-radius: var(--radius-lg);
  padding: 1.6rem;
  color: var(--text-dim);
}
.placeholder p { margin: 0; font-size: .92rem; }
.placeholder__label {
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--violet);
  margin-bottom: .5rem !important;
}

.owner {
  margin-top: 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem;
}
.owner--missing { border-color: rgba(255, 122, 138, .35); background: rgba(255, 122, 138, .05); }
.owner p { margin: 0; }
.owner__label {
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: .4rem !important;
}
.owner__name { font-weight: 600; }
.owner--missing .owner__name { color: var(--danger); }
.owner__note { margin-top: .5rem !important; font-size: .85rem; color: var(--text-dim); }
.owner code { font-family: var(--font-mono); font-size: .85em; }

.child-list {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
}

.child-card {
  display: flex;
  flex-direction: column;
  gap: .4rem;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .6);
  padding: 1.4rem;
  color: var(--text);
  transition: border-color .18s ease, background .18s ease;
}
.child-card:hover { border-color: var(--violet); background: rgba(25, 16, 48, .85); color: var(--text); }
.child-card h2 { font-size: 1.1rem; }
.child-card p { margin: 0; font-size: .88rem; color: var(--text-muted); }
.child-card__go { margin-top: auto; padding-top: .6rem; font-size: .78rem; color: var(--violet); }

/* ── Homepage ───────────────────────────────────────────────────────────── */
.hero-block, .strip, .band { width: 100%; }

.band__inner {
  max-width: var(--shell);
  margin-inline: auto;
  padding-block: clamp(3rem, 7vw, 5.5rem);
}
.band__inner--narrow { max-width: 46rem; }

.band + .band .band__inner { border-top: 1px solid var(--border); }

.section-title {
  font-size: clamp(1.75rem, 4vw, 2.6rem);
  letter-spacing: -.035em;
  max-width: 26ch;
}
.band--closing .section-title { max-width: 20ch; }
.section-title--sm { font-size: clamp(1.25rem, 2.6vw, 1.6rem); max-width: 28ch; }

.section-lead {
  margin: 1rem 0 0;
  font-size: 1.05rem;
  color: var(--text-muted);
  max-width: 58ch;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--violet);
  margin: 0 0 .75rem;
}

.prose-lg {
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--text-muted);
  max-width: 62ch;
  margin: 1.25rem 0 0;
}

/* Hero. Copy on the left, the platform answering a question on the right.
   Below 62rem the mock drops under the copy and both centre. */
.hero-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
  max-width: var(--shell);
  margin-inline: auto;
  padding-block: clamp(2rem, 6vw, 4rem) clamp(2.5rem, 6vw, 4rem);
}

@media (max-width: 62rem) {
  .hero-block { grid-template-columns: minmax(0, 1fr); text-align: center; }
}

.pill {
  display: inline-block;
  margin: 0 0 1.5rem;
  padding: .4rem 1rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .03);
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .06em;
  color: var(--text-muted);
}
.pill--good { border-color: rgba(105, 230, 163, .35); color: var(--green); }

.hero-block__headline {
  font-size: clamp(2.1rem, 5vw, 3.4rem);
  letter-spacing: -.04em;
  line-height: 1.06;
  max-width: 18ch;
}

.hero-block__subhead {
  margin: 1.5rem 0 0;
  max-width: 54ch;
  font-size: 1.05rem;
  color: var(--text-muted);
}

.hero-block__actions {
  margin-top: 2.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

/* Where we are, and the one partner status we hold. Generated from Office, so
   this line and the footer cannot disagree. */
.hero-block__locations {
  margin: 1.75rem 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--text-dim);
}
/* ── The hero's offices ──────────────────────────────────────────────────
   A row of triggers that has to keep reading as the quiet line of cities it
   was: same mono, same size, same dim colour. What changes is that it now
   answers the pointer, which it always looked as though it should.

   The separators are drawn rather than written. They were markup when this was
   a sentence; as a list they belong to the list, and a middot in the markup
   would be read out between every city. */
.office-list--hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
}

.office-item--hero + .office-item--hero::before {
  content: "\00b7";
  margin: 0 .5rem;
  opacity: .5;
}

.office-item--hero { display: flex; align-items: center; }

.office-item--hero .office__trigger {
  width: auto;
  font-family: inherit;
  border-radius: var(--radius);
}

.office-item--hero .office__city {
  display: inline;
  font-size: inherit;
  color: inherit;
  /* Dotted rather than solid: enough to say it can be pressed without turning
     a quiet line into six links shouting. */
  border-bottom: 1px dotted rgba(185, 169, 214, .45);
  padding-bottom: 1px;
  transition: color .2s ease, border-color .2s ease;
}

.office-item--hero .office__trigger:hover .office__city,
.office-item--hero .office__trigger:focus-visible .office__city {
  color: var(--text);
  border-bottom-color: var(--violet-bright);
}

/* ── The dialog's action ─────────────────────────────────────────────────── */
.modal__actions {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.modal__actions .btn { width: 100%; text-align: center; }

@media (max-width: 62rem) {
  .hero-block__headline { max-width: 22ch; margin-inline: auto; }
  .hero-block__subhead { margin-inline: auto; }
  .hero-block__actions, .hero-block__locations { justify-content: center; }
}

/* A call to action whose target is not built yet. */
.is-pending { opacity: .45; cursor: not-allowed; }

/* Rotating lines. Every line stays in the DOM; only one is shown. */
.rotator { display: grid; }
.rotator__item {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(.3em);
  transition: opacity .45s ease, transform .45s ease;
  pointer-events: none;
  margin: 0;
}
.rotator__item.is-active { opacity: 1; transform: none; pointer-events: auto; }
.hero-block__headline .rotator__item { color: var(--violet-bright); }

/* Pedigree marquee */
.strip { border-block: 1px solid var(--border); padding-block: 1.75rem; }

.strip__label {
  text-align: center;
  margin: 0 0 1.1rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.marquee {
  display: flex;
  overflow: hidden;
  /* No gap here. The track carries its own leading padding, and adding a gap
     on top of it doubled the space at the seam between the two copies. */
  gap: 0;
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.marquee__track {
  display: flex;
  flex: none;
  gap: 2.5rem;
  margin: 0;
  padding: 0 0 0 2.5rem;
  list-style: none;
  animation: marquee-scroll 42s linear infinite;
}

.marquee__track li { white-space: nowrap; }

/* Dimmed by default, brought up on hover. The strip pauses on hover, so a
   logo can be pointed at while the row is moving. */
.employer {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  opacity: .38;
  transition: opacity .3s ease, transform .3s ease;
}

.employer:hover { opacity: 1; transform: translateY(-1px); }

/* ── The name behind the mark ────────────────────────────────────────────
   Half of these are a glyph with no word in it, and a reader who does not know
   the mark learns nothing from it.

   The tooltip sits below the logo, inside the rail's own bottom padding,
   because a marquee clips its overflow on both axes - `overflow-x: hidden`
   forces the other axis to scroll whatever you ask for - so anything that
   escapes the box vertically is simply cut off. Giving the rail room and
   putting the tooltip in it is the only way this works without turning the
   clipping off, and turning it off would let the track spill sideways. */
.employer { position: relative; }

.employer__tip {
  position: absolute;
  top: calc(100% + .5rem);
  left: 50%;
  translate: -50% 0;
  padding: .3rem .6rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: rgba(6, 3, 14, .96);
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .06em;
  white-space: nowrap;
  color: var(--text);
  opacity: 0;
  visibility: hidden;
  transition: opacity .18s ease, visibility .18s;
  pointer-events: none;
}

.employer:hover .employer__tip { opacity: 1; visibility: visible; }

/* Room underneath for the tooltip to live in, since it cannot leave the box.
   Measured rather than guessed: the tip is about 28px tall and sits half a rem
   below the mark, and 2.1rem left the last two pixels of it under the clip. */
.marquee--employers { padding-bottom: 2.6rem; }

/* On About the rail carries the section on its own, so it gets the air the
   standfirst used to take up. */
.about__employers { margin-top: 1.75rem; }

@media (prefers-reduced-motion: reduce) {
  .employer__tip { transition: none; }
}

/* A logo arrives in whatever colours it was drawn in. Flattening every one to
   the same white silhouette keeps the row even, whatever gets dropped in. */
/* Every logo fits inside the same box rather than matching on height alone.
   These marks run from a square pecten to a wordmark five times as wide, and
   sizing them all to one height made the square ones look tiny next to the
   wordmarks. Fitting to a box evens out how much room each one takes. */
/* Height comes from Employer#display_height, which evens out how much room
   each mark takes. Width follows from the logo's own proportions. */
.employer__logo {
  width: auto;
  max-width: 7rem;
  object-fit: contain;
  filter: brightness(0) invert(1);
}

.employer__name {
  font-size: .95rem;
  color: var(--text);
  letter-spacing: -.01em;
}

@media (prefers-reduced-motion: reduce) {
  .employer { transition: opacity .3s ease; }
  .employer:hover { transform: none; }
}

.marquee:hover .marquee__track { animation-play-state: paused; }

@keyframes marquee-scroll {
  to { transform: translateX(-100%); }
}

/* Comparison */
.compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
}

.compare__card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.75rem;
}
.compare__card--us { border-color: var(--violet); background: rgba(105, 67, 230, .1); }

.compare__title { font-size: 1.05rem; margin-bottom: 1.1rem; }
.compare__card--us .compare__title { color: var(--violet-bright); }

.compare__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.compare__list li { display: flex; gap: .65rem; align-items: baseline; font-size: .95rem; color: var(--text-muted); }
.compare__list li::before { flex: none; font-family: var(--font-mono); font-size: .8rem; }
.compare__list--them li::before { content: "\00d7"; color: var(--text-dim); }
.compare__list--us li::before { content: "\2713"; color: var(--green); }

/* ── Thank you page ─────────────────────────────────────────────────────── */
.confirmed { width: 100%; max-width: 46rem; margin-inline: auto; }
.confirmed__block { padding-block: 2.25rem; }
.confirmed__block + .confirmed__block { border-top: 1px solid var(--border); }

.confirmed__block--feature {
  border: 1px solid var(--violet);
  border-radius: var(--radius-lg);
  background: rgba(105, 67, 230, .1);
  padding: 1.75rem;
  margin-block: 1rem;
}
.confirmed__block--feature .btn { margin-top: 1.5rem; }

.confirmed__note {
  margin: 1.5rem 0 0;
  font-size: .92rem;
  color: var(--text-dim);
  max-width: 58ch;
}

.meeting-host { display: flex; gap: 1.25rem; margin-top: 1.5rem; flex-wrap: wrap; }

.meeting-host__photo {
  flex: none;
  width: 92px;
  height: 92px;
  border-radius: var(--radius-lg);
  border: 1px dashed var(--border-bright);
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.meeting-host__detail p { margin: 0; }
.meeting-host__name { font-size: 1.1rem; font-weight: 600; }
.meeting-host__role { font-size: .9rem; color: var(--violet-bright); margin-bottom: .5rem !important; }
.meeting-host__line { font-size: .9rem; color: var(--text-muted); }

.steps { margin: 1.5rem 0 0; padding-left: 1.2rem; display: grid; gap: .75rem; color: var(--text-muted); }
/* The bare list nudges its items off the marker. A .step is a card and sets its
   own padding, and this was winning on specificity and squashing the left of
   every one of them. */
.steps > li:not(.step) { padding-left: .35rem; }

.confirmed__reading { margin: 1.25rem 0 0; color: var(--text-muted); }

/* Anything still carrying a placeholder is marked, so it cannot ship unnoticed. */
[data-pending-fee], [data-pending-name], [data-pending-booking],
[data-pending-reading], [data-pending-photo] {
  outline: 1px dashed rgba(255, 122, 138, .35);
  outline-offset: 2px;
  border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .rotator__item { transition: none; }
}

/* Confirmation pages carry the brand and nothing else. */
.topbar--minimal .topbar__inner { justify-content: flex-start; }

/* ── Anchor targets ─────────────────────────────────────────────────────── */
/* The bar is sticky, so a plain anchor jump lands the heading underneath it.
   Push targets down by the bar's height plus a little air. */
section[id],
.band[id],
[id].hero-block {
  scroll-margin-top: calc(var(--bar-height) + var(--ticker-h) + 1.5rem);
}

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ── Modals ─────────────────────────────────────────────────────────────── */
.modal__intro {
  margin: -.9rem 0 1.5rem;
  font-size: .92rem;
  color: var(--text-muted);
}

.form--modal { gap: 1rem; }
.form--modal .errors ul { margin: 0; padding-left: 1.1rem; }

.hint--warn {
  border: 1px dashed rgba(255, 122, 138, .35);
  border-radius: var(--radius);
  padding: .7rem .85rem;
  color: var(--text-dim);
}

.recaptcha { min-height: 78px; }

.diagnostic__pricing { margin: 1rem 0 0; }

/* The hero badge names systems, so it needs room to breathe and to wrap. */
.pill--tech {
  max-width: min(44rem, 100%);
  white-space: normal;
  line-height: 1.6;
}

/* ── Reveal on scroll ───────────────────────────────────────────────────── */
/* Content is always in the markup. This only changes how it arrives. */
.will-reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .55s cubic-bezier(.22, .61, .36, 1),
              transform .55s cubic-bezier(.22, .61, .36, 1);
}
.is-revealed { opacity: 1; transform: none; }

/* ── Estate scan panel ──────────────────────────────────────────────────── */
.scan {
  margin-top: 1.75rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .9), rgba(11, 6, 20, .9));
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}

.scan__chrome {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--border);
  background: rgba(5, 3, 10, .6);
}

.scan__dots { display: inline-flex; gap: .35rem; }
.scan__dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--border-bright); }
.scan__dots i:nth-child(1) { background: #FF5F57; }
.scan__dots i:nth-child(2) { background: #FEBC2E; }
.scan__dots i:nth-child(3) { background: #28C840; }

.scan__name {
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--text-muted);
  letter-spacing: .04em;
}

.scan__badge {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: .15rem .6rem;
}

.scan__body {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  min-height: 19rem;
}

@media (max-width: 44rem) {
  .scan__body { grid-template-columns: 1fr; }
}

.scan__panel { padding: 1.25rem; }
.scan__panel--findings { border-right: 1px solid var(--border); position: relative; }

@media (max-width: 44rem) {
  .scan__panel--findings { border-right: 0; border-bottom: 1px solid var(--border); }
}

.scan__label {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0 0 1rem;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.scan__live { margin-left: auto; display: inline-flex; align-items: center; gap: .35rem; color: var(--green); }

.scan__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--green);
  animation: scan-pulse 1.8s ease-in-out infinite;
}

@keyframes scan-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .25; }
}

.scan__log { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }

.scan__finding {
  display: flex;
  gap: .6rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  line-height: 1.55;
  color: var(--text-muted);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .4s ease, transform .4s ease;
}
.scan__finding.is-shown { opacity: 1; transform: none; }
/* A check mark, not a terminal prompt. The angle bracket read as a console,
   which is the wrong register for the practice manager and the family office
   this band also sits under. */
.scan__prompt {
  color: var(--violet-bright);
  flex: none;
  font-size: .9em;
  line-height: 1.5;
}

.scan__cursor {
  display: inline-block;
  width: 7px;
  height: 15px;
  margin-top: .7rem;
  background: var(--violet);
  animation: scan-blink 1.1s step-end infinite;
}

@keyframes scan-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

.scan__checks { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.scan__check {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .6rem .8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .02);
  transition: border-color .3s ease, background .3s ease;
}

.scan__check-name { font-size: .85rem; color: var(--text); }

.scan__status {
  flex: none;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: .18rem .55rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  color: var(--text-dim);
  transition: color .3s ease, border-color .3s ease;
}

.scan__check[data-state="running"] { border-color: rgba(254, 188, 46, .4); background: rgba(254, 188, 46, .06); }
.scan__check[data-state="running"] .scan__status { color: #FEBC2E; border-color: rgba(254, 188, 46, .45); }

.scan__check[data-state="done"] { border-color: rgba(105, 230, 163, .35); }
.scan__check[data-state="done"] .scan__status { color: var(--green); border-color: rgba(105, 230, 163, .4); }

.scan__deliverable {
  margin: 1.1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  opacity: .5;
  transition: opacity .5s ease, color .5s ease;
}
.scan.is-complete .scan__deliverable { opacity: 1; color: var(--violet-bright); }

/* ── The morning list ───────────────────────────────────────────────────── */
.backlog {
  margin-top: 2.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .55);
  overflow: hidden;
}

.backlog__label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0;
  padding: .8rem 1.1rem;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.backlog__clock { color: var(--violet); }

.backlog__list { list-style: none; margin: 0; padding: .5rem; display: grid; gap: .3rem; }

.backlog__row {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .65rem .8rem;
  border-radius: var(--radius);
  font-size: .9rem;
  transition: background .4s ease, color .4s ease, opacity .4s ease;
}

/* Incidents start collapsed. Expanding them is what pushes the work down. */
.backlog__row--incident {
  max-height: 0;
  padding-block: 0;
  opacity: 0;
  overflow: hidden;
  background: rgba(255, 122, 138, .07);
  color: var(--text);
  transition: max-height .45s cubic-bezier(.22, .61, .36, 1),
              padding .45s cubic-bezier(.22, .61, .36, 1),
              opacity .35s ease;
}

.backlog__row--incident.is-in { max-height: 3.5rem; padding-block: .65rem; opacity: 1; }

.backlog__row--work { background: rgba(255, 255, 255, .02); color: var(--text-muted); }
.backlog__row--work.is-sinking { color: var(--text-dim); }

.backlog__flag {
  flex: none;
  width: 1.1rem;
  text-align: center;
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--danger);
}
.backlog__flag--quiet { color: var(--text-dim); font-weight: 400; }

.backlog__task { flex: 1; min-width: 0; }

.backlog__tag {
  flex: none;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--danger);
}

.backlog__tag--waiting { color: var(--text-dim); }
.backlog__row--work.is-sinking .backlog__tag--waiting { color: rgba(125, 107, 158, .6); }

@media (prefers-reduced-motion: reduce) {
  .backlog__row--incident { transition: none; }
}

/* ── Two weeks against two quarters ─────────────────────────────────────── */
.race {
  margin-top: 2rem;
  display: grid;
  gap: .85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.35rem 1.5rem;
}

.race__lane {
  display: grid;
  grid-template-columns: 8rem 1fr auto;
  align-items: center;
  gap: 1rem;
}

@media (max-width: 40rem) {
  .race__lane { grid-template-columns: 1fr; gap: .35rem; }
}

.race__name {
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.race__track {
  position: relative;
  height: 8px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .05);
  overflow: hidden;
}

/* The resting state is the finished comparison, so a paused animation, a
   background tab or no JavaScript at all still makes the point. The animation
   only decides how it gets there. */
.race__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--violet-deep), var(--violet));
}

.race__lane--slow .race__fill { width: 34%; background: rgba(125, 107, 158, .55); }

.race__state {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.race__lane--fast .race__state { color: var(--green); }

/* The fast lane completes early and reports. The slow one is still crawling
   when the cycle restarts. */
.race.is-running .race__lane--fast .race__fill { animation: race-fast 2.4s cubic-bezier(.3, .8, .4, 1) forwards; }
.race.is-running .race__lane--slow .race__fill { animation: race-slow 7.2s linear forwards; }

@keyframes race-fast { from { width: 0; } to { width: 100%; } }
@keyframes race-slow { from { width: 0; } to { width: 34%; } }

@media (prefers-reduced-motion: reduce) {
  .race.is-running .race__fill { animation: none; }
}

/* ── Product sections ───────────────────────────────────────────────────── */
/* What is left of the layout that ran one long section per product down the
   home page. Those became a single tabbed section, styled under .demos, and
   only the two pieces it still renders survive here. A
   `#products > .band__inner { padding-bottom: 0 }` rule went with them: it was
   written for a layout where the product articles sat outside the inner, and
   left the section with no bottom padding once they moved inside it. */
.product__eyebrow {
  margin: 0 0 .7rem;
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--violet);
}

.product__link { margin: 1.5rem 0 0; }

/* An onward link that looks like one without being a button. */
.arrow-link { font-size: .92rem; font-weight: 500; color: var(--violet-bright); }
.arrow-link::after { content: " \2192"; }
.arrow-link:hover { color: var(--text); }
span.arrow-link { color: rgba(125, 107, 158, .8); cursor: default; }

/* Assess, fix, connect. The order of work, stated wherever a product is. */
.steps {
  list-style: none;
  counter-reset: step;
  margin: 1.75rem 0 0;
  padding: 0;
  display: grid;
  gap: .6rem;
}

.steps--wide { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 1rem; }

.step {
  counter-increment: step;
  display: grid;
  gap: .5rem;
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(18, 10, 34, .5);
}

.step__name {
  font-family: var(--font-mono);
  font-size: .68rem;
  line-height: 1.5;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--violet);
}
.step__name::before { content: counter(step, decimal-leading-zero) " "; opacity: .55; }

.step__body { font-size: .9rem; line-height: 1.65; color: var(--text-muted); }

/* The plain numbered list, used where the steps are prose rather than named. */
.steps--plain {
  list-style: decimal;
  counter-reset: none;
  margin: 1.25rem 0 0;
  padding-left: 1.2rem;
  gap: .6rem;
  display: grid;
}
.steps--plain li { color: var(--text-muted); }

/* ── Industry cards ─────────────────────────────────────────────────────── */
.industries {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1rem;
}

.industry {
  display: flex;
  flex-direction: column;
  gap: .9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.75rem;
}

.industry__title { font-size: 1.15rem; max-width: 24ch; }
.industry__body { margin: 0; font-size: .92rem; color: var(--text-muted); }
/* Pinned to the bottom so the links line up across cards of different lengths. */
.industry__link { margin: auto 0 0; padding-top: .3rem; }

.industries__note {
  margin: 1.5rem 0 0;
  font-size: .88rem;
  color: var(--text-dim);
}

/* The rotating question above the grid. */
.rotator--quote {
  margin: 1.75rem 0 0;
  min-height: 3.2rem;
  font-size: clamp(1.15rem, 2.4vw, 1.5rem);
  letter-spacing: -.02em;
  line-height: 1.35;
  color: var(--violet-bright);
}

/* ── Technology Operator tiles ──────────────────────────────────────────── */
.operator-tiles {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}

.operator-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.5rem;
}

.operator-tile__title { font-size: 1rem; margin-bottom: .55rem; }
.operator-tile__title a { color: var(--text); }
.operator-tile__title a:hover { color: var(--violet-bright); }
.operator-tile__title span { color: rgba(125, 107, 158, .8); }
.operator-tile__body { margin: 0; font-size: .88rem; color: var(--text-muted); }

.operator__note {
  margin: 1.5rem 0 0;
  font-size: .9rem;
  color: var(--text-dim);
  max-width: 62ch;
}

/* ── Proof grid ─────────────────────────────────────────────────────────── */
.proof {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}

.proof__item {
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(18, 10, 34, .5);
  font-size: .92rem;
  color: var(--text-muted);
}

.compare { margin-top: 2.5rem; }

/* ── Interface mocks ────────────────────────────────────────────────────── */
/* Every mock is an illustration built here, not a screenshot. None of them
   states a figure: the charts carry shape, the pills carry a state word, and
   the bar says "Example view" so nobody reads one as a client's numbers. */
.mock {
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
  overflow: hidden;
  text-align: left;
}

.mock__bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .15rem .6rem;
  padding: .6rem .9rem;
  border-bottom: 1px solid var(--border);
  background: rgba(5, 3, 10, .5);
}

.mock__dots { display: inline-flex; gap: .3rem; }
.mock__dots i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border-bright);
}

/* The bar wraps as a whole rather than breaking a name across two lines. */
.mock__title {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  color: var(--text-muted);
  white-space: nowrap;
}

.mock__note {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.mock__body { padding: 1.1rem; display: grid; gap: 1rem; }

.mock__caption {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.mock__caption--foot { border-top: 1px solid var(--border); padding-top: .8rem; letter-spacing: .04em; text-transform: none; font-size: .78rem; }

/* Two columns inside a panel, stacking on narrow screens. */
.panel__split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1rem;
}

.panel__cell { display: grid; gap: .5rem; align-content: start; }

.panel__cell-label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Bars. No value axis, deliberately: a figure drawn here is a figure we made up. */
.chart-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }

.chart-bars__row {
  display: grid;
  grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr);
  align-items: center;
  gap: .6rem;
}

.chart-bars__label { font-size: .74rem; color: var(--text-muted); white-space: nowrap; }

.chart-bars__track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .06);
  overflow: hidden;
}

.chart-bars__fill { display: block; height: 100%; border-radius: var(--radius-pill); }
.chart-bars__fill--base  { background: var(--violet); }
.chart-bars__fill--muted { background: var(--border-bright); }
.chart-bars__fill--good  { background: var(--green); }
.chart-bars__fill--warn  { background: var(--danger); }

/* Trend lines. A shape, with no axis and no scale. */
.chart-spark { width: 100%; height: 3.2rem; display: block; }
.chart-spark__line { fill: none; stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.chart-spark__area { stroke: none; }
.chart-spark--violet .chart-spark__line { stroke: var(--violet-bright); }
.chart-spark--violet .chart-spark__area { fill: rgba(167, 139, 250, .14); }
.chart-spark--warn .chart-spark__line { stroke: var(--danger); }
.chart-spark--warn .chart-spark__area { fill: rgba(255, 122, 138, .12); }

/* The quiet tone. It had no rule at all, which meant its line had no stroke and
   its area kept the browser's default black fill - so anywhere a spark asked
   for "muted" it drew a black block with an invisible line on top of it. The
   pricing table made that obvious because it had eight of them in a column. */
.chart-spark--muted .chart-spark__line { stroke: var(--text-dim); }
.chart-spark--muted .chart-spark__area { fill: rgba(185, 169, 214, .10); }

/* Rows: a name, what it is, and a state word. Never a figure. */
.chart-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; }

.chart-rows__row {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: .6rem;
  padding: .45rem .6rem;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .03);
}

.chart-rows__name { font-size: .78rem; font-weight: 500; color: var(--text); }
.chart-rows__meta { font-size: .7rem; color: var(--text-dim); }

.chart-rows__pill {
  justify-self: end;
  padding: .1rem .5rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .06em;
  white-space: nowrap;
  border: 1px solid var(--border-bright);
  color: var(--text-muted);
}
.chart-rows__pill--good { border-color: rgba(105, 230, 163, .4); color: var(--green); }
.chart-rows__pill--warn { border-color: rgba(255, 122, 138, .4); color: var(--danger); }

/* ── The industry switcher ──────────────────────────────────────────────── */
/* Nothing is hidden until the tabs controller connects and adds .is-tabbed.
   Without JavaScript the reader gets all six dashboards stacked and headed by
   their industry: longer, never wrong. */
/* The chooser is hidden until the controller connects, because without it the
   six dashboards are simply stacked and there is nothing to choose between. */
.dashboards__chooser { display: none; }

.is-tabbed .dashboards__chooser {
  display: block;
  margin-bottom: .9rem;
}

.dashboards__label {
  margin: 0 0 .5rem;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Outside the window frame, so it reads as a control the reader operates
   rather than as one app's own tab strip. */
.mock-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}

/* On a phone every one of these strips wraps, and the rows it wraps to are
   most of what the section has left after its heading - the credentials strip
   was five tabs on three rows. One row that scrolls sideways instead: the same
   tabs, at the same size, in a third of the height.
 *
 * The scrollbar is hidden because the pills say it themselves - a strip cut off
 * mid-pill reads as scrollable without needing to be labelled - and the tabs
 * controller scrolls the chosen one into view, so nothing is ever picked and
 * then left off screen. */
@media (max-width: 62rem) {
  .mock-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* A strip that will not wrap is as wide as its tabs, and a flex or grid
       item is floored at its content unless it is told otherwise - so without
       this the strip does not scroll, it pushes its whole section past the edge
       of the screen and takes the page with it. `overflow-x` decides what
       happens to the content; `min-width` is what decides the box. */
    min-width: 0;
    max-width: 100%;
    /* The marker is positioned inside this box and would be clipped at the
       bottom edge without a little room under it. */
    padding-bottom: .3rem;
  }

  .mock-tabs::-webkit-scrollbar { display: none; }
}

.mock-tabs__tab {
  flex: none;
  background: rgba(18, 10, 34, .6);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: .35rem .8rem;
  font-family: var(--font-body);
  font-size: .78rem;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.mock-tabs__tab:hover { color: var(--text); border-color: var(--border-bright); }
.mock-tabs__tab.is-active {
  color: var(--text);
  border-color: var(--violet);
  background: rgba(105, 67, 230, .18);
}

/* Which industry the window is showing. The chooser is outside the frame now,
   so without this the two read as unrelated things. */
.mock__scope {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .06em;
  color: var(--violet-bright);
  white-space: nowrap;
}
/* Empty until the controller names a panel, and an empty one must draw
   nothing at all, separator included. */
.mock__scope:not(:empty)::before { content: "/ "; color: var(--text-dim); }

.mock-panel { display: grid; gap: .9rem; }
/* A class with a display of its own outranks the browser's own [hidden] rule,
   and without this the tabs controller hides a panel and nothing happens: all
   six dashboards stay stacked. */
.mock-panel[hidden] { display: none; }
.mock-panel + .mock-panel { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }

/* Once tabbed, the heading is the tab, so the in-panel one goes and the panels
   no longer need rules between them. */
.is-tabbed .mock-panel__heading { display: none; }
.is-tabbed .mock-panel + .mock-panel { margin-top: 0; padding-top: 0; border-top: 0; }

.mock-panel__heading { margin: 0; font-size: .95rem; color: var(--violet-bright); }

.mock-panel__ask {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .03);
}

.mock-panel__ask-label,
.ask__label {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--violet);
}

.mock-panel__question { font-size: .82rem; color: var(--text); }

.mock-panel__answer {
  margin: 0;
  font-size: .86rem;
  color: var(--text-muted);
  padding-left: .7rem;
  border-left: 2px solid var(--violet-deep);
}

/* Keeps the panels from jumping about as the switcher moves between
   dashboards of different heights. */
.is-tabbed .mock--dashboards .mock__body { min-height: 21rem; align-content: start; }

/* ── The hero mock ──────────────────────────────────────────────────────── */
.ask__prompt {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .7rem .8rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .03);
  min-height: 3.1rem;
}

.ask__text { font-size: .9rem; color: var(--text); }

/* Only shown while the question is being typed. */
.ask__caret { display: none; }
.is-typing .ask__caret {
  display: inline-block;
  width: 2px;
  height: 1em;
  translate: 0 .15em;
  background: var(--violet-bright);
  animation: ask-blink 1s steps(2, start) infinite;
}

@keyframes ask-blink { 50% { opacity: 0; } }

/* The answer and the reminder arrive; the resting state is both shown. */
.ask__answer, .ask__reminder {
  opacity: 1;
  transform: none;
  transition: opacity .45s ease, transform .45s ease;
}
.is-typing .ask__answer,
.is-typing .ask__reminder { opacity: 0; transform: translateY(.4rem); }
.ask__answer.is-shown, .ask__reminder.is-shown { opacity: 1; transform: none; }

.ask__answer { display: grid; gap: .7rem; }
.ask__answer-line { margin: 0; font-size: .88rem; color: var(--text-muted); }

.ask__reminder {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .55rem .7rem;
  border: 1px solid rgba(105, 230, 163, .3);
  border-radius: var(--radius);
  background: rgba(105, 230, 163, .06);
  font-size: .8rem;
  color: var(--text-muted);
}
.ask__reminder-mark { color: var(--green); }

/* Nothing above may animate for someone who has asked for less motion. The
   resting state is the finished one, so this simply holds it there. */
@media (prefers-reduced-motion: reduce) {
  .ask__answer, .ask__reminder { transition: none; opacity: 1; transform: none; }
  .is-typing .ask__caret { animation: none; }
}

/* ── The elasticity curve ───────────────────────────────────────────────── */
.curve { width: 100%; height: auto; display: block; }
.curve__grid line { stroke: var(--border); stroke-width: 1; }
.curve__area { fill: rgba(167, 139, 250, .12); }
.curve__line { fill: none; stroke: var(--violet-bright); stroke-width: 2; }
.curve__band { fill: rgba(105, 230, 163, .12); stroke: rgba(105, 230, 163, .45); stroke-width: 1; }
.curve__now { stroke: var(--text-dim); stroke-width: 1; stroke-dasharray: 3 3; }
.curve__point { fill: var(--text-dim); }
.curve__point--target { fill: var(--green); }
.curve__axis {
  fill: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.curve__key {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  font-size: .72rem;
  color: var(--text-dim);
}
.curve__key li { display: flex; align-items: center; gap: .4rem; }

.curve__swatch { width: 12px; height: 8px; border-radius: 2px; display: inline-block; }
.curve__swatch--now { background: var(--text-dim); }
.curve__swatch--band { background: rgba(105, 230, 163, .35); border: 1px solid rgba(105, 230, 163, .5); }

/* ── Marketing page bodies ──────────────────────────────────────────────── */
/* Width stated rather than inherited. This block is nothing but text, so if it
   is ever sized to its content instead of its container the result is a column
   of single words - and it is the first thing on the page. */
.page__head { width: 100%; max-width: 46rem; }

/* Narrower than the site's general measure. The headline above it is capped at
   about twenty characters, and a standfirst running the full sixty-eight under
   that reads as two different columns rather than one block. */
.page__head .page__lede { max-width: 52ch; }
.page__head .page__title { max-width: 20ch; }

.page__visual { margin-top: 2.5rem; }

.page__section {
  margin-top: 3rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--border);
}

.page__note {
  margin: 1rem 0 0;
  font-size: .82rem;
  color: var(--text-dim);
  max-width: 62ch;
}

.page--product .page__head .page__title,
.page--industry .page__head .page__title { max-width: 22ch; }

/* The other things in the same family, listed at the foot of a page. */
.siblings {
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

.sibling {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.4rem;
}
.sibling__title { font-size: 1rem; margin-bottom: .45rem; }
.sibling__title a { color: var(--text); }
.sibling__title a:hover { color: var(--violet-bright); }
.sibling__title span { color: rgba(125, 107, 158, .8); }
.sibling__body { margin: 0; font-size: .88rem; color: var(--text-muted); }

.siblings--cards .sibling { display: flex; flex-direction: column; gap: .5rem; }
.siblings--cards .product__link { margin: auto 0 0; padding-top: .5rem; }

/* ── Book a call ────────────────────────────────────────────────────────── */
/* Narrower than the shell on purpose. Four form fields do not need 80rem, and
   an input the width of the page is unpleasant to aim at. */
.contact {
  max-width: 64rem;
  margin-top: 2.5rem;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}

@media (max-width: 62rem) {
  .contact { grid-template-columns: minmax(0, 1fr); }
}

.form--contact {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .5);
  padding: 1.75rem;
}

.contact__aside .section-title--sm { margin-top: 0; }
.contact__aside .section-title--sm + * { margin-top: .9rem; }
.contact__aside .office-list--contact { margin-top: .9rem; }
.contact__aside .section-title--sm ~ .section-title--sm { margin-top: 2rem; }

.contact__email { margin: 1rem 0 0; font-size: .9rem; }

.office-list--contact, .office-list--about {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.25rem;
}

/* ── Footer legal line ──────────────────────────────────────────────────── */
.footer-legal {
  margin: 1.25rem 0 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: .76rem;
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 78ch;
}

/* ── The risk list ──────────────────────────────────────────────────────── */
/* Reuses the backlog animation, whose mechanic is exactly this one: arrivals
   land on top and push what is already there further down. */
.backlog--risk { margin-top: .25rem; }
.backlog--risk .backlog__task { display: grid; gap: .1rem; }
.backlog__why { font-size: .7rem; color: var(--text-dim); }

/* ── The nav below 62rem ────────────────────────────────────────────────── */
/* A <details> disclosure rather than a controller. This is the only way to
   reach anything on a phone, so it must not wait for a script. */
.mobilenav { display: none; margin-left: auto; }

@media (max-width: 68rem) {
  .mobilenav { display: block; position: static; }

  /* Client sign in and Lock site wrap to two lines each once the Menu button
     is in the bar. Both are in the footer, so the bar gives up its copies. */
  .topbar__end { display: none; }
}

.mobilenav__toggle {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem .8rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  font-size: .82rem;
  color: var(--text);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.mobilenav__toggle::-webkit-details-marker { display: none; }

.mobilenav__icon { display: grid; gap: 3px; }
.mobilenav__icon i {
  display: block;
  width: 14px;
  height: 1.5px;
  background: currentColor;
  border-radius: 1px;
}
.mobilenav[open] .mobilenav__icon i:nth-child(2) { opacity: .3; }

/* Anchored to the bar, not to the button, so it spans the screen. */
.mobilenav__panel {
  position: absolute;
  top: calc(var(--bar-height) + var(--ticker-h));
  left: 0;
  right: 0;
  z-index: 700;
  max-height: calc(100dvh - var(--bar-height) - var(--ticker-h));
  overflow-y: auto;
  background: var(--surface-2);
  border-top: 1px solid var(--bar-border);
  border-bottom: 1px solid var(--border-bright);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
  padding: 1rem var(--gutter) 1.75rem;
}

.mobilenav__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; }

.mobilenav__group { padding-top: .2rem; }

/* Each group is its own <details>. The summary carries the group name and the
   chevron and does nothing else: a summary that both navigates and toggles does
   neither predictably, so the hub page is the first link inside instead. */
/* Scoped to the details so it outranks .mobilenav__top's `display: block`,
   which is on the same element and comes later in this file. */
.mobilenav__section > .mobilenav__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  cursor: pointer;
  list-style: none;
}

.mobilenav__summary::-webkit-details-marker { display: none; }
.mobilenav__summary::marker { content: ""; }

.mobilenav__chevron {
  display: block;
  flex: none;
  width: .5rem;
  height: .5rem;
  border-right: 1.5px solid var(--text-dim);
  border-bottom: 1.5px solid var(--text-dim);
  transform: translateY(-.15em) rotate(45deg);
  transition: transform .2s ease;
}

.mobilenav__section[open] .mobilenav__chevron { transform: translateY(.1em) rotate(225deg); }
.mobilenav__section[open] .mobilenav__summary { color: var(--violet-bright); }

/* The group's own page, set apart from the things inside it. */
.mobilenav__link--hub { color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .mobilenav__chevron { transition: none; }
}

.mobilenav__top {
  display: block;
  padding: .55rem 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}
span.mobilenav__top { color: var(--text-dim); }

.mobilenav__top--cta {
  margin-top: 1rem;
  text-align: center;
  border-radius: var(--radius-pill);
  background: var(--violet-deep);
  padding: .7rem 1rem;
}
.mobilenav__top--cta:hover { color: var(--text); }

.mobilenav__sub {
  list-style: none;
  margin: 0 0 .5rem;
  padding: 0 0 0 .9rem;
  border-left: 1px solid var(--border);
  display: grid;
  gap: .1rem;
}

.mobilenav__link { display: block; padding: .4rem 0; font-size: .9rem; color: var(--text-muted); }
.mobilenav__link:hover { color: var(--violet-bright); }
span.mobilenav__link { color: rgba(125, 107, 158, .8); }

/* ════════════════════════════════════════════════════════════════════════════
   The rebuilt home page.

   Everything below belongs to sections written after the original page: the
   hero brain, the hold, the sector tabs, the services list, the product demos
   and the credentials band. It sits at the end of the file on purpose. Several
   of these rules exist to beat an earlier one (a band's top rule, the marquee's
   duration, the industry tile's colours), and being last is what makes that
   true without a specificity argument in every selector.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── The whole industry tile is the link ────────────────────────────────── */
.industry-tile { display: block; color: var(--text); }
.industry-tile:hover { color: var(--text); }
.industry-tile:hover .industry-tile__title { color: var(--violet-bright); }
.industry-tile--pending { cursor: default; }
.industry-tile--pending .industry-tile__title { color: rgba(125, 107, 158, .8); }

/* ── Marquee, parameterised ─────────────────────────────────────────────── */
/* The employer strip and the credential strips are the same control at
   different sizes and speeds, so the two things that differ became variables.
   Every copy of a track moves left by one track width, which means running the
   same keyframes backwards moves it right and needs no second animation. */
.marquee__track {
  gap: var(--marquee-gap, 2.5rem);
  padding-left: var(--marquee-gap, 2.5rem);
  animation-duration: var(--marquee-dur, 42s);
}

.marquee--reverse .marquee__track { animation-direction: reverse; }

/* ── The hero brain ─────────────────────────────────────────────────────── */
/* A network in the shape of a brain: nodes, the wiring between them, and
   signals travelling in from the rim towards the middle. Pure SVG and CSS.

   The pulse relies on pathLength being normalised to 100, so every signal
   takes the same share of its own animation whatever its real length. */
.brain {
  position: relative;
  width: 100%;
  margin-block: -6%;
}

/* The light the network is sitting in. A blurred radial behind the mesh does
   in one element what a bigger SVG blur would cost on eight hundred. */
.brain::before {
  content: "";
  position: absolute;
  inset: 4% 2%;
  border-radius: 50%;
  background: radial-gradient(58% 54% at 52% 46%, rgba(124, 88, 245, .4), rgba(88, 56, 208, .16) 52%, transparent 74%);
  filter: blur(28px);
}

.brain__net { position: relative; }

.brain svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  filter: url(#brain-glow);
}

/* --o is how far forward the thing is, worked out at generate time from its
   position in the volume and written into the markup. Opacity is the strongest
   depth cue there is after overlap, and putting it on stroke-opacity rather
   than opacity leaves the flicker animation, which owns opacity, alone. */
.brain__edge {
  stroke: var(--violet-bright);
  stroke-width: 1;
  stroke-opacity: var(--o, .5);
}

.brain__fibre {
  fill: none;
  stroke: #cbb6ff;
  stroke-width: 1.7;
  stroke-opacity: .62;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.brain__pulse {
  fill: none;
  stroke: #fff;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 9 91;
  stroke-dashoffset: 100;
  opacity: 0;
  animation: brain-pulse var(--dur, 8s) linear infinite;
  animation-delay: var(--delay, 0s);
}

@keyframes brain-pulse {
  0%   { stroke-dashoffset: 100; opacity: 0; }
  10%  { opacity: .95; }
  72%  { opacity: .95; }
  86%  { stroke-dashoffset: 0; opacity: 0; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

.brain__node {
  fill: #d5c6ff;
  fill-opacity: var(--o, 1);
  animation: brain-flicker 5.5s ease-in-out infinite;
  animation-delay: var(--delay, 0s);
}

.brain__hub {
  fill: #fff;
  fill-opacity: var(--o, 1);
  animation: brain-hub 4.5s ease-in-out infinite;
  animation-delay: var(--delay, 0s);
}

@keyframes brain-flicker {
  0%, 100% { opacity: .6; }
  50%      { opacity: 1; }
}

@keyframes brain-hub {
  0%, 100% { opacity: .7; r: 3.2; }
  50%      { opacity: 1;  r: 4.2; }
}

/* ── What the brain is thinking ─────────────────────────────────────────── */
/* The questions an operating partner is carrying, surfacing over the network
   one at a time. The svg is width 100% with height auto and a 620x560 viewBox,
   so the list can be positioned in plain percentages and every thought lands
   exactly where config said, whatever size the hero is. */
.thoughts {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  pointer-events: none;
}

.thought {
  position: absolute;
  display: flex;
  align-items: center;
  gap: .55rem;
  max-width: 14.5rem;
  padding: .55rem .8rem;
  border: 1px solid var(--violet);
  border-radius: var(--radius);
  /* Near-black rather than transparent: a thought has to be readable over the
     brightest part of the mesh, not just over the empty corners. */
  background: rgba(6, 3, 14, .92);
  box-shadow: 0 0 0 1px rgba(105, 67, 230, .18), 0 10px 30px rgba(0, 0, 0, .55);
  font-size: .82rem;
  line-height: 1.4;
  color: var(--text);
  text-align: left;
  text-wrap: balance;
  opacity: 0;
  animation: thought-surface calc(var(--count, 6) * 3.1s) ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * 3.1s);
}

/* The point in the network the thought came from. */
.thought__dot {
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--violet-bright);
  box-shadow: 0 0 8px rgba(182, 156, 255, .9);
}

@keyframes thought-surface {
  0%        { opacity: 0; transform: translateY(6px) scale(.96); }
  4%        { opacity: 1; transform: none; }
  26%       { opacity: 1; transform: none; }
  32%, 100% { opacity: 0; transform: translateY(-6px) scale(.98); }
}

/* Below the hero's own breakpoint the brain is a third of the width and the
   copy is centred under it. Six pills over a 20rem drawing is a pile, not a
   thought, so they come off rather than shrink. */
@media (max-width: 62rem) {
  .thoughts { display: none; }
}

/* ── The hold, stage by stage ───────────────────────────────────────────── */
/* Two across, not four. The readers this is written for are operating partners
   reading on a laptop, and four columns of 15px type made them take in nine
   narrow blocks at once. Two columns buy back the line length and the type
   size, and the section is understood in one pass instead of scanned.

   The rule along the top of each stage is the timeline, and it wipes in from
   the left as the stage arrives. Both marks are pseudo-elements so the rule can
   be transformed on its own; as a border-top there is nothing to animate
   without moving the text with it. If the reveal controller never runs, no
   stage is hidden and the only thing missing is the hairline. */
.cycle {
  list-style: none;
  margin: 2.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2.5rem 3.5rem;
}

.cycle__stage { position: relative; padding-top: 1.4rem; }

.cycle__stage::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border-bright);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .8s cubic-bezier(.22, .61, .36, 1);
}

.cycle__stage.is-revealed::after { transform: scaleX(1); }

/* The dot marks where the stage starts on the line, so it lands once the line
   has been drawn under it. */
.cycle__stage::before {
  content: "";
  position: absolute;
  top: -3px;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--violet);
  opacity: 0;
  transform: scale(.4);
  transition: opacity .45s ease .5s, transform .45s cubic-bezier(.34, 1.56, .64, 1) .5s;
}

.cycle__stage.is-revealed::before { opacity: 1; transform: none; }

.cycle__when {
  margin: 0 0 .5rem;
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--violet);
}

.cycle__name { margin: 0 0 .6rem; font-size: 1.2rem; letter-spacing: -.02em; }

.cycle__body {
  margin: 0;
  color: var(--text-muted);
  font-size: 1.02rem;
  line-height: 1.6;
  max-width: 40ch;
}

/* ── The five services ──────────────────────────────────────────────────── */
/* These were five bordered cards in a row. Alongside the timeline that put ten
   boxes on screen at once, at a type size that made every one of them work to
   read. A list of five, ruled apart, says the same thing and asks for nothing.
   The detail lives on each service's own page. */
.services__list { list-style: none; margin: 2.25rem 0 0; padding: 0; }

/* The whole row is the link. It is inset by its own padding and pulled back
   out by the same amount, so the hover panel is wider than the text without
   the text moving when it appears. */
.service {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: .35rem 2.5rem;
  padding: 1.15rem 1rem;
  margin-inline: -1rem;
  border-top: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
  transition: background .2s ease, border-color .2s ease;
}

.service__name { margin: 0; font-size: 1.05rem; letter-spacing: -.015em; color: var(--text); }
.service__body { margin: 0; color: var(--text-muted); font-size: 1.02rem; line-height: 1.6; }

/* Present at rest, not conjured on hover: an affordance that only appears once
   the cursor is already on the thing has told the reader nothing. */
.service__go {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: .85rem;
  color: var(--text-dim);
  white-space: nowrap;
  transition: color .2s ease;
}

.service__arrow {
  width: .8rem;
  height: 1px;
  position: relative;
  background: currentColor;
  transition: transform .2s ease;
}

.service__arrow::after {
  content: "";
  position: absolute;
  right: 0;
  top: -2.5px;
  width: 6px;
  height: 6px;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(45deg);
}

a.service:hover,
a.service:focus-visible {
  background: var(--surface-2);
  border-color: var(--violet);
}

a.service:hover .service__name,
a.service:focus-visible .service__name {
  color: var(--violet-bright);
  text-decoration: underline;
  text-underline-offset: 4px;
}

a.service:hover .service__go,
a.service:focus-visible .service__go { color: var(--violet-bright); }

a.service:hover .service__arrow,
a.service:focus-visible .service__arrow { transform: translateX(4px); }

.service--pending { cursor: default; }
.service--pending .service__go { opacity: .6; }

@media (prefers-reduced-motion: reduce) {
  .service, .service__go, .service__arrow { transition: none; }
}

/* Below the two-column width the row stacks, and "Read more" sits under the
   line it belongs to rather than in a third column that no longer exists. */
@media (max-width: 48rem) {
  .service {
    grid-template-columns: 1fr;
    gap: .4rem;
  }
  .service__go { margin-top: .35rem; }
}

/* Sits under the whole timeline, not under whichever stage happens to end
   last. Without the space it reads as a fifth line of the bottom-left one. */
.operator__note { margin-top: 3rem; }

@media (max-width: 52rem) {
  .cycle { grid-template-columns: minmax(0, 1fr); }
  .service { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  .cycle__stage::after { transition: none; transform: scaleX(1); }
  .cycle__stage::before { transition: none; opacity: 1; transform: none; }
  .thought { animation: none; opacity: 1; transform: none; }
  .brain__pulse { animation: none; opacity: .8; stroke-dashoffset: 55; }
  .brain__node,
  .brain__hub { animation: none; opacity: .8; }
}

/* ── Product demos ──────────────────────────────────────────────────────── */
/* Four products, one tab each.

   Heading, then the control, then the stage the control changes, in that order
   and never interleaved. An earlier attempt hoisted the mock up beside the
   heading with `display: contents`; it put the control between the lede and its
   own result, stretched the heading row to the height of the mock and left a
   hole in the middle of the section. The mock sits high because the heading
   above it is short, which is the version of that idea that works.

   Two layouts, and which one applies is the JavaScript question. Without the
   tabs controller the strip is not drawn and all four panels stack, each its
   own two-column block, ruled apart and headed by its product. That reads
   correctly and is longer, which is the right way round. */
.demos { display: grid; grid-template-columns: minmax(0, 1fr); }
.demos__head { max-width: 46rem; }

/* The control bar. Ruled off from the heading above it and sitting directly on
   the stage below, so it reads as belonging to what it changes. */
.demos__chooser {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  padding-bottom: 1.35rem;
  border-top: 1px solid var(--border);
}

.demos.is-tabbed .demos__chooser { margin-bottom: 0; }
.demos .dashboards__label { margin-bottom: .7rem; }

.demo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  /* Tops together, not centres. The mock is taller than the copy, so centring
     hung it above the product's own name and dropped it below the link. */
  align-items: start;
  gap: clamp(1.75rem, 4vw, 3.5rem);
}

/* A class with a display of its own outranks the browser's own [hidden] rule,
   and without this the controller hides a panel and nothing happens. */
.demo[hidden] { display: none; }

/* Ruled apart only while they are stacked. Once one shows at a time there is
   nothing to separate. */
.demo + .demo { margin-top: 2.5rem; padding-top: 2.5rem; border-top: 1px solid var(--border); }
.demos.is-tabbed .demo + .demo { margin-top: 0; padding-top: 0; border-top: 0; }

/* The four mocks are different heights. Without a floor the page jumps under
   the reader every time they press a tab, which is the one thing a tab strip
   must never do. */
.demos.is-tabbed .demo { min-height: 25rem; }

.demo__copy { padding-top: .25rem; }

.demo__headline {
  font-size: clamp(1.3rem, 2.6vw, 1.75rem);
  letter-spacing: -.025em;
  margin: 0 0 .75rem;
  max-width: 22ch;
}

.demo__body { margin: 0 0 1.1rem; color: var(--text-muted); max-width: 48ch; }

@media (max-width: 62rem) {
  .demo { grid-template-columns: minmax(0, 1fr); }
  .demos.is-tabbed .demo { min-height: 0; }
}

/* ── Compliant and certified ────────────────────────────────────────────── */
/* These marks are not flattened to white silhouettes the way the employer
   strip's are. Half of them are badges and crests with the name set inside the
   shape: an OffSec hexagon, a Salesforce cloud, the Oxford arms. Flatten those
   and every one becomes the same featureless blob. So the section is a white
   band and the marks sit on it, greyscaled rather than destroyed.

   The ground is not white on arrival, and does not stay white. It takes the
   page's own violet-black on the way in, holds white for as long as the band is
   being read, and hands it back as the closing section arrives. Every colour
   here is a registered custom property for that reason: a var() holding a
   colour cannot be interpolated unless the browser has been told it is one.
   Where scroll-driven animation is unsupported the band is simply white. */
@property --creds-ground { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --creds-ink { syntax: "<color>"; inherits: true; initial-value: #101018; }
@property --creds-ink-soft { syntax: "<color>"; inherits: true; initial-value: #55555f; }
@property --creds-accent { syntax: "<color>"; inherits: true; initial-value: #5b3ad6; }
@property --creds-rule { syntax: "<color>"; inherits: true; initial-value: #e4e4ec; }

.band--creds {
  /* A full screen of it. The content is short on purpose, but a white section
     that only covers two thirds of the viewport leaves a purple strip under it
     and reads as a white box on a dark page rather than as the page changing
     ground. The content sits in the middle of the screen instead of filling
     it. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;

  --creds-ground: #ffffff;
  --creds-ink: #101018;
  --creds-ink-soft: #55555f;
  --creds-accent: #5b3ad6;
  --creds-rule: #e4e4ec;

  /* A band is width 100% of the shell's content box, so the negative margin
     alone only slides it left. It has to be given the two gutters back as
     width as well, or it overhangs one edge and falls short of the other. */
  width: calc(100% + var(--gutter) * 2);
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
  background: var(--creds-ground);
  color: var(--creds-ink);
}

.band--creds .eyebrow { color: var(--creds-accent); }
.band--creds .section-title { color: var(--creds-ink); }
.band--creds .section-lead { color: var(--creds-ink-soft); }

/* Bands are ruled apart from each other. This one changes ground instead, so
   the rule would draw a grey line across the top of the white and a dark one
   across the top of whatever follows it. */
.band.band--creds .band__inner,
.band--creds + .band .band__inner { border-top: 0; }

.band--creds > .band__inner { width: 100%; }

@keyframes creds-ground {
  0% {
    --creds-ground: #0b0614;
    --creds-ink: #f4f1ff;
    --creds-ink-soft: #b9a9d6;
    --creds-accent: #b69cff;
    --creds-rule: #241640;
  }
  22%, 55% {
    --creds-ground: #ffffff;
    --creds-ink: #101018;
    --creds-ink-soft: #55555f;
    --creds-accent: #5b3ad6;
    --creds-rule: #e4e4ec;
  }
  76%, 100% {
    --creds-ground: #0b0614;
    --creds-ink: #f4f1ff;
    --creds-ink-soft: #b9a9d6;
    --creds-accent: #b69cff;
    --creds-rule: #241640;
  }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band--creds {
      animation: creds-ground linear both;
      animation-timeline: view();
      /* `cover` runs from the moment any of the band is on screen to the moment
         none of it is, so the white is there before the marks are and gone once
         the band is leaving. The marks are dark on white and invisible on
         violet, which is what sets the two turnover points. */
      animation-range: cover 0% cover 100%;
    }
  }
}

/* Tabs down the side, one group at a time. Three sections above this one open
   with a horizontal pill strip, so a fourth would read as the same section
   again; a column is the one shape the page has not used, and it is what makes
   this the short section it should have been all along. */
/* A row of tabs above the panel, not a column beside it. A column of four
   headings this long pushed the marks into the right-hand two thirds of a
   full-width white band and left them nowhere to breathe. */
.creds { display: block; margin-top: 2.5rem; }

/* The marker is absolutely positioned and the controller places it from each
   tab's offsetLeft, so this has to be what those offsets are measured from. */
.creds__nav { position: relative; margin-bottom: 2.25rem; }

/* .mock-tabs__tab gives the pill its shape. These give it this band's palette:
   the strip everywhere else is set for violet on near-black. */
.creds__tab {
  background: transparent;
  border-color: var(--creds-rule);
  color: var(--creds-ink-soft);
  font-size: .82rem;
  padding: .42rem .9rem;
}

.creds__tab:hover { color: var(--creds-ink); border-color: var(--creds-accent); }
.creds__tab.is-active { color: var(--creds-ink); }

.creds__nav .mock-tabs__marker {
  border-color: var(--creds-accent);
  background: rgba(182, 156, 255, .16);
}

.creds__panel { display: grid; gap: .1rem; }

/* A class with a display of its own outranks the browser's own [hidden] rule,
   and without this the controller hides a panel and nothing happens. */
.creds__panel[hidden] { display: none; }

/* Without the controller every group shows, stacked, each under its own
   heading. With it, the tab is the heading and the panels carry a note. */
.creds__heading {
  margin: 0 0 .4rem;
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--creds-ink);
}

.creds.is-tabbed .creds__heading { display: none; }
.creds__panel + .creds__panel { margin-top: 2.25rem; }
.creds.is-tabbed .creds__panel + .creds__panel { margin-top: 0; }

.creds__note {
  margin: 0 0 1.75rem;
  font-size: .92rem;
  color: var(--creds-ink-soft);
  max-width: 46ch;
}

/* The three groups hold different numbers of marks, so without a floor the
   section changes height every time a tab is pressed. */
.creds.is-tabbed .creds__panel { min-height: 11rem; align-content: start; }

.creds__grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.75rem 2.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Above the breakpoint the wrapper is not there as far as the layout is
   concerned: the grid wraps as it always did and nothing moves. */
.marquee--creds { display: block; overflow: visible; mask-image: none; -webkit-mask-image: none; }

@media (max-width: 62rem) {
  .creds { grid-template-columns: minmax(0, 1fr); }
  .creds.is-tabbed .creds__panel { min-height: 0; }


  /* ── The logos ──────────────────────────────────────────────────────────
     A rail rather than a grid. Nine rows of marks is most of a phone screen
     for one tab of five. */
  .marquee--creds {
    display: flex;
    overflow: hidden;
    mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  }

  .marquee--creds .creds__grid {
    flex: none;
    flex-wrap: nowrap;
    gap: 2.25rem;
    padding-right: 2.25rem;
    animation: marquee-scroll 34s linear infinite;
  }

  /* Nothing to hover with, and inside a strip that clips its overflow the
     tooltip would be cut in half anyway. The mark's own label stays. */
  .cred__tip { display: none; }
}

@media (max-width: 62rem) and (prefers-reduced-motion: reduce) {
  /* Stopped, not slowed: what is left is a rail that scrolls by hand, which is
     the same marks without the movement. */
  .marquee--creds .creds__grid { animation: none; }
  .marquee--creds { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
}

.cred {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .45rem;
  filter: grayscale(1);
  opacity: .8;
  transition: opacity .3s ease, filter .3s ease, transform .3s ease;
}

.cred:hover { filter: none; opacity: 1; transform: translateY(-1px); }

/* Height comes from Credential#display_height, which evens out how much room
   each mark takes. Width follows from the logo's own proportions. */
.cred__logo { width: auto; max-width: 9rem; object-fit: contain; }

/* Two of these marks are a glyph with no name in them, and the crests carry
   their name in type too small to read at this size. */
.cred__label {
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .08em;
  color: var(--creds-ink-soft);
}

.cred__name { font-size: 1rem; color: var(--creds-ink); letter-spacing: -.01em; }

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


/* ── The sector tabs ────────────────────────────────────────────────────── */
/* The "we are not generalists" argument, made by naming systems rather than by
   claiming expertise, and then by naming what usually goes wrong in that
   sector. One tab per sector; behind each, the landscape grouped by the
   categories that sector actually has, then the three problems.

   Landscape above problems, not beside: the landscape is breadth and the
   problems are relevance, and a reader wants breadth established before
   relevance means anything. Side by side they competed for the same glance and
   the wider column starved the narrower one.

   Same progressive-enhancement contract as everything else here. Without the
   tabs controller the chooser is not drawn and all five sectors stack, each
   headed by its own name. */
/* Columns stated, not left to `auto`. A bare one-column grid floors its track
   at the widest thing inside it, so one child that will not wrap - a tab strip
   set to scroll sideways, say - makes the track wider than the grid and pushes
   the whole page past the edge of the screen. minmax(0, 1fr) is the difference
   between a track that fits its box and one its content decides. */
.sectors { display: grid; grid-template-columns: minmax(0, 1fr); margin-top: 2.5rem; }

.sectors__chooser {
  padding-top: 1.5rem;
  padding-bottom: 1.75rem;
  border-top: 1px solid var(--border);
}

.sector { display: grid; gap: 2.75rem; }
.sector[hidden] { display: none; }

/* Once one sector shows at a time the tab is the heading, so the in-panel one
   goes. Without the controller it is the only thing saying which sector this
   block belongs to. */
.sector__heading { margin: 0; font-size: 1.1rem; color: var(--violet-bright); }
.sectors.is-tabbed .sector__heading { display: none; }

.sector + .sector { margin-top: 2.5rem; padding-top: 2.5rem; border-top: 1px solid var(--border); }
.sectors.is-tabbed .sector + .sector { margin-top: 0; padding-top: 0; border-top: 0; }

/* The five sectors carry different numbers of categories, so without a floor
   the page jumps every time a tab is pressed. */
.sectors.is-tabbed .sector { min-height: 26rem; align-content: start; }

.sector__label {
  margin: 0 0 1.25rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ── The landscape ──────────────────────────────────────────────────────── */
.landscape {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.75rem 2.5rem;
}

.landscape__category {
  margin: 0 0 .65rem;
  font-size: .92rem;
  font-weight: 500;
  color: var(--text);
  letter-spacing: -.01em;
}

.landscape__group .vendors { margin: 0; }

.sector__problems {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem 2.5rem;
}

.sector__problems li {
  position: relative;
  padding-left: 1.1rem;
  color: var(--text-muted);
  line-height: 1.55;
}

.sector__problems li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: 5px;
  height: 1px;
  background: var(--violet);
}

.sector__block--problems .product__link { margin-top: 1.75rem; }

/* ── The cascade ────────────────────────────────────────────────────────── */
/* Pressing a tab takes a panel from display:none to displayed, and that
   restarts any animation on it or its children. So the cascade costs no
   JavaScript and cannot fall out of step with the control: it is the same
   event. Two levels, category then system, which is the structure of the thing
   being drawn.

   Only while tabbed. Stacked, all five panels are on screen and animating
   sixty chips nobody asked to see is noise. */
@keyframes sector-rise {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}

.sectors.is-tabbed .sector__label,
.sectors.is-tabbed .landscape__group,
.sectors.is-tabbed .sector__problems li,
.sectors.is-tabbed .sector__block--problems .product__link {
  animation: sector-rise .5s cubic-bezier(.22, .61, .36, 1) both;
}

.sectors.is-tabbed .landscape__group { animation-delay: calc(80ms + var(--i, 0) * 55ms); }
.sectors.is-tabbed .sector__block--problems .sector__label { animation-delay: 340ms; }
.sectors.is-tabbed .sector__problems li { animation-delay: calc(400ms + var(--i, 0) * 70ms); }
.sectors.is-tabbed .sector__block--problems .product__link { animation-delay: 640ms; }

/* The systems inside a category arrive behind their own heading. A second
   level of stagger is what makes this read as structure rather than as one
   block sliding up. */
.sectors.is-tabbed .landscape__group .vendor {
  animation: sector-rise .42s cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: calc(140ms + var(--i, 0) * 55ms + var(--j, 0) * 30ms);
}

@media (prefers-reduced-motion: reduce) {
  .sectors.is-tabbed .sector__label,
  .sectors.is-tabbed .landscape__group,
  .sectors.is-tabbed .landscape__group .vendor,
  .sectors.is-tabbed .sector__problems li,
  .sectors.is-tabbed .sector__block--problems .product__link { animation: none; }
}

/* ── The sliding tab marker ─────────────────────────────────────────────── */
/* One pill that moves between tabs, instead of a background switching off at
   one and on at another. The eye tracks a thing that moves; it has to hunt for
   a thing that reappears somewhere else, and that hunt is where a reader loses
   their place in a strip of five.

   Sized and placed by the tabs controller from the tab's own box, so it is
   right whatever the label says and however the strip has wrapped. */
.mock-tabs { position: relative; }

/* Positioned elements paint over non-positioned siblings, so without this the
   marker covers the label it is supposed to sit behind. */
.mock-tabs__tab { position: relative; z-index: 1; }

/* No size until the controller has measured a tab, so it draws nothing on a
   page whose JavaScript never ran. Nothing on this site is hidden in the
   markup itself, and a decorative pill is not the thing to make an exception
   for. */
.mock-tabs__marker {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  border: 1px solid var(--violet);
  border-radius: var(--radius-pill);
  background: rgba(105, 67, 230, .18);
  pointer-events: none;
}

/* Added a frame after the first placement, so the first tab does not get a
   pill sliding onto it from the corner of the strip on page load. */
.mock-tabs__marker.is-ready {
  transition: transform .38s cubic-bezier(.22, .61, .36, 1),
              width .38s cubic-bezier(.22, .61, .36, 1),
              height .38s cubic-bezier(.22, .61, .36, 1);
}

/* The marker carries the active state now, so the tab itself only carries the
   text colour. Leaving both on drew two pills on the selected tab. */
.is-tabbed .mock-tabs__tab.is-active { background: none; border-color: transparent; }

@media (prefers-reduced-motion: reduce) {
  .mock-tabs__marker.is-ready { transition: none; }
}

/* ── One source of truth ────────────────────────────────────────────────── */
/* Three points across, then the sector chooser, then the platform as that
   sector would see it. Same shape as the sector tabs and the products section:
   heading, control, stage. */
.truth__headline { max-width: 22ch; }

/* Numbered, with a rule across the top of each. The four bodies are different
   lengths and always will be, so the eye needs a strong shared edge at the top
   to read them as a set; a small dot at the start of each gave it nothing, and
   the group looked broken rather than uneven. */
.truth__points {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 2rem 2.5rem;
  align-items: start;
}

.truth__point {
  padding-top: 1rem;
  border-top: 1px solid var(--border-bright);
}

.truth__num {
  display: block;
  margin-bottom: .7rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .1em;
  color: var(--violet-bright);
}

.truth__name { margin: 0 0 .3rem; font-size: 1.05rem; letter-spacing: -.015em; }
.truth__body { margin: 0; color: var(--text-muted); line-height: 1.6; }

.consoles { display: grid; grid-template-columns: minmax(0, 1fr); margin-top: 2.75rem; }

.consoles__chooser {
  padding-top: 1.5rem;
  padding-bottom: 1.75rem;
  border-top: 1px solid var(--border);
}

.console-panel { position: relative; }
.console-panel[hidden] { display: none; }

/* Once one shows at a time the tab is the heading. Without the controller it
   is the only thing saying which sector this window belongs to. */
.console-panel__heading { margin: 0 0 1rem; font-size: 1.1rem; color: var(--violet-bright); }
.consoles.is-tabbed .console-panel__heading { display: none; }

.console-panel + .console-panel { margin-top: 2.5rem; padding-top: 2.5rem; border-top: 1px solid var(--border); }
.consoles.is-tabbed .console-panel + .console-panel { margin-top: 0; padding-top: 0; border-top: 0; }

/* ── The console ────────────────────────────────────────────────────────── */
/* Sidebar and main, inside the same window chrome every other mock uses. */
.console__body {
  display: grid;
  grid-template-columns: minmax(0, 13.5rem) minmax(0, 1fr);
  min-height: 20rem;
}

.console__nav {
  list-style: none;
  margin: 0;
  padding: .9rem .7rem;
  border-right: 1px solid var(--border);
  background: rgba(5, 3, 10, .45);
  display: grid;
  align-content: start;
  gap: .15rem;
}

.console__item {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .65rem;
  border-radius: var(--radius);
  font-size: .82rem;
  color: var(--text-muted);
}

/* The screen the window has open. The chat is first in every sector, which is
   the one thing the five sidebars have in common. */
.console__item.is-current {
  background: rgba(105, 67, 230, .18);
  color: var(--text);
  box-shadow: inset 2px 0 0 var(--violet);
}

.console__item-name { flex: 1; }

/* A dot, not a count. A number here would be a figure about a business we made
   up, and this whole set of mocks is built not to do that. */
.console__alert {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 8px rgba(255, 122, 138, .8);
}

.console__main { padding: 1.1rem 1.25rem 1.35rem; display: grid; align-content: start; gap: .75rem; }

.console__ask {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
}

.console__question { font-size: .95rem; color: var(--text); }
.console__answer { margin: 0 0 .25rem; font-size: .88rem; color: var(--violet-bright); }

/* ── The phone in the corner ────────────────────────────────────────────── */
/* Set into the window rather than placed beside it: the claim is that this is
   one platform in two places, and two things side by side read as two things. */
.console-panel__pocket {
  position: absolute;
  /* Mostly outside the window rather than on top of it: an inset that covers
     the right-hand column of the table underneath is not a composition, it is
     an obstruction. */
  right: -3.5rem;
  bottom: -2.75rem;
  width: 11.5rem;
  filter: drop-shadow(0 24px 50px rgba(0, 0, 0, .6));
}


/* The five sectors carry the same six sidebar rows but different dashboards
   under the chat, so without a floor the page jumps on every tab press. */
.consoles.is-tabbed .console-panel { min-height: 27rem; }

/* ── The cascade ────────────────────────────────────────────────────────── */
/* Pressing a tab takes a panel from display:none to displayed, which restarts
   every animation on it. So the sidebar deals itself out and the window arrives
   under it, on the same event as the control, with no JavaScript to fall out of
   step. */
@keyframes console-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@keyframes console-deal {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}

.consoles.is-tabbed .console { animation: console-rise .45s cubic-bezier(.22, .61, .36, 1) both; }

.consoles.is-tabbed .console__item {
  animation: console-deal .38s cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: calc(160ms + var(--i, 0) * 55ms);
}

.consoles.is-tabbed .console__main {
  animation: console-rise .5s cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: 240ms;
}

.consoles.is-tabbed .console-panel__pocket {
  animation: console-rise .55s cubic-bezier(.34, 1.26, .64, 1) both;
  animation-delay: 520ms;
}

@media (max-width: 62rem) {
  .console__body { grid-template-columns: minmax(0, 1fr); }
  .console__nav {
    border-right: 0;
    border-bottom: 1px solid var(--border);
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    overflow-x: auto;
  }
  .console__item.is-current { box-shadow: inset 0 2px 0 var(--violet); }

  /* The pricing console groups its nav under headings. Turned on its side those
     headings became columns of their own, so the strip read as a heading, then
     an item, then another heading, stacked at different heights. On a phone the
     grouping is not what the reader is being shown - the screens are - so the
     headings go and the items run as one row. */
  .console__group { display: none; }
  .console__list {
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    gap: .15rem;
  }
  /* The phone stops being an inset once there is no window corner to sit in. */
  .console-panel__pocket { position: static; width: 100%; max-width: 18rem; margin: 1.75rem auto 0; }
  .consoles.is-tabbed .console-panel { min-height: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .consoles.is-tabbed .console,
  .consoles.is-tabbed .console__item,
  .consoles.is-tabbed .console__main,
  .consoles.is-tabbed .console-panel__pocket { animation: none; }
}

/* ── The platform on a phone ────────────────────────────────────────────── */
/* Drawn, not photographed, like every other mock here. The frame is
   deliberately plain: a bezel with a radius and a status bar is enough to say
   "phone", and anything more competes with the thing inside it. */
.pocket { display: flex; justify-content: center; }

.pocket__frame {
  width: 100%;
  max-width: 20rem;
  border: 1px solid var(--border-bright);
  border-radius: 2.25rem;
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: 0 30px 70px rgba(0, 0, 0, .55), 0 0 0 6px rgba(255, 255, 255, .02);
  padding: .55rem;
  overflow: hidden;
}

.pocket__status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .35rem .9rem .5rem;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .06em;
  color: var(--text-dim);
}

.pocket__signal { display: inline-flex; align-items: flex-end; gap: 2px; }
.pocket__signal i { width: 3px; background: var(--text-dim); border-radius: 1px; }
.pocket__signal i:nth-child(1) { height: 4px; }
.pocket__signal i:nth-child(2) { height: 6px; }
.pocket__signal i:nth-child(3) { height: 8px; }

.pocket__app {
  border-radius: 1.75rem;
  background: rgba(5, 3, 10, .6);
  border: 1px solid var(--border);
  padding: .9rem .85rem 1.1rem;
}

.pocket__app-name {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  margin: 0 0 .85rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.pocket__note { font-size: .55rem; letter-spacing: .08em; color: var(--text-dim); }

.pocket__thread { display: grid; gap: .65rem; }

.pocket__bubble {
  margin: 0;
  padding: .6rem .75rem;
  border-radius: var(--radius);
  font-size: .82rem;
  line-height: 1.45;
}

/* The question is the reader's, so it sits on the accent and to the right, the
   way the message you sent does in every thread anyone has ever read. */
.pocket__bubble--asked {
  justify-self: end;
  max-width: 92%;
  border: 1px solid var(--violet);
  background: rgba(105, 67, 230, .2);
  color: var(--text);
}

.pocket__bubble--answered {
  border: 1px solid var(--border);
  background: rgba(18, 10, 34, .85);
}

.pocket__line { margin: 0 0 .6rem; font-size: .82rem; color: var(--text); }

.pocket__reminder {
  margin: 0;
  display: flex;
  gap: .4rem;
  font-size: .72rem;
  line-height: 1.4;
  color: var(--text-muted);
}

/* The phone set into the window's corner is the same mock at two thirds the
   size. Only what has to shrink is restated; the frame it borrows is above. */
.console-panel__pocket .pocket__frame { max-width: none; border-radius: 1.7rem; padding: .4rem; }
.console-panel__pocket .pocket__app { border-radius: 1.35rem; padding: .7rem .6rem .8rem; }
.console-panel__pocket .pocket__app-name { font-size: .56rem; margin-bottom: .6rem; }
.console-panel__pocket .pocket__note { display: none; }
.console-panel__pocket .pocket__status { padding: .2rem .6rem .35rem; font-size: .52rem; }
.console-panel__pocket .pocket__thread { gap: .45rem; }
.console-panel__pocket .pocket__bubble { font-size: .68rem; padding: .4rem .5rem; }
.console-panel__pocket .pocket__line { font-size: .68rem; margin-bottom: 0; }
.console-panel__pocket .pocket__reminder { font-size: .6rem; }
.console-panel__pocket .chart-bars__label { font-size: .58rem; }

/* The catalogue is the whole view now, so it gets the room the panels below it
   used to take, and two more columns to spend it on.

   Lines without their fill. A spark fills the area under itself, which reads as
   a trend when it is one chart in a panel and as a black block when it is
   eight of them stacked in a table - at this height there is more fill than
   line, and the shape disappears into it. */
.lab .chart-spark__area { display: none; }
.lab .chart-spark { height: 1.35rem; }
.lab__history { min-width: 5.5rem; }
.lab__cover .lab__word { white-space: nowrap; }

/* ── The pricing console ────────────────────────────────────────────────── */
/* A revenue management platform rather than one chart: a grouped sidebar and a
   single SKU open in the main pane. Narrower sidebar than the sector consoles,
   because this one sits in the products section's right-hand column rather
   than across the full measure. */
/* A desktop refinement only. Below the breakpoint the shared rule stacks the
   console, and this used to override it because it came later in the file with
   the same specificity - which left the product previews as a 176px sidebar
   beside a 157px dashboard on a phone, the decoration taking more room than the
   thing it decorates. */
@media (min-width: 62.01rem) {
  .console__body--narrow { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }
}

.console__group {
  margin: .9rem 0 .35rem;
  padding: 0 .65rem;
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.console__group:first-child { margin-top: 0; }
.console__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }

.console__body--narrow .console__item { font-size: .78rem; padding: .38rem .6rem; }

.console__sku {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 0 0 .35rem;
}

.console__sku-id { font-weight: 600; font-size: .95rem; }
.console__sku-note { font-size: .8rem; color: var(--text-muted); }

.console__cell { display: grid; gap: .4rem; }
.console__cell + .console__cell { margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--border); }

/* ── The price history ──────────────────────────────────────────────────── */
/* Stepped, not smoothed: a price holds and then moves on a day, and a curve
   through it would say something untrue about how the number behaves. */
.stepline {
  width: 100%;
  height: 5rem;
  display: block;
  overflow: visible;
  border-bottom: 1px solid var(--border);
}

.stepline__path {
  fill: none;
  stroke: var(--violet-bright);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

.stepline__mark { fill: var(--bg); stroke: var(--violet-bright); stroke-width: 1.5; }

/* ── The cost stack ─────────────────────────────────────────────────────── */
.stack { display: grid; gap: .6rem; }

.stack__bar {
  display: flex;
  height: 12px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--bar-bg);
}

.stack__part--base   { background: var(--violet-deep); }
.stack__part--violet { background: var(--violet); }
.stack__part--warn   { background: var(--danger); }
.stack__part--muted  { background: rgba(125, 107, 158, .55); }

.stack__key {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem .9rem;
  font-size: .72rem;
  color: var(--text-muted);
}

.stack__legend { display: flex; align-items: center; gap: .35rem; }

.stack__swatch { width: 8px; height: 8px; border-radius: 2px; }
.stack__swatch--base   { background: var(--violet-deep); }
.stack__swatch--violet { background: var(--violet); }
.stack__swatch--warn   { background: var(--danger); }
.stack__swatch--muted  { background: rgba(125, 107, 158, .55); }

/* The products section is a tab strip too, so its panels get the same
   sidebar-deals-itself-out cascade the sector consoles have. */
.demos.is-tabbed .console__item {
  animation: console-deal .38s cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: calc(160ms + var(--i, 0) * 45ms);
}

.demos.is-tabbed .console__main {
  animation: console-rise .5s cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: 220ms;
}

@media (prefers-reduced-motion: reduce) {
  .demos.is-tabbed .console__item,
  .demos.is-tabbed .console__main { animation: none; }
}

/* ── The product stage, lined up with the tab strip ─────────────────────── */
/* The heading stays full width. Below it the section is two columns with
   three rows: the chooser and the copy stack down the left, and the mock
   spans both of those rows on the right, so its top edge starts level with
   the tabs rather than under them.

   The panel becomes `display: contents` for that, which is the only way two
   halves of one element can take separate places in a grid above it. The
   alternative was two sets of tab targets and a controller that knows about
   both, which is more moving parts for the same picture. Roles on a
   display:contents element are exposed by every browser this site supports.

   Only while tabbed. Stacked, each panel is its own two-column block, which is
   what a reader with no JavaScript gets. */
.demos.is-tabbed {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  grid-template-rows: auto auto 1fr;
  column-gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: start;
}

.demos.is-tabbed .demos__head { grid-column: 1 / -1; grid-row: 1; }
.demos.is-tabbed .demos__chooser { grid-column: 1; grid-row: 2; }

.demos.is-tabbed .demo { display: contents; }
.demos.is-tabbed .demo[hidden] { display: none; }
.demos.is-tabbed .demo__copy { grid-column: 1; grid-row: 3; }
.demos.is-tabbed .demo__visual { grid-column: 2; grid-row: 2 / span 2; }

/* The three consoles carry the same chrome and roughly the same amount in the
   main pane, and this holds the last few pixels between them. Without it the
   section resizes under the reader every time they press a tab. */
.demos .console__body { min-height: 27rem; }
.demos.is-tabbed .demo { min-height: 0; }

.console__foot {
  margin: .35rem 0 0;
  padding-top: .75rem;
  border-top: 1px solid var(--border);
  font-size: .76rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.console__main { align-content: start; }

@media (max-width: 62rem) {
  .demos.is-tabbed { display: block; }
  .demos.is-tabbed .demo { display: grid; grid-template-columns: minmax(0, 1fr); }
  .demos.is-tabbed .demo[hidden] { display: none; }
  .demos .console__body { min-height: 0; }

}

/* The spacing above the chooser belongs to the grid, not to the chooser: as a
   margin on the element it pushed the tabs down inside their row while the
   mock beside them started at the row's top edge, so the two were forty pixels
   out of line with each other. */
.demos.is-tabbed { row-gap: 2.5rem; }
.demos.is-tabbed .demos__chooser { margin-top: 0; }

/* The three consoles hold different amounts, so the shortest decides nothing
   and the tallest decides everything: the body is floored at the tallest and
   the closing line is pinned to the bottom of the pane. Pressing a tab now
   changes what is in the window, not how tall the section is. */
.demos .console__body { min-height: 37.4rem; }
.console__main { display: flex; flex-direction: column; }
.console__foot { margin-top: auto; }

@media (max-width: 62rem) {
  .demos.is-tabbed { row-gap: 0; }
  .demos .console__body { min-height: 0; }
  .console__foot { margin-top: .35rem; }
}

/* ── The stage beside the whole left column ─────────────────────────────── */
/* The heading moves into the left column and the mock runs the full height of
   the section beside it, starting level with "Three other products…". That is
   what gets the section into one screen: stacked, the heading and the mock were
   adding their heights together.
   
   Four rows, and the fourth is the flexible one. An earlier version spanned the
   mock across two auto rows, which handed the mock's extra height back to the
   heading row and left a hole under the lede. Slack has to have somewhere to
   go that is not a row with content in it. */
.demos.is-tabbed {
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  grid-template-rows: auto auto auto 1fr;
  row-gap: 0;
}

.demos.is-tabbed .demos__head { grid-column: 1; grid-row: 1; }
.demos.is-tabbed .demos__chooser { grid-column: 1; grid-row: 2; margin-top: 1.5rem; }
.demos.is-tabbed .demo__copy { grid-column: 1; grid-row: 3; padding-top: .75rem; }
.demos.is-tabbed .demo__visual { grid-column: 2; grid-row: 1 / -1; align-self: start; }

/* Shorter, because the section now has to fit a laptop screen with the nav
   bar on top of it. The consoles lost nothing: the sidebar is the same and the
   main pane is floored at the tallest of the three. */
/* Floored at the tallest of the three, and at roughly the height of the copy
   column beside it, so the two sides of the section finish together and
   pressing a tab changes what is in the window rather than how tall the
   section is. */
.demos .console__body { min-height: 32.8rem; }
.demos .console__body--narrow { grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); }
.demos .console__main { padding: .8rem .9rem .9rem; gap: .5rem; }
.demos .console__cell + .console__cell { margin-top: .55rem; padding-top: .55rem; }

@media (max-width: 62rem) {
  .demos.is-tabbed .demos__chooser { margin-top: 2.5rem; }
  .demos.is-tabbed .demo__copy { padding-top: 0; }

  /* The placements have to be undone as well, and this is the whole of the bug
   * that was on this section.
   *
   * Tabbed and wide, the copy is placed in column one and the mock in column
   * two. Collapsing the template to a single column does not undo that:
   * `grid-column: 2` on the mock creates an implicit second column, the mock
   * fills it, and the copy is left in column one at zero pixels wide - which
   * sets every line of the headline and the standfirst to one word. A
   * one-column grid is only one column if nothing inside it asks for a second.
   *
   * It has to be here, in the last of the three blocks that place these two,
   * rather than beside the rule that collapses the template: the placements are
   * stated again further down the file, so a reset written up there is undone
   * before it reaches the browser. */
  .demos.is-tabbed .demo__copy,
  .demos.is-tabbed .demo__visual {
    grid-column: 1;
    grid-row: auto;
  }
}

/* Two cells side by side even in the narrower pane. The shared split falls back
   to one column under 13rem a cell, which in here stacked two five-row charts
   on top of each other and made one product half a screen taller than the
   other two. In a pane this size the pair is the point. */
.demos .console__main .panel__split { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .85rem; }
.demos .console__main .chart-bars__label { font-size: .72rem; }
.demos .console__main .panel__cell-label { font-size: .58rem; }

/* Wider on the left, narrower on the right. The mock does not need six hundred
   pixels to read as a platform, and every pixel it gives back is a line the
   copy beside it does not have to wrap onto — which is what actually decides
   how tall this section is. */
.demos.is-tabbed { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
.demos .demo__body { max-width: none; }
.demos .demo__headline { max-width: 26ch; }

/* This section has to fit a laptop screen with the nav bar on top of it, which
   no other band on the page has to do. It carries less padding than they do
   for that reason and no other. */
/* This section has to fit a laptop screen with the nav bar on top of it, which
   no other band on the page has to do, and it has to breathe while doing it.
   The air is outside the section rather than inside: the room for it came from
   a redundant line in the lede and from the gaps around the chooser, not from
   shrinking anything the reader is here to read. */
#products > .band__inner { padding-block: clamp(3.5rem, 7vw, 6rem); }

/* ── The handover to the white band ─────────────────────────────────────── */
/* The credentials band changes the page's ground, and it used to do it at its
   own top edge: a hard line with a dark section above it and a white one
   below. The section above washes out to white with it now, so by the time
   "The stack we operate" is on screen the whole view has changed ground and
   there is no seam to see.

   The animation is driven by the credentials band's own arrival rather than by
   the products section's departure, which is why the timeline is named and
   handed up to the shell: `timeline-scope` is what lets one element drive an
   animation on a sibling that comes before it.

   Where scroll-driven animation is not supported none of this runs and the
   boundary is the plain one, which is the version that was there before. */
@keyframes wash-to-white {
  from { background-color: rgba(255, 255, 255, 0); }
  to   { background-color: rgba(255, 255, 255, 1); }
}

/* The copy is white on violet, so it cannot survive the ground turning under
   it. It leaves before the ground arrives rather than being left illegible on
   it, and it holds at full strength for the first third of the range so the
   section is still readable while any of it is worth reading. */
@keyframes wash-out {
  0%, 30% { opacity: 1; }
  100%    { opacity: 0; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .shell { timeline-scope: --creds-view; }
    .band--creds { view-timeline-name: --creds-view; }

    /* Full bleed only while it is washing, so the white reaches both edges
       instead of leaving a dark stripe down each side. The inner keeps its own
       max-width and centring, so nothing moves. */
    #products {
      width: calc(100% + var(--gutter) * 2);
      margin-inline: calc(var(--gutter) * -1);
      padding-inline: var(--gutter);
      animation: wash-to-white linear both;
      animation-timeline: --creds-view;
      animation-range: entry 6% entry 62%;
    }

    #products > .band__inner {
      animation: wash-out linear both;
      animation-timeline: --creds-view;
      animation-range: entry 6% entry 46%;
    }
  }
}

/* The copy column's last line of air, taken back so the section can carry the
   padding around it instead. */
.demos .demo__headline { margin-bottom: .5rem; }
.demos .demo__body { margin-bottom: .85rem; }
.demos .product__link { margin-top: 1rem; }

/* A shorter price history in the products pane. The shape of a stepped line
   survives losing a sixteenth of its height; the section fitting a screen does
   not survive gaining it. */
.demos .stepline { height: 4rem; }

/* The trend lines in the products pane run a little shorter than elsewhere.
   A sparkline is a shape and the shape survives; the three consoles finishing
   at the same height is what does not survive the extra ten pixels. */
.demos .chart-spark { height: 2.6rem; }

/* ── Why a fund calls us ────────────────────────────────────────────────── */
/* The home page's close. Four reasons, arriving in sequence, so the reader
   takes them as a list of four rather than as a paragraph in columns. The
   numeral is drawn by a counter off the list itself, so a reason added to
   config numbers itself. */
.now {
  list-style: none;
  counter-reset: now;
  margin: 2.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2.25rem 3.5rem;
}

.now__item {
  counter-increment: now;
  position: relative;
  padding-left: 3rem;
}

.now__item::before {
  content: counter(now, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: .1rem;
  font-family: var(--font-mono);
  font-size: .8rem;
  letter-spacing: .1em;
  color: var(--violet);
}

.now__name { margin: 0 0 .5rem; font-size: 1.15rem; letter-spacing: -.02em; }
.now__body { margin: 0; color: var(--text-muted); line-height: 1.6; max-width: 42ch; }

.band--now .hero-block__actions { margin-top: 3rem; }

@media (max-width: 52rem) {
  .now { grid-template-columns: minmax(0, 1fr); }
}

/* ── The globe ──────────────────────────────────────────────────────────── */
/* Partly seen: it sits off the right edge of the section and the band crops
   it. A globe you can see all of is a diagram; a globe you can see part of is
   a place. */
.band--why { position: relative; overflow: hidden; }

.globe {
  position: absolute;
  right: -8rem;
  top: 50%;
  width: 32rem;
  max-width: 52vw;
  transform: translateY(-50%);
  pointer-events: none;
  opacity: .85;
}

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

.globe__limb {
  fill: none;
  stroke: var(--violet);
  stroke-opacity: .22;
  stroke-width: 1;
}

/* Every ring is normalised to a path length of 100, so one dash pattern is the
   same fraction of every ring however wide it is. That is what keeps them
   turning as one body: the same offset per second is the same angle per
   second, equator and pole alike. */
/* Dots, not dashes. A round cap on a dash short enough to have almost no
   length of its own draws a circle; at the first attempt the dash carried
   real length and the sphere read as a field of scattered ticks. */
.globe__ring {
  fill: none;
  stroke: var(--violet-bright);
  stroke-opacity: .9;
  stroke-width: 2.4;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;

  /* The plain pattern first, so a browser that cannot do the calculated one
     still draws a dotted sphere rather than nothing. */
  stroke-dasharray: .05 3.6;
  stroke-dasharray: .05 calc(var(--period) - .05);

  /* Each ring moves one gap per iteration. The duration is proportional to
     that gap, so every ring turns through the same angle in the same time and
     the sphere reads as one body rather than a stack of hoops running at
     different speeds. */
  animation: globe-turn 11s linear infinite;
  animation-duration: calc(var(--period) * 3.3s);
}

/* Negative offset walks the dashes forward along the path, and an ellipse is
   drawn clockwise from its right-hand edge, so the near face travels east. */
@keyframes globe-turn {
  to { stroke-dashoffset: calc(var(--period) * -1); }
}

/* The copy has to stay readable over it, so the column it sits in gets a hard
   width rather than letting the lede run under the sphere. */
.band--why .band__inner { position: relative; }
.band--why .section-title { max-width: 24ch; }
.band--why .section-lead { max-width: 52ch; }

@media (max-width: 62rem) {
  .globe { right: -10rem; opacity: .32; }
}

@media (prefers-reduced-motion: reduce) {
  .globe__ring { animation: none; }
}

/* ── Why us: the reasons left, the globe right ──────────────────────────── */
/* Six reasons in one column rather than two, because a numbered list read
   straight down is a list and a numbered list read in a zigzag is a grid the
   reader has to work out the order of. The right half of the section is the
   globe, which is why the copy is held to a column instead of running under
   it. */
.why__copy { max-width: 35rem; }

.band--why .now {
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  margin-top: 2.25rem;
}

.band--why .now__body { max-width: 46ch; }
.band--why .hero-block__actions { margin-top: 2.5rem; }

.globe {
  right: -4rem;
  width: min(46rem, 50vw);
  max-width: none;
}

@media (max-width: 62rem) {
  .why__copy { max-width: none; }
  .globe { right: -14rem; width: 34rem; opacity: .3; }
}

/* The canvas the globe controller draws on, and the static sphere it replaces.
   Both fill the same box, so whichever is showing sits in the same place. */
.globe { position: absolute; }
.globe__static,
.globe__canvas {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  height: auto;
}

.globe__canvas { display: none; }
.globe.is-live .globe__static { display: none; }
.globe.is-live .globe__canvas { display: block; }

/* No glow behind the canvas any more. A blurred radial on an element inside a
   section that clips its overflow is a glow with a straight edge down one side,
   which is what it looked like. The halo is painted inside the canvas instead,
   where it has room to fade out before it reaches anything that would cut it.
*/
/* Bigger, now that it carries labels and a readout that have to be legible. */
.globe { width: min(52rem, 56vw); right: -9rem; opacity: 1; }

@media (max-width: 62rem) {
  .globe { right: -18rem; width: 42rem; opacity: .32; }
}

/* ── The hero's industry dropdown ────────────────────────────────────────── */
/* The second call to action in the hero opens the sector list rather than
   going anywhere. Same panel treatment as the nav's dropdowns, but anchored to
   a button that has to keep looking like the ghost button it replaced. */
.herodrop { position: relative; display: inline-block; }

.herodrop__button {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  cursor: pointer;
  font-family: var(--font-body);
  list-style: none;          /* the disclosure triangle, in every engine */
}

.herodrop__button::-webkit-details-marker { display: none; }
.herodrop__button::marker { content: ""; }

.herodrop[open] .sitenav__caret { transform: rotate(180deg); }

.herodrop__panel {
  position: absolute;
  top: calc(100% + .45rem);
  left: 0;
  z-index: 600;
  background: var(--surface-2);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  padding: .4rem;
}

/* Names only. The line explaining each sector is in the Industries menu in the
   bar, and repeating it here made a panel half the height of the hero. */
.herodrop__list {
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 13.5rem;
}

.herodrop__link {
  display: block;
  padding: .45rem .7rem;
  border-radius: var(--radius);
  font-size: .875rem;
  font-weight: 500;
  color: var(--text-muted);
  white-space: nowrap;
}

a.herodrop__link:hover { background: var(--surface-3); color: var(--text); }


/* Below the fold on a phone the panel would open off the right edge, so it
   spans the column instead of sitting under the button. */
@media (max-width: 34rem) {
  .herodrop { display: block; width: 100%; }
  .herodrop__button { width: 100%; justify-content: center; }
  .herodrop__panel { left: 0; right: 0; }
  .herodrop__list { min-width: 0; }
  .herodrop__link { white-space: normal; }
}

/* ── The AI Platform page ────────────────────────────────────────────────── */
/* The one product page with a file of its own, because the claim it has to
   make is about how the answer is produced rather than what it looks like. */

/* Where the answer came from, on the answer itself. Small, monospaced and
   dim: a citation, not a headline. */
.mock-panel__cites {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem .5rem;
  margin: .55rem 0 0;
}

.mock-panel__cites-label {
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.mock-panel__cite {
  font-family: var(--font-mono);
  font-size: .66rem;
  line-height: 1.5;
  padding: .1rem .4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  white-space: nowrap;
}

/* ── The retrieval pipeline ─────────────────────────────────────────────── */
/* Four stages on one rail. The rail is drawn behind the stages and a signal
   runs it as the section crosses the viewport, so the reader's scroll is what
   moves the answer along rather than a timer they cannot see. */
.pipeline {
  --pipe-gap: 2.25rem;
  --pipe-mark: 27px;
  position: relative;
  list-style: none;
  margin: 2.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 2rem var(--pipe-gap);
}

/* Runs from the centre of the first mark to the centre of the last, rather
   than the full width of the list: a rail that carries on past the last stage
   reads as a step nobody wrote. With four equal columns each is
   (100% - 3 gaps) / 4 wide, and the last mark sits half a mark into it. */
.pipeline__rail {
  position: absolute;
  left: calc(var(--pipe-mark) / 2);
  right: calc((100% - 3 * var(--pipe-gap)) / 4 - var(--pipe-mark) / 2);
  top: calc(var(--pipe-mark) / 2);
  height: 1px;
  background: var(--border-bright);
  overflow: hidden;
}

.pipeline__signal {
  position: absolute;
  top: 0;
  left: 0;
  width: 22%;
  height: 100%;
  background: linear-gradient(90deg, transparent, var(--violet-bright), transparent);
  opacity: 0;
}

/* The rail is positioned for exactly four columns. Below the width where four
   of them fit, the list wraps and a horizontal line behind it is noise. */
@media (max-width: 69rem) {
  .pipeline__rail { display: none; }
}

.pipeline__stage { position: relative; }

.pipeline__mark {
  display: grid;
  place-items: center;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  border: 1px solid var(--border-bright);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--violet-bright);
}

.pipeline__name {
  margin: .9rem 0 .4rem;
  font-size: 1rem;
  font-weight: 600;
}

.pipeline__body {
  margin: 0;
  font-size: .9rem;
  line-height: 1.6;
  color: var(--text-muted);
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .pipeline__signal {
      animation: pipeline-run linear both;
      animation-timeline: view();
      /* `entry` rather than `cover`: the signal should have finished its run by
         the time the section is properly on screen and being read, not still be
         crawling while the reader is on the paragraph below it. */
      animation-range: entry 20% cover 55%;
    }
  }
}

@keyframes pipeline-run {
  0%   { left: -22%; opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { left: 100%; opacity: 0; }
}

/* ── What it will not do ────────────────────────────────────────────────── */
.guards {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  gap: .9rem;
  max-width: 46rem;
}

.guard {
  display: flex;
  align-items: flex-start;
  gap: .85rem;
  padding: .95rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  font-size: .95rem;
  line-height: 1.6;
  color: var(--text-muted);
  transition: border-color .18s ease, background .18s ease;
}

.guard:hover { border-color: var(--border-bright); background: var(--surface-3); }

/* A struck-through bar rather than a cross: these are things the platform
   does not do, and a line through the row reads as that without a word. */
.guard__mark {
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: .34em;
  border-radius: 3px;
  border: 1px solid rgba(255, 122, 138, .5);
  position: relative;
}

.guard__mark::after {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  top: 50%;
  height: 1px;
  background: rgba(255, 122, 138, .75);
}

/* ── The systems it reads ───────────────────────────────────────────────── */
/* Two rows running opposite ways, at half the speed of the employer strip:
   these are names to be read, not logos to be recognised. */
.sourcebelt {
  --marquee-gap: 1rem;
  --marquee-dur: 78s;
  margin-top: 2.25rem;
  display: grid;
  gap: 1rem;
}

.sourcebelt__chip {
  font-family: var(--font-mono);
  font-size: .78rem;
  padding: .38rem .8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-muted);
}

.sourcebelt__groups {
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .45rem .5rem;
}

.sourcebelt__groups li {
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.sourcebelt__groups li:not(:last-child)::after {
  content: "\00b7";
  margin-left: .55rem;
  opacity: .55;
}

/* ── Credentials: the work that has no logo ──────────────────────────────── */
/* The office group is half vendor marks and half things people do. A wordmark
   for "network administration" reads as a company nobody has heard of, so
   those are set as text under the grid instead. */
.creds__services {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 1.25rem 0 0;
  border-top: 1px solid var(--creds-rule);
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .55rem;
}

.creds__service {
  font-size: .8rem;
  line-height: 1.4;
  padding: .32rem .75rem;
  border: 1px solid var(--creds-rule);
  border-radius: var(--radius-pill);
  color: var(--creds-ink-soft);
  transition: border-color .18s ease, color .18s ease;
}

.creds__service:hover { border-color: var(--creds-accent); color: var(--creds-ink); }

/* ── The mark's name, on hover ───────────────────────────────────────────── */
/* Several marks are a glyph with no name in them, and several more are
   labelled short - "Azure" under Microsoft Azure - so this is the only place
   the full name is written. Not the browser's own `title`, which takes about a
   second to appear and cannot be styled.

   An element rather than a ::after, because the tip controller has to measure
   it to keep it inside the band, and a pseudo-element has no box to measure.
   It is aria-hidden: the same string is the mark's alt text already.

   `.cred` is greyscaled until hover, and a filter would take the tooltip with
   it. The rule above sets `filter: none` on hover, which is exactly when this
   is visible, so it is never actually filtered. */
@media (hover: hover) and (pointer: fine) {
  .cred[data-name] { position: relative; }

  .cred__tip {
    position: absolute;
    bottom: calc(100% + .45rem);
    left: 0;
    z-index: 5;
    padding: .3rem .6rem;
    border-radius: var(--radius);
    background: var(--creds-ink);
    color: var(--creds-ground);
    font-size: .72rem;
    line-height: 1.3;
    letter-spacing: .01em;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    /* --tip-shift is the controller's nudge, and zero until it sets one. */
    transform: translate(var(--tip-shift, 0px), 3px);
    transition: opacity .16s ease, transform .16s ease;
  }

  .cred[data-name]:hover .cred__tip {
    opacity: 1;
    transform: translate(var(--tip-shift, 0px), 0);
  }
}

/* Off a pointer device it is dead weight: a hover tooltip on a phone needs a
   tap to appear, and that tap is the one the reader wanted for something else.
   The mark's label and its alt text carry the name there. */
@media not all and (hover: hover) {
  .cred__tip { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cred__tip { transition: none; }
}

/* ── Legal documents ─────────────────────────────────────────────────────── */
/* The privacy policy and the terms. Long, read once, and read properly when
   they are read at all, so this is set for reading: one narrow column, generous
   leading, and no decoration competing with the words. */
.page--legal .page__head { max-width: 48rem; }

.legal__updated {
  margin: 1rem 0 0;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--text-dim);
}

.legal {
  max-width: 44rem;
  margin-top: 3rem;
}

.legal__h2 {
  margin: 2.75rem 0 .9rem;
  font-size: clamp(1.1rem, 2.2vw, 1.35rem);
  font-weight: 600;
  line-height: 1.3;
}

.legal__h2:first-child { margin-top: 0; }

.legal__h3 {
  margin: 1.9rem 0 .6rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
}

.legal__p {
  margin: 0 0 1.1rem;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--text-muted);
}

.legal__list {
  margin: 0 0 1.3rem;
  padding-left: 1.2rem;
  display: grid;
  gap: .55rem;
}

.legal__list li {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--text-muted);
}

.legal__list li::marker { color: var(--violet); }

.legal__foot {
  margin: 3rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  font-size: .92rem;
  color: var(--text-dim);
  max-width: 44rem;
}

/* ── The cookie banner ───────────────────────────────────────────────────── */
/* Sits at the bottom, over the page rather than pushing it, and never covers
   the whole screen: a reader who ignores it can still read everything. */
.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 900;
  padding: 0 var(--gutter) max(1rem, env(safe-area-inset-bottom));
  pointer-events: none;
}

.consent[hidden] { display: none; }

.consent__inner {
  pointer-events: auto;
  max-width: 62rem;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2rem;
  padding: 1.1rem 1.35rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: rgba(18, 10, 34, .96);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  backdrop-filter: blur(10px);
}

.consent__body {
  margin: 0;
  flex: 1 1 24rem;
  font-size: .88rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.consent__link { color: var(--violet-bright); text-decoration: underline; text-underline-offset: 3px; }
.consent__link:hover { color: var(--text); }

/* Both buttons the same size. Declining has to be as easy as accepting. */
.consent__actions {
  flex: none;
  display: flex;
  gap: .5rem;
}

.consent__actions .btn { cursor: pointer; }

@media (max-width: 34rem) {
  .consent__actions { width: 100%; }
  .consent__actions .btn { flex: 1; }
}

/* ── Book a demo ─────────────────────────────────────────────────────────── */
/* The trigger is a button inside an inline wrapper, so it sits in a row of
   calls to action as though it were one of the links beside it. */
.demo-book { display: inline-flex; }
.demo-book__trigger { cursor: pointer; font-family: var(--font-body); }

.truth__actions {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

@media (max-width: 34rem) {
  .truth__actions { width: 100%; }
  .truth__actions > * { flex: 1 1 100%; }
  .demo-book__trigger { width: 100%; }
}

/* ── A sector's long-form page ───────────────────────────────────────────── */
/* Manufacturing has one; the other four render the shared template. These are
   its components. Each section looks different from the one above it on
   purpose: seven identical stacks of headed paragraphs is a page nobody
   finishes, and the reader here is senior and short of patience. */

/* Findings. Numbered, with a rule over each, so four separate things read as
   four separate things rather than as a list of complaints. */
.facets {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 2rem 2.5rem;
  align-items: start;
}

.facet { padding-top: 1rem; border-top: 1px solid var(--border-bright); }

.facet__num {
  display: block;
  margin-bottom: .7rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .1em;
  color: var(--violet-bright);
}

.facet__name { margin: 0 0 .45rem; font-size: 1.02rem; letter-spacing: -.01em; line-height: 1.45; }
.facet__body { margin: 0; color: var(--text-muted); font-size: .95rem; line-height: 1.7; }

/* The landscape, by layer. Grouped rather than run together, because the claim
   is breadth across layers and a single long list does not make it. */
.landmap {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.25rem;
}

.landmap__group {
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  transition: border-color .2s ease, background .2s ease;
}

.landmap__group:hover { border-color: var(--border-bright); background: var(--surface-3); }

.landmap__category {
  margin: 0 0 .95rem;
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.landmap__systems {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}

/* The objection, answered. A panel rather than another stack of headings,
   because this is the bit the reader arrived sceptical about and it should not
   look like more of the pitch. */
.callout {
  margin-top: 1rem;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border: 1px solid var(--border-bright);
  border-left: 3px solid var(--violet);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}

.callout__title {
  margin: 0 0 .75rem;
  font-size: clamp(1.15rem, 2.4vw, 1.5rem);
  line-height: 1.25;
  letter-spacing: -.015em;
}

.callout__lede {
  margin: 0 0 1.75rem;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1.7;
  max-width: 52ch;
}

/* A rule over each rather than a dot beside it. A seven pixel dot next to a
   heading that wraps to two lines sits against the first of them and points at
   nothing, and four of those across a panel read as debris. The rule gives the
   four a shared top edge, which is what the dot was failing to do. */
.callout__points {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.75rem 2.5rem;
  align-items: start;
}

.callout__point { padding-top: .95rem; border-top: 1px solid var(--border-bright); }

.callout__name {
  margin: 0 0 .45rem;
  font-size: .98rem;
  line-height: 1.45;
  letter-spacing: -.01em;
}

.callout__body {
  margin: 0;
  color: var(--text-muted);
  font-size: .92rem;
  line-height: 1.7;
}

/* Value creation. Cards, because these are the four the reader will want to
   quote at somebody, and a card is easier to pull out of a page than a row. */
.valuecards {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.25rem;
}

.valuecard {
  padding: 1.4rem 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, var(--surface-2), transparent 70%);
  transition: border-color .2s ease, transform .2s ease;
}

.valuecard:hover { border-color: var(--violet); transform: translateY(-2px); }

.valuecard__name {
  margin: 0 0 .55rem;
  font-size: 1rem;
  letter-spacing: -.01em;
  line-height: 1.45;
}

.valuecard__body { margin: 0; color: var(--text-muted); font-size: .93rem; line-height: 1.7; }

@media (prefers-reduced-motion: reduce) {
  .valuecard, .landmap__group { transition: none; }
  .valuecard:hover { transform: none; }
}

/* ── A smaller closing, and a smaller tail ───────────────────────────────── */
/* The closing band is a panel, not a chapter. It ran at full section weight on
   all seventeen pages that carry it, which made the last thing on every page
   the loudest thing on it. */
.band--tight > .band__inner { padding-block: clamp(2rem, 4vw, 3rem); }
.band--tight .section-lead { max-width: 54ch; }
.band--tight .scan { margin-top: 1.75rem; }

/* The dashboards heading. `.section-title` caps every heading at twenty-six
   characters, which is right for a sentence and wrong for a name: it broke
   "AI intelligent dashboards" over two lines. Uncapped here so the name holds
   one line wherever there is room for it, and wraps on a phone, where it
   should. */
.platform__dashtitle { max-width: none; margin-top: 2.75rem; }
.platform__dashlede { margin-top: .7rem; }

.section-title--xs {
  font-size: .75rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 500;
}

/* The tail of a page: the least important thing on it, given the least room. */
.page__section--tail { margin-top: 3.5rem; }

.alsolist {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .75rem;
}

.alsolist__link {
  display: inline-block;
  padding: .4rem .85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: .9rem;
  color: var(--text-muted);
  transition: border-color .18s ease, color .18s ease, background .18s ease;
}

a.alsolist__link:hover {
  border-color: var(--violet);
  color: var(--text);
  background: var(--surface-2);
}

/* Copy left, panel right, the same shape as the hero. Stacked and centred, the
   panel ran the full width of the page and the band was the tallest thing on
   it. Side by side it is a panel again, and the reader gets heading, sentence,
   button in that order. */
.closing {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 3.5rem);
}

.closing__copy .section-lead { margin-top: .85rem; max-width: 42ch; }
.closing__copy .hero-block__actions { margin-top: 1.75rem; }

/* No top margin: it is a grid cell now, not something following a paragraph. */
.closing__visual .scan { margin-top: 0; }

/* Smaller throughout. It is an illustration beside the words, not the thing
   being read. */
.band--tight .scan__body { grid-template-columns: 1fr 1.2fr; min-height: 0; }
.band--tight .scan__panel { padding: .9rem 1rem; }
.band--tight .scan__chrome { padding-block: .55rem; }
.band--tight .scan__check { padding: .28rem .6rem; font-size: .78rem; }
.band--tight .scan__checks,
.band--tight .scan__log { gap: .12rem; }
.band--tight .scan__finding { font-size: .78rem; line-height: 1.45; }
.band--tight .scan__label { margin-bottom: .55rem; }
.band--tight .scan__status { font-size: .6rem; }

/* Left-aligned and no longer shouting. The band used to centre it, and centred
   uppercase mono over two lines is not a caption, it is a banner. */
.band--tight .scan__deliverable {
  margin-top: .85rem;
  padding-top: .7rem;
  font-size: .65rem;
  letter-spacing: .06em;
  text-transform: none;
  text-align: left;
}

@media (max-width: 62rem) {
  .closing { grid-template-columns: minmax(0, 1fr); }
  .closing__visual .scan { margin-top: .5rem; }
}

/* Two panels of five rows each do not survive being cut in half. */
@media (max-width: 40rem) {
  .band--tight .scan__body { grid-template-columns: minmax(0, 1fr); }
}

/* ── The screens a sector gets ───────────────────────────────────────────── */
/* The console mock shows the sidebar; this says what is behind each item on it.
   A reader who cannot picture the dashboard cannot picture the meeting it
   replaces, which is the thing actually being sold. */
.platform__impact {
  margin: 1.75rem 0 0;
  max-width: 62ch;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--text-muted);
}

.platform__impact + .section-title--xs { margin-top: 2.25rem; }

.screens {
  list-style: none;
  margin: 1.1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1.1rem 2.25rem;
}

.screen { display: flex; gap: .8rem; align-items: flex-start; }

.screen__mark {
  flex: none;
  width: 7px;
  height: 7px;
  margin-top: .55em;
  border-radius: 2px;
  background: var(--violet);
}

.screen__name { margin: 0 0 .3rem; font-size: .97rem; letter-spacing: -.01em; line-height: 1.45; }
.screen__body { margin: 0; color: var(--text-muted); font-size: .91rem; line-height: 1.7; }

/* ── The chat, shown ─────────────────────────────────────────────────────── */
/* The typing mock beside the same conversation on a phone. The argument is that
   it is one thing in two places, so they sit side by side rather than stacked. */
.chatshow {
  margin-top: 2.25rem;
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}

.chatshow__aside { display: grid; gap: 1.1rem; justify-items: start; }
.chatshow__phone { width: min(100%, 15rem); }

.chatshow__note {
  margin: 0;
  font-size: .92rem;
  line-height: 1.65;
  color: var(--text-muted);
}

@media (max-width: 56rem) {
  .chatshow { grid-template-columns: minmax(0, 1fr); }
  .chatshow__aside { justify-items: stretch; }
  .chatshow__phone { width: min(100%, 14rem); margin-inline: auto; }
}

/* ── Who asks it what ────────────────────────────────────────────────────── */
/* The questions are the point of this section: they are what each of these
   people would actually type, which is the claim that nobody has to be taught
   somebody else's tool. */
.desks {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
}

.desk {
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  transition: border-color .2s ease, background .2s ease;
}

.desk:hover { border-color: var(--violet); background: var(--surface-3); }

.desk__name {
  margin: 0 0 .55rem;
  font-size: .7rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.desk__question {
  margin: 0;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--text);
}

.desk__ask {
  display: inline-block;
  margin-right: .45rem;
  padding: .1rem .4rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-dim);
  vertical-align: 2px;
}

/* ── Watching, not reporting ─────────────────────────────────────────────── */
.watching {
  margin-top: 2.25rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}

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

.compare--tight { margin-top: 2.5rem; }

@media (max-width: 62rem) {
  .watching { grid-template-columns: minmax(0, 1fr); }
}

/* ── The anomaly chart ───────────────────────────────────────────────────── */
/* A reading leaving the band it has always sat in. The finished state is what
   is in the markup; the animation only reveals it, so with no JavaScript, in a
   background tab or under reduced motion this is still a chart with an alert on
   it, which is the whole point of the section. */
.anomaly {
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .9), rgba(11, 6, 20, .9));
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}

.anomaly__body { padding: 1.1rem 1.2rem 1.25rem; }

.anomaly__label {
  margin: 0 0 .9rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.anomaly__chart { width: 100%; height: auto; display: block; }
.anomaly__band { fill: rgba(105, 67, 230, .12); stroke: rgba(105, 67, 230, .28); stroke-width: 1; }
.anomaly__grid line { stroke: var(--border); stroke-width: 1; }

.anomaly__line {
  fill: none;
  stroke: var(--violet-bright);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.anomaly__point { fill: var(--danger, #ff7a8a); }
.anomaly__ping { fill: none; stroke: var(--danger, #ff7a8a); stroke-width: 1.5; opacity: 0; }

.anomaly__alert {
  margin: 1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--border);
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  font-size: .84rem;
  line-height: 1.55;
  color: var(--text-muted);
}

.anomaly__alert-dot {
  flex: none;
  width: 7px;
  height: 7px;
  margin-top: .5em;
  border-radius: 50%;
  background: var(--danger, #ff7a8a);
}

@media (prefers-reduced-motion: no-preference) {
  .anomaly__line {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: anomaly-draw 9s ease-out infinite;
  }

  .anomaly__ping { animation: anomaly-ping 9s ease-out infinite; }
  .anomaly__alert { animation: anomaly-raise 9s ease-out infinite; }
}

@keyframes anomaly-draw {
  0%   { stroke-dashoffset: 100; }
  45%  { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 0; }
}

@keyframes anomaly-ping {
  0%, 45%  { r: 4; opacity: 0; }
  55%      { opacity: .9; }
  72%      { r: 15; opacity: 0; }
  100%     { r: 15; opacity: 0; }
}

@keyframes anomaly-raise {
  0%, 52%  { opacity: 0; transform: translateY(4px); }
  64%, 100% { opacity: 1; transform: translateY(0); }
}

/* Calls to action sit under the heading that earned them, not at the foot of
   the section. The visual that follows needs a little more room than a
   paragraph would. */
.page__section .hero-block__actions { margin-top: 1.6rem; }
.page__section .hero-block__actions + .page__visual { margin-top: 2.25rem; }

/* ── The pricing recommendation ──────────────────────────────────────────── */
/* A number with nothing under it is a number a commercial lead will not sign,
   so the reasoning is the part of this that matters. Visible at rest and only
   faded in by the animation, which means with no JavaScript, in a background
   tab or under reduced motion the recommendation is simply there. */
.reco {
  margin-top: .9rem;
  padding: .7rem .85rem;
  border: 1px solid rgba(105, 67, 230, .35);
  border-radius: var(--radius);
  background: rgba(105, 67, 230, .1);
}

.reco__head { margin: 0; display: flex; align-items: center; gap: .5rem; }
.reco__mark { color: var(--violet-bright); font-size: .9em; }

.reco__verdict {
  font-size: .82rem;
  font-weight: 600;
  color: var(--text);
}

.reco__why {
  margin: .3rem 0 0;
  font-size: .74rem;
  line-height: 1.5;
  color: var(--text-muted);
}

@media (prefers-reduced-motion: no-preference) {
  .reco { animation: reco-arrive 7s ease-out infinite; }
}

@keyframes reco-arrive {
  0%, 22%   { opacity: 0; transform: translateY(5px); }
  34%, 100% { opacity: 1; transform: translateY(0); }
}

/* The struck-bar list carries a name and a line on the product pages, where the
   AI Platform's version of it carries only a line. */
.guard__name { display: block; margin-bottom: .15rem; color: var(--text); font-weight: 600; }

/* ── The catalogue table ─────────────────────────────────────────────────── */
/* Every line against the four things that decide its price. A revenue manager
   works from the catalogue, not from one SKU with two charts under it, and a
   table is what that screen actually is. */
.lab {
  width: 100%;
  border-collapse: collapse;
  font-size: .72rem;
}

.lab thead th {
  padding: 0 .5rem .5rem;
  text-align: left;
  font-family: var(--font-mono);
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.lab tbody tr { border-bottom: 1px solid rgba(255, 255, 255, .04); }
.lab tbody tr:last-child { border-bottom: 0; }

.lab td, .lab th[scope="row"] {
  padding: .45rem .5rem;
  vertical-align: middle;
  text-align: left;
  font-weight: 400;
  color: var(--text-muted);
}

.lab__sku { font-family: var(--font-mono); font-size: .7rem; color: var(--text); white-space: nowrap; }
.lab__channel { white-space: nowrap; }

/* The trend and the word it amounts to, side by side: the shape is the evidence
   and the word is the reading of it. */
.lab__demand, .lab__cost { min-width: 6.5rem; }
.lab__demand .chart-spark, .lab__cost .chart-spark { width: 3rem; height: 1rem; display: inline-block; vertical-align: middle; }
/* Wider, because a twelve month history has more to say than a direction. */
.lab__history .chart-spark { width: 5rem; height: 1.1rem; display: inline-block; vertical-align: middle; }
.lab__word { margin-left: .45rem; white-space: nowrap; font-size: .68rem; }

/* The line the panels below are about. */
.lab__row.is-focus { background: rgba(105, 67, 230, .1); }
.lab__row.is-focus .lab__sku { color: var(--violet-bright); }

.lab__label--tight { margin-top: 1rem; }

/* ── What it read, and when ──────────────────────────────────────────────── */
/* A recommendation is only as good as the freshness of what went into it. */
.inputs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}

.inputs__item {
  display: inline-flex;
  align-items: baseline;
  gap: .4rem;
  padding: .25rem .6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}

.inputs__name { font-size: .68rem; color: var(--text-muted); }

.inputs__age {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .06em;
  color: var(--violet-bright);
}

/* The table carries six columns. Below this it is scrolled rather than
   crushed: a six column table at 375px is not a table, it is a wall. */
@media (max-width: 52rem) {
  .mock--lab .console__cell:has(.lab) { overflow-x: auto; }
  .lab { min-width: 34rem; }
}

/* ── Small windows ───────────────────────────────────────────────────────── */
/* The other screens of a product, at a size meant to be recognised rather than
   read. Three of them at full size would be another screen of page. */
.screenshots {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.5rem;
}

.screenshot { display: grid; gap: .85rem; align-content: start; }

.mock--mini {
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .9), rgba(11, 6, 20, .9));
  overflow: hidden;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .4);
}

.mock--mini .mock__body { padding: .85rem .9rem; }
.mock--mini .chart-rows__row { padding-block: .3rem; }
.mock--mini .chart-rows__name { font-size: .72rem; }
.mock--mini .chart-rows__meta { font-size: .64rem; }

.screenshot__note {
  margin: .8rem 0 0;
  padding-top: .7rem;
  border-top: 1px solid var(--border);
  font-size: .7rem;
  line-height: 1.5;
  color: var(--text-dim);
}

.screenshot__caption {
  margin: 0;
  font-size: .88rem;
  line-height: 1.6;
  color: var(--text-muted);
}

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

/* ── The model a sector runs on ──────────────────────────────────────────── */
/* Copy left, the operating position drawn right. The argument is that the
   strategy is right and the administration of it is not, and the reader has to
   see the second half to believe the first. */
/* Centred against the points rather than pinned to the top of them. Sticky was
   the obvious idea and it does nothing here: the whole section is shorter than
   a viewport, so there is no scroll for the panel to survive. Top-aligned it
   simply left a couple of hundred pixels of nothing beside the last two
   points. */
/* Four arguments, each with its own screen.

   `align-items: start` rather than centre, and the heading lives in the left
   column: that is what puts the panel level with "The endowment model" instead
   of floating halfway down beside the copy. It is also what lets the right
   column stick, which centring quietly prevents. */
.model {
  margin-top: 2.25rem;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: start;
}

.model__points { margin-top: 2rem; grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }

.mock--book .mock__body { display: grid; gap: 1.1rem; }

/* Room to scroll through four points against one panel. Without this the
   column has nothing to be sticky within and the effect never happens. */
.model.is-wide .model__points { gap: clamp(2.5rem, 9vh, 5rem); }

.model.is-wide .model__visual {
  position: sticky;
  /* Under both bars, with air. */
  top: calc(var(--bar-height) + var(--ticker-h) + 1.5rem);
}

/* One panel visible, the rest still in the document and out of the way. Not
   `hidden`: they are swapped constantly, and a fade reads as the same object
   changing rather than four objects taking turns. */
.model.is-wide .model__panel,
.model.is-tabbed .model__panel { display: none; }

.model.is-wide .model__panel.is-current,
.model.is-tabbed .model__panel.is-current {
  display: block;
  animation: model-swap .35s ease both;
}

@keyframes model-swap {
  from { opacity: 0; transform: translateY(.5rem); }
  to   { opacity: 1; transform: none; }
}

/* The point being illustrated is marked, quietly: without it a reader has no
   way of knowing which paragraph the panel belongs to. */
.model.is-wide .model__point { transition: opacity .3s ease; }
.model.is-wide .model__point:not(.is-current) { opacity: .55; }
.model.is-wide .model__point.is-current .facet__num { color: var(--violet-bright); }

/* ── Narrow: the points become tabs ──────────────────────────────────────
   There is no second column to stick anything in, so instead of four tall
   panels in a row the reader gets one point and one screen at a time. */
.model.is-tabbed { grid-template-columns: minmax(0, 1fr); }
.model.is-tabbed .model__visual { display: none; }
.model.is-tabbed .model__points { gap: 0; margin-top: 1.5rem; }
.model.is-tabbed .model__point:not(.is-current) { display: none; }
.model.is-tabbed .model__point { padding-left: 0; }
.model.is-tabbed .model__point .facet__num { display: none; }
.model.is-tabbed .model__panel { margin-top: 1.25rem; }

/* The strip scrolls sideways rather than wrapping to two rows, which is what
   the console nav does a few sections down: one row of choices reads as a
   control, two rows read as a list. */
.model__tabs {
  display: flex;
  gap: .4rem;
  margin-top: 1.75rem;
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.model__tab {
  flex: none;
  padding: .45rem .9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: none;
  font-size: .82rem;
  color: var(--text-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}

.model__tab:hover { border-color: var(--border-bright); color: var(--text); }

.model__tab.is-current {
  border-color: var(--violet-deep);
  background: color-mix(in srgb, var(--violet-deep) 22%, transparent);
  color: var(--text);
}

@media (max-width: 62rem) {
  .model { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  .model__panel.is-current { animation: none; }
  .model.is-wide .model__point:not(.is-current) { opacity: 1; }
}

/* ── Which of ours fit ───────────────────────────────────────────────────── */
/* Cards that say what each product is for before they say what it is, because
   two of the four are for what the family owns rather than for the office, and
   a reader should be able to skip those in one glance. */
.platforms__heading { margin-top: 2.75rem; }
.platforms__lede { margin-top: .6rem; max-width: 54ch; }

.platforms {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}

.platform {
  position: relative;
  display: grid;
  gap: .35rem;
  height: 100%;
  padding: 1.1rem 1.2rem 2.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  color: inherit;
  align-content: start;
  transition: border-color .2s ease, background .2s ease, transform .2s ease;
}

.platform:hover, .platform:focus-visible {
  border-color: var(--violet);
  background: var(--surface-3);
  transform: translateY(-2px);
}

.platform__fit {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.platform__name { font-size: 1rem; font-weight: 600; letter-spacing: -.01em; color: var(--text); }
.platform__body { font-size: .9rem; line-height: 1.65; color: var(--text-muted); }

.platform__go {
  position: absolute;
  left: 1.2rem;
  bottom: 1.1rem;
  color: var(--text-dim);
  transition: color .2s ease;
}

.platform:hover .platform__go { color: var(--violet-bright); }
.platform:hover .service__arrow { transform: translateX(4px); }

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

/* ── What we take on ─────────────────────────────────────────────────────── */
/* Two columns of equal weight. The administration gets the same room as the
   estate deliberately: it is the half nobody costs and everybody resents, and a
   reader recognising their own week in that list is the point of the section. */
.takeon {
  margin-top: 2.25rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1.5rem;
}

.takeon__half {
  padding: 1.4rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}

.takeon__half .section-title--xs { margin: 0 0 1rem; }

.takeon__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .7rem;
}

.takeon__list li {
  display: flex;
  gap: .65rem;
  align-items: flex-start;
  font-size: .93rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.takeon__mark { flex: none; color: var(--violet-bright); font-size: .9em; line-height: 1.7; }

/* ── Reserved space for a visual that does not exist yet ─────────────────── */
/* Visible and marked on purpose. An empty div would be forgotten and a
   placeholder that looked finished would be worse, so this says what belongs
   here and carries the same dashed outline every other unbuilt thing on the
   site does. */
.slot {
  margin-top: 2rem;
  aspect-ratio: var(--slot-ratio, 16 / 7);
  display: grid;
  place-items: center;
  padding: 1.5rem;
  border: 1px dashed rgba(167, 139, 250, .4);
  border-radius: var(--radius-lg);
  background:
    repeating-linear-gradient(135deg,
      rgba(105, 67, 230, .05) 0 12px,
      transparent 12px 24px);
}

.slot__inner { text-align: center; max-width: 34ch; }

.slot__label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.slot__note {
  margin: .5rem 0 0;
  font-size: .85rem;
  line-height: 1.6;
  color: var(--text-dim);
}

/* ── Where the engineers came from ───────────────────────────────────────── */
/* The logos at rest rather than moving. The home page runs them past you; here
   the reader has arrived to look at them, so they hold still. */
/* The About page's logo grid became the same rail the home page shows; see
   .marquee--employers. */

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

/* ── Who runs it ─────────────────────────────────────────────────────────── */
.people {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.75rem;
}

.person {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}

.person__portrait .slot { margin-top: 0; }
.person__name { margin: 0 0 .2rem; font-size: 1.05rem; letter-spacing: -.01em; }

.person__role {
  margin: 0 0 .6rem;
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.person__line { margin: 0 0 .7rem; font-size: .92rem; line-height: 1.7; color: var(--text-muted); }

.person__link {
  font-size: .85rem;
  color: var(--violet-bright);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.person__link:hover { color: var(--text); }

@media (max-width: 34rem) {
  .person { grid-template-columns: minmax(0, 1fr); }
  .person__portrait { max-width: 8rem; }
}

/* ── Vetted the way we vet yours ─────────────────────────────────────────── */
/* The checks on the left, the visual beside them, because the list is the
   substance and the picture is the illustration rather than the other way
   round. */
.vetted {
  margin-top: 2.25rem;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: clamp(1.75rem, 4vw, 3rem);
  align-items: center;
}

.vetted__list { margin: 0; }
.vetted__visual .slot { margin-top: 0; }

@media (max-width: 56rem) {
  .vetted { grid-template-columns: minmax(0, 1fr); }
}

/* ── The bench, animated ─────────────────────────────────────────────────── */
/* Six disciplines on a rail, one lighting at a time and running a line down to
   the work. Resting state is all six present and the rail drawn, so it still
   makes its point with nothing moving. */
.bench {
  position: relative;
  padding: 1.75rem 1.5rem 1.5rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .85), rgba(11, 6, 20, .85));
  overflow: hidden;
}

.bench__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: .5rem;
}

.bench__seat { display: grid; justify-items: center; gap: .5rem; position: relative; }

.bench__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1px solid var(--violet);
  background: var(--bg);
}

.bench__name {
  font-size: .64rem;
  line-height: 1.35;
  text-align: center;
  color: var(--text-dim);
  transition: color .3s ease;
}

/* The line from a seat down to the rail. */
.bench__wire {
  width: 1px;
  height: 2.1rem;
  background: linear-gradient(180deg, var(--violet), transparent);
  opacity: .18;
  transition: opacity .3s ease;
}

.bench__rail {
  height: 1px;
  margin: 0 0 1.1rem;
  background: linear-gradient(90deg, transparent, var(--border-bright) 6%, var(--border-bright) 94%, transparent);
}

.bench__work {
  display: grid;
  gap: .2rem;
  padding: .8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  text-align: center;
}

.bench__work-label {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--violet-bright);
}

.bench__work-note { font-size: .78rem; line-height: 1.5; color: var(--text-muted); }

@media (prefers-reduced-motion: no-preference) {
  .bench__dot   { animation: bench-dot 9s ease-in-out infinite; animation-delay: calc(var(--i) * 1.5s); }
  .bench__wire  { animation: bench-wire 9s ease-in-out infinite; animation-delay: calc(var(--i) * 1.5s); }
  .bench__name  { animation: bench-name 9s ease-in-out infinite; animation-delay: calc(var(--i) * 1.5s); }
}

@keyframes bench-dot {
  0%, 6%    { background: var(--bg); box-shadow: none; }
  10%, 14%  { background: var(--violet-bright); box-shadow: 0 0 0 4px rgba(105, 67, 230, .25); }
  20%, 100% { background: var(--bg); box-shadow: none; }
}

@keyframes bench-wire {
  0%, 6%    { opacity: .18; }
  10%, 14%  { opacity: 1; }
  20%, 100% { opacity: .18; }
}

@keyframes bench-name {
  0%, 6%    { color: var(--text-dim); }
  10%, 14%  { color: var(--text); }
  20%, 100% { color: var(--text-dim); }
}

@media (max-width: 40rem) {
  .bench__row { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem .5rem; }
  .bench__wire { height: 1.1rem; }
}

/* ── Vetting, animated ───────────────────────────────────────────────────── */
/* Checks tick through in order, a sweep re-runs them, and one raises a flag.
   Checked once, then watched, in one loop. */
.watched {
  position: relative;
  padding: 1.1rem 1.2rem 1.25rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .85), rgba(11, 6, 20, .85));
  overflow: hidden;
}

.watched__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .8rem;
  margin-bottom: .8rem;
  border-bottom: 1px solid var(--border);
}

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

.watched__live {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.watched__pulse {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #7ee08a;
}

.watched__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; }

.watched__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: .6rem;
  padding: .38rem .5rem;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .02);
}

.watched__mark {
  width: 15px;
  height: 15px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--violet);
  font-size: .6rem;
  color: var(--violet-bright);
}

.watched__name { font-size: .74rem; color: var(--text-muted); }

.watched__state {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.watched__row.is-flagged .watched__mark {
  border-color: rgba(255, 122, 138, .7);
  color: var(--danger, #ff7a8a);
}

.watched__row.is-flagged .watched__state { color: var(--danger, #ff7a8a); }

/* The re-check, running back down the list. */
.watched__sweep {
  position: absolute;
  left: 0;
  right: 0;
  height: 34%;
  top: -34%;
  background: linear-gradient(180deg, transparent, rgba(105, 67, 230, .16), transparent);
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .watched__mark  { animation: watched-tick 8s ease-out infinite; animation-delay: calc(var(--i) * .35s); }
  .watched__pulse { animation: watched-pulse 2s ease-in-out infinite; }
  .watched__sweep { animation: watched-sweep 8s ease-in-out infinite; }
}

@keyframes watched-tick {
  0%, 4%    { opacity: .25; transform: scale(.85); }
  12%, 100% { opacity: 1; transform: scale(1); }
}

@keyframes watched-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .25; }
}

@keyframes watched-sweep {
  0%, 45%   { top: -34%; }
  75%, 100% { top: 100%; }
}

/* ── Both sides of the table ─────────────────────────────────────────────── */
/* Advisors above, the deal below, and a signal running the table between them.
   The headline says "on your side of the table", so the table is drawn. */
.bothsides {
  position: relative;
  padding: 1.5rem 1.4rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, rgba(25, 16, 48, .85), rgba(11, 6, 20, .85));
  overflow: hidden;
}

.bothsides__row { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.bothsides__row--us { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.bothsides__row--deal { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.bothsides__node,
.bothsides__stage {
  padding: .5rem .6rem;
  border-radius: var(--radius);
  text-align: center;
  font-size: .7rem;
  line-height: 1.35;
  border: 1px solid var(--border);
}

.bothsides__node { background: rgba(105, 67, 230, .14); border-color: rgba(105, 67, 230, .4); color: var(--text); }
.bothsides__stage { background: var(--surface-2); color: var(--text-muted); }

.bothsides__table {
  position: relative;
  height: 1px;
  margin: 1.1rem 0;
  background: var(--border-bright);
  overflow: hidden;
}

.bothsides__signal {
  position: absolute;
  top: 0;
  left: -20%;
  width: 20%;
  height: 100%;
  background: linear-gradient(90deg, transparent, var(--violet-bright), transparent);
}

@media (prefers-reduced-motion: no-preference) {
  .bothsides__signal { animation: bothsides-run 7s linear infinite; }
  .bothsides__node   { animation: bothsides-lift 7s ease-in-out infinite; animation-delay: calc(var(--i) * 2.3s); }
  .bothsides__stage  { animation: bothsides-lift 7s ease-in-out infinite; animation-delay: calc(var(--i) * 1.7s); }
}

@keyframes bothsides-run {
  0%   { left: -20%; }
  100% { left: 100%; }
}

@keyframes bothsides-lift {
  0%, 8%    { border-color: var(--border); }
  14%, 22%  { border-color: var(--violet); }
  30%, 100% { border-color: var(--border); }
}

@media (max-width: 34rem) {
  .bothsides__row--us { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .bothsides__row--deal { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bothsides__node, .bothsides__stage { font-size: .64rem; }
}

/* The animated panels on the About page sit at the same rhythm as a section's
   other content rather than butting against the paragraph above them. */
.about__visual { margin-top: 2.25rem; }

/* The founder's card carries the brand mark now; see .person__mark. */

/* ── News ────────────────────────────────────────────────────────────────
   One wall of cards, tabbed. The tabs change what is in the wall rather than
   how it looks, so there is one card design and two skins of it: a headline
   from an outlet, and an article of ours.

   The distinction has to survive a glance. A headline's publisher is named in
   violet above the title and the title carries an outbound arrow; ours says
   Neurotic in the site's own green and has no arrow, because it does not
   leave. */

/* ── The tabs ─────────────────────────────────────────────────────────── */
.newsfilter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: 2.25rem 0;
}

/* The standfirst under the tabs was sitting on top of the wall. It is the
   sentence that says what the wall is, so it gets to finish before the cards
   start. */
.page--news .section-lead { margin-bottom: 2.75rem; }

.newsfilter__tab {
  padding: .42rem .95rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: .85rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}

.newsfilter__tab:hover { border-color: var(--border-bright); color: var(--text); }

.newsfilter__tab.is-current {
  border-color: var(--violet-deep);
  background: color-mix(in srgb, var(--violet-deep) 22%, transparent);
  color: var(--text);
}

/* Ours is set apart from the sectors it sits beside, and not subtly: the others
   are places we read, and this is the only thing on the page we wrote. It is
   lit whether or not it is the one selected, which none of the others are. */
.newsfilter__tab--ours {
  margin-left: auto;
  border-color: color-mix(in srgb, var(--green) 45%, transparent);
  background: color-mix(in srgb, var(--green) 10%, transparent);
  color: var(--text);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 40%, transparent);
  animation: ours-glow 3.2s ease-in-out infinite;
}

.newsfilter__tab--ours::before {
  content: "";
  display: inline-block;
  width: .4rem;
  height: .4rem;
  margin-right: .5rem;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--green);
}

.newsfilter__tab--ours:hover,
.newsfilter__tab--ours.is-current {
  border-color: var(--green);
  background: color-mix(in srgb, var(--green) 20%, transparent);
}

/* A slow breath rather than a flash. It has to catch the eye on a page of
   somebody else's headlines without behaving like an advert. */
@keyframes ours-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 34%, transparent); }
  50%      { box-shadow: 0 0 0 .4rem color-mix(in srgb, var(--green) 0%, transparent); }
}

@media (max-width: 46rem) {
  .newsfilter__tab--ours { margin-left: 0; }
}

/* ── The collage ──────────────────────────────────────────────────────── */
.collage {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The lead story is what makes this a wall rather than a list. Only where
   there are at least two columns to span. */
@media (min-width: 46rem) {
  .clip--lead { grid-column: span 2; }
  .clip--lead .clip__title { font-size: 1.3rem; }
}

.clip__link {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  height: 100%;
  padding: 1.35rem 1.45rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: border-color .25s ease, background .25s ease, transform .25s ease;
}

.clip__link:hover {
  border-color: var(--border-bright);
  background: var(--surface-2);
  transform: translateY(-2px);
}

/* Every card carries a mark. Only two of the twelve feeds publish a picture, so
   the monogram is the design and the thumbnail is the exception laid over it -
   the other way round would give a wall three-quarters full of holes.

   The hue comes from the publisher's name and never changes, so the same outlet
   is the same colour every time it appears and the wall gains something a
   reader can navigate by rather than only decoration. */
.clip__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  flex: none;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid hsl(var(--hue, 265) 60% 62% / .28);
  background: linear-gradient(150deg,
    hsl(var(--hue, 265) 62% 58% / .30),
    hsl(var(--hue, 265) 62% 40% / .10));
}

.clip__initials {
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .04em;
  color: hsl(var(--hue, 265) 70% 82%);
}

/* Sits over the monogram rather than replacing it, so an image that fails to
   load uncovers the mark again with no error handling anywhere. */
.clip__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Ours is the brand mark, not a monogram, and it is the one colour on the wall
   that is not derived from somebody else's name. */
.clip__mark--ours {
  border-color: color-mix(in srgb, var(--green) 40%, transparent);
  background: color-mix(in srgb, var(--green) 12%, transparent);
}

.clip__mark-logo { width: 1.5rem; height: 1.5rem; }

.clip__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .65rem;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* The publisher, never dimmed away. Attribution is the condition on which
   these headlines are here at all. */
.clip__source { color: var(--violet); }
.clip__source--ours { color: var(--green); }
.clip__date { color: var(--text-dim); }

.clip__title {
  font-size: 1.02rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}

/* Marked as leaving the site, always rather than on hover: a reader deciding
   whether to click should know it takes them to the publisher. Our own
   articles do not get one, because they do not leave. */
.clip:not(.clip--ours) .clip__title::after {
  content: " \2197";
  font-size: .78em;
  color: var(--text-dim);
}

.clip__summary {
  font-size: .9rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* On the wall as in the tabs: ours is lit. A reader scanning a page of other
   people's headlines should be able to find the ones with our name on them
   without reading a single word. */
.clip--ours .clip__link {
  border-color: color-mix(in srgb, var(--green) 34%, var(--border));
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--green) 7%, var(--surface)),
    var(--surface) 60%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--green) 6%, transparent);
}

.clip--ours .clip__link:hover {
  border-color: var(--green);
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--green) 12%, var(--surface-2)),
    var(--surface-2) 60%);
}

@media (prefers-reduced-motion: reduce) {
  .newsfilter__tab--ours { animation: none; }
}

/* ── The ticker ──────────────────────────────────────────────────────────
   One line of headlines on the move, directly under the nav on every page, on
   the same marquee the pedigree strip uses. It pauses on hover so a headline
   can be read, and on focus-within so a keyboard reader is not chasing the
   thing it just tabbed to.

   No border on top: the nav above it already ends in one, and two rules
   touching read as a mistake. */
/* Its height is a variable rather than padding, because the strip is sticky and
   two other things need to know how tall it is: the offset that stops an anchor
   landing underneath it, and nothing else may guess. `:has` keeps it honest on
   the pages that have no ticker at all - the news page, and the minimal-chrome
   ones - where it has to be zero. allow_browser :modern already requires `:has`,
   so this is not a fallback question. */
body { --ticker-h: 0px; }
body:has(.ticker) { --ticker-h: 50px; }
[data-news-ticker="hidden"] body:has(.ticker) { --ticker-h: 34px; }

.ticker {
  position: sticky;
  /* Under the nav, and beneath it in the stacking order: an open menu covers
     the news rather than the other way round. */
  top: var(--bar-height);
  z-index: 400;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  height: var(--ticker-h);
  padding-inline: max(var(--gutter), calc((100% - var(--shell)) / 2));
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  overflow: hidden;
}

.ticker__label {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: none;
  margin: 0;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.ticker__label-link { color: var(--text); text-decoration: none; }
.ticker__label-link:hover { color: var(--violet-bright); }

.ticker__live {
  width: .45rem;
  height: .45rem;
  border-radius: 50%;
  background: var(--green);
  animation: ticker-pulse 2.4s ease-in-out infinite;
}

@keyframes ticker-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .3; }
}

.marquee--ticker { flex: 1 1 auto; min-width: 0; }

.ticker__track { animation-duration: 90s; gap: 0; padding-left: 0; }

.ticker__item {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: 0 1.5rem;
}

/* A separator between headlines, so two of them cannot read as one. */
.ticker__item + .ticker__item { border-left: 1px solid var(--border-bright); }

.ticker__link {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: color .2s ease;
}

.ticker__link:hover { color: var(--text); }

.ticker__source {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--violet);
}

.ticker__title { font-size: .9rem; }

.marquee--ticker:hover .marquee__track,
.marquee--ticker:focus-within .marquee__track { animation-play-state: paused; }

/* ── Hiding it ───────────────────────────────────────────────────────────
   Quiet by design. The control is a word at the end of the strip, dim until
   it is wanted, because dismissing the news should not feel like dismissing
   an advert. */
.ticker__toggle {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex: none;
  padding: .25rem .1rem;
  border: 0;
  background: none;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: color .2s ease;
}

.ticker__toggle:hover,
.ticker__toggle:focus-visible { color: var(--text); }

.ticker__chevron {
  width: .42rem;
  height: .42rem;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-.1em) rotate(225deg);
  transition: transform .25s ease;
}

/* Collapsed twice over: by the class the controller sets, and by the attribute
   the head script sets before the first paint. The second is what stops the
   strip appearing and then dropping out on every page load for somebody who
   already said no. */
.ticker.is-collapsed { --ticker-h: 34px; }

.marquee--ticker[hidden],
[data-news-ticker="hidden"] .marquee--ticker { display: none; }

.ticker.is-collapsed .ticker__chevron,
[data-news-ticker="hidden"] .ticker__chevron { transform: translateY(.15em) rotate(45deg); }

/* With the rail gone the label and the control would sit at opposite ends of an
   empty strip. Pulling them together is what makes the collapsed state read as
   a closed thing rather than a broken one. */
.ticker.is-collapsed .ticker__toggle,
[data-news-ticker="hidden"] .ticker__toggle { margin-left: auto; }

/* Stopped, not slowed. What is left is a row of headlines that can be scrolled
   sideways by hand, which is the same content without the motion. */
@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
  .marquee--ticker { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
  .ticker__live { animation: none; }
  .ticker__chevron { transition: none; }
}

/* On a phone the label is the first thing to go: the pulse and the word cost
   more room than they earn beside a headline. The control stays, because a
   strip that cannot be dismissed is worse on a small screen, not better. */
@media (max-width: 46rem) {
  .ticker { gap: .75rem; padding-inline: var(--gutter); }
  body:has(.ticker) { --ticker-h: 44px; }
  .ticker__label { display: none; }
  .ticker.is-collapsed .ticker__label,
  [data-news-ticker="hidden"] .ticker__label { display: flex; }
}

/* ── Admin: news curation ────────────────────────────────────────────── */
.admin-note {
  max-width: 60ch;
  margin: 0 0 1.25rem;
  font-size: .9rem;
  line-height: 1.65;
  color: var(--text-dim);
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  padding: 1rem 1rem 0;
}

/* ── Previews on a phone ─────────────────────────────────────────────────
   These consoles are drawn to be read at desk width. Stacked into one column
   they run past a thousand pixels, and offering a button to open all of it only
   traded four lines of scrolling for four screens of a layout that was never
   meant for a phone.

   So the preview is cut to about a third of a screen with a caption saying what
   it is. Both come from the controller: with no JavaScript the preview is shown
   in full, which is the right fallback, and the caption never appears on a
   desktop where it would be untrue. */
.console.is-compact .mock--console {
  max-height: 17rem;
  overflow: hidden;
  /* Faded rather than cut, so the edge reads as "there is more of this" instead
     of as a screenshot that ends mid-row. */
  mask-image: linear-gradient(#000 62%, transparent);
  -webkit-mask-image: linear-gradient(#000 62%, transparent);
}

.console__note {
  margin: .85rem 0 0;
  padding-left: 1.1rem;
  position: relative;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--text-dim);
}

/* A dot rather than an icon: this is a footnote about the drawing above it, not
   a warning about anything. */
.console__note::before {
  content: "";
  position: absolute;
  left: 0;
  top: .55em;
  width: .35rem;
  height: .35rem;
  border-radius: 50%;
  background: var(--violet);
}

/* ── The type floor on a phone ───────────────────────────────────────────
   These are sized to sit inside an interface at desk width, where small reads
   as chrome. On a phone the same rem values land under eleven pixels, which is
   not small, it is unreadable. Lifted to a floor rather than redesigned: they
   are still the smallest thing on the screen, just legible.

   The first group is our own copy and gets the higher floor; the second is
   labelling inside the previews, which may stay quieter. */
@media (max-width: 46rem) {
  .scan__label,
  .scan__deliverable,
  .vendor,
  .chart-rows__meta,
  .bench__name,
  .bothsides__node { font-size: .74rem; }

  .panel__cell-label,
  .dashboards__label,
  .inputs__age,
  .chart-rows__pill,
  .platform__fit,
  .bench__work-label { font-size: .7rem; }

  .cred__label { font-size: .74rem; }

  /* Set again later in the file, at their own specificity, so they would have
     walked straight past the floor above. */
  .band--tight .scan__deliverable { font-size: .74rem; }
  .mock--mini .chart-rows__meta { font-size: .7rem; }
  .demos .console__main .panel__cell-label { font-size: .7rem; }

  /* Three columns and the middle one was being squeezed to nothing, so the
     line that says what the row is never appeared at all. Name and verdict
     keep the top line; the description takes the width under them. */
  .chart-rows__row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: .15rem;
  }

  .chart-rows__meta { grid-column: 1 / -1; }
}

/* ── The founder ─────────────────────────────────────────────────────────
   A photograph where there is one, initials in a frame where there is not.
   Both are finished, so the page never looks like it is waiting for artwork. */
.person__mark {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  padding: 1.4rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, rgba(105, 67, 230, .22), transparent 70%);
}

/* A quote, not a biography. Set larger than the copy around it and hung off a
   rule, so it reads as something said rather than something written about. */
.person__quote {
  margin: .2rem 0 .9rem;
  padding-left: 1rem;
  border-left: 2px solid var(--violet-deep);
}

.person__quote p {
  margin: 0;
  max-width: 48ch;
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--text);
}

/* ── Where the bench is ──────────────────────────────────────────────────
   A globe hard against the left, turning, with the six places reporting in on
   the right. The canvas covers the whole block so the circuit drawn from a
   city can reach the card it belongs to; everything that is words is HTML over
   the top of it. */
.benchmap {
  position: relative;
  margin-top: 2.5rem;
  min-height: 34rem;
}

.benchmap__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The cards sit in the right half, clear of the sphere. */
.benchfeed {
  position: relative;
  display: grid;
  gap: .75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.benchcard {
  padding: .9rem 1.1rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.benchcard__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 0 0 .4rem;
}

.benchcard__dot {
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--green);
  align-self: center;
}

.benchcard__city { font-size: 1.02rem; font-weight: 600; color: var(--text); }

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

.benchcard__role { margin: 0 0 .6rem; font-size: .92rem; color: var(--text); }

/* Side by side rather than stacked. Three cards of six lines each is a column
   taller than the planet beside it. */
.benchcard__checks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem .9rem;
}

.benchcard__check {
  display: flex;
  align-items: baseline;
  gap: .45rem;
  font-size: .8rem;
  color: var(--text-dim);
}

.benchcard__check.is-on { color: var(--text-muted); }
.benchcard__tick { color: var(--violet); }
.benchcard__check.is-on .benchcard__tick { color: var(--green); }

/* The handshake line. Mono, dim, and the width is held steady so the scramble
   does not shove the card about while it resolves. */
.benchcard__cipher {
  margin: .65rem 0 0;
  padding-top: .55rem;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .08em;
  color: var(--violet);
  white-space: nowrap;
  overflow: hidden;
}

/* ── With the canvas running ─────────────────────────────────────────────
   The controller adds is-live once it has a globe to draw. Everything below
   here is that state; without it the block above is a plain list of six, which
   is the whole content and the correct fallback. */
.benchmap.is-live { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 21rem); }

/* Width stated rather than inherited. Every card inside is positioned, so the
   list has nothing in flow to be as wide as, and a grid item with no content
   collapses however wide its track is. */
.benchmap.is-live .benchfeed {
  grid-column: 2;
  align-self: center;
  display: block;
  width: 100%;
  min-height: 22rem;
}

/* Three at a time, arriving and leaving together. All six at once is a table
   and one at a time is a slideshow; a batch that comes in, reports, and clears
   off completely is the thing being described. The blank moment between batches
   is the point - a rolling window never resolves. */
.benchmap.is-live .benchfeed {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: .7rem;
  /* Held at the tallest a batch gets, so the column does not jump between
     batches while one is fading in and the other has gone. */
  min-height: 24rem;
  justify-content: center;
}

/* In the flow but invisible: `is-mounted` puts a card in the column so the
   three size themselves and stack without overlapping, and `is-showing` fades
   it in. Keeping those separate is what lets one batch fade out and the next
   fade in without either reflowing the other. */
.benchmap.is-live .benchcard {
  display: none;
  opacity: 0;
  transform: translateX(1.2rem);
  border-color: color-mix(in srgb, var(--green) 34%, var(--border-bright));
  box-shadow: 0 20px 50px rgba(0, 0, 0, .5);
  transition:
    opacity .55s cubic-bezier(.22, .61, .36, 1),
    transform .55s cubic-bezier(.22, .61, .36, 1);
}

.benchmap.is-live .benchcard.is-mounted { display: block; }

/* Staggered on the way in by position in the batch, so three cards do not
   arrive as one block. No delay on the way out: they leave together, which is
   what makes it a batch clearing rather than three cards expiring. */
.benchmap.is-live .benchcard.is-showing {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--n, 0) * .13s);
}

.benchmap.is-live .benchcard.is-live .benchcard__dot {
  animation: benchcard-ping 1.8s ease-out infinite;
}

@keyframes benchcard-ping {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 50%, transparent); }
  70%  { box-shadow: 0 0 0 .5rem color-mix(in srgb, var(--green) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 0%, transparent); }
}

/* Below the breakpoint the globe would be a thumbnail beside cards with no room
   to be cards, so the controller stands down and this is what is left. */
@media (max-width: 62rem) {
  .benchmap { min-height: 0; }
  .benchmap__canvas { display: none; }
  .benchfeed { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
}

/* ── News ────────────────────────────────────────────────────────────────
   One wall of cards, tabbed. The tabs change what is in the wall rather than
   how it looks, so there is one card design and two skins of it: a headline
   from an outlet, and an article of ours.

   The distinction has to survive a glance. A headline's publisher is named in
   violet above the title and the title carries an outbound arrow; ours says
   Neurotic in the site's own green and has no arrow, because it does not
   leave. */

/* ── The tabs ─────────────────────────────────────────────────────────── */
.newsfilter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: 2.25rem 0;
}

/* The standfirst under the tabs was sitting on top of the wall. It is the
   sentence that says what the wall is, so it gets to finish before the cards
   start. */
.page--news .section-lead { margin-bottom: 2.75rem; }

.newsfilter__tab {
  padding: .42rem .95rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: .85rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}

.newsfilter__tab:hover { border-color: var(--border-bright); color: var(--text); }

.newsfilter__tab.is-current {
  border-color: var(--violet-deep);
  background: color-mix(in srgb, var(--violet-deep) 22%, transparent);
  color: var(--text);
}

/* Ours is set apart from the sectors it sits beside, and not subtly: the others
   are places we read, and this is the only thing on the page we wrote. It is
   lit whether or not it is the one selected, which none of the others are. */
.newsfilter__tab--ours {
  margin-left: auto;
  border-color: color-mix(in srgb, var(--green) 45%, transparent);
  background: color-mix(in srgb, var(--green) 10%, transparent);
  color: var(--text);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 40%, transparent);
  animation: ours-glow 3.2s ease-in-out infinite;
}

.newsfilter__tab--ours::before {
  content: "";
  display: inline-block;
  width: .4rem;
  height: .4rem;
  margin-right: .5rem;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--green);
}

.newsfilter__tab--ours:hover,
.newsfilter__tab--ours.is-current {
  border-color: var(--green);
  background: color-mix(in srgb, var(--green) 20%, transparent);
}

/* A slow breath rather than a flash. It has to catch the eye on a page of
   somebody else's headlines without behaving like an advert. */
@keyframes ours-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 34%, transparent); }
  50%      { box-shadow: 0 0 0 .4rem color-mix(in srgb, var(--green) 0%, transparent); }
}

@media (max-width: 46rem) {
  .newsfilter__tab--ours { margin-left: 0; }
}

/* ── The collage ──────────────────────────────────────────────────────── */
.collage {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The lead story is what makes this a wall rather than a list. Only where
   there are at least two columns to span. */
@media (min-width: 46rem) {
  .clip--lead { grid-column: span 2; }
  .clip--lead .clip__title { font-size: 1.3rem; }
}

.clip__link {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  height: 100%;
  padding: 1.35rem 1.45rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: border-color .25s ease, background .25s ease, transform .25s ease;
}

.clip__link:hover {
  border-color: var(--border-bright);
  background: var(--surface-2);
  transform: translateY(-2px);
}

/* Every card carries a mark. Only two of the twelve feeds publish a picture, so
   the monogram is the design and the thumbnail is the exception laid over it -
   the other way round would give a wall three-quarters full of holes.

   The hue comes from the publisher's name and never changes, so the same outlet
   is the same colour every time it appears and the wall gains something a
   reader can navigate by rather than only decoration. */
.clip__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  flex: none;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid hsl(var(--hue, 265) 60% 62% / .28);
  background: linear-gradient(150deg,
    hsl(var(--hue, 265) 62% 58% / .30),
    hsl(var(--hue, 265) 62% 40% / .10));
}

.clip__initials {
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .04em;
  color: hsl(var(--hue, 265) 70% 82%);
}

/* Sits over the monogram rather than replacing it, so an image that fails to
   load uncovers the mark again with no error handling anywhere. */
.clip__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Ours is the brand mark, not a monogram, and it is the one colour on the wall
   that is not derived from somebody else's name. */
.clip__mark--ours {
  border-color: color-mix(in srgb, var(--green) 40%, transparent);
  background: color-mix(in srgb, var(--green) 12%, transparent);
}

.clip__mark-logo { width: 1.5rem; height: 1.5rem; }

.clip__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .65rem;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* The publisher, never dimmed away. Attribution is the condition on which
   these headlines are here at all. */
.clip__source { color: var(--violet); }
.clip__source--ours { color: var(--green); }
.clip__date { color: var(--text-dim); }

.clip__title {
  font-size: 1.02rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}

/* Marked as leaving the site, always rather than on hover: a reader deciding
   whether to click should know it takes them to the publisher. Our own
   articles do not get one, because they do not leave. */
.clip:not(.clip--ours) .clip__title::after {
  content: " \2197";
  font-size: .78em;
  color: var(--text-dim);
}

.clip__summary {
  font-size: .9rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* On the wall as in the tabs: ours is lit. A reader scanning a page of other
   people's headlines should be able to find the ones with our name on them
   without reading a single word. */
.clip--ours .clip__link {
  border-color: color-mix(in srgb, var(--green) 34%, var(--border));
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--green) 7%, var(--surface)),
    var(--surface) 60%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--green) 6%, transparent);
}

.clip--ours .clip__link:hover {
  border-color: var(--green);
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--green) 12%, var(--surface-2)),
    var(--surface-2) 60%);
}

@media (prefers-reduced-motion: reduce) {
  .newsfilter__tab--ours { animation: none; }
}

/* ── The ticker ──────────────────────────────────────────────────────────
   One line of headlines on the move, directly under the nav on every page, on
   the same marquee the pedigree strip uses. It pauses on hover so a headline
   can be read, and on focus-within so a keyboard reader is not chasing the
   thing it just tabbed to.

   No border on top: the nav above it already ends in one, and two rules
   touching read as a mistake. */
/* Its height is a variable rather than padding, because the strip is sticky and
   two other things need to know how tall it is: the offset that stops an anchor
   landing underneath it, and nothing else may guess. `:has` keeps it honest on
   the pages that have no ticker at all - the news page, and the minimal-chrome
   ones - where it has to be zero. allow_browser :modern already requires `:has`,
   so this is not a fallback question. */
body { --ticker-h: 0px; }
body:has(.ticker) { --ticker-h: 50px; }
[data-news-ticker="hidden"] body:has(.ticker) { --ticker-h: 34px; }

.ticker {
  position: sticky;
  /* Under the nav, and beneath it in the stacking order: an open menu covers
     the news rather than the other way round. */
  top: var(--bar-height);
  z-index: 400;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  height: var(--ticker-h);
  padding-inline: max(var(--gutter), calc((100% - var(--shell)) / 2));
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  overflow: hidden;
}

.ticker__label {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: none;
  margin: 0;
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.ticker__label-link { color: var(--text); text-decoration: none; }
.ticker__label-link:hover { color: var(--violet-bright); }

.ticker__live {
  width: .45rem;
  height: .45rem;
  border-radius: 50%;
  background: var(--green);
  animation: ticker-pulse 2.4s ease-in-out infinite;
}

@keyframes ticker-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .3; }
}

.marquee--ticker { flex: 1 1 auto; min-width: 0; }

.ticker__track { animation-duration: 90s; gap: 0; padding-left: 0; }

.ticker__item {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: 0 1.5rem;
}

/* A separator between headlines, so two of them cannot read as one. */
.ticker__item + .ticker__item { border-left: 1px solid var(--border-bright); }

.ticker__link {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: color .2s ease;
}

.ticker__link:hover { color: var(--text); }

.ticker__source {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--violet);
}

.ticker__title { font-size: .9rem; }

.marquee--ticker:hover .marquee__track,
.marquee--ticker:focus-within .marquee__track { animation-play-state: paused; }

/* ── Hiding it ───────────────────────────────────────────────────────────
   Quiet by design. The control is a word at the end of the strip, dim until
   it is wanted, because dismissing the news should not feel like dismissing
   an advert. */
.ticker__toggle {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex: none;
  padding: .25rem .1rem;
  border: 0;
  background: none;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  transition: color .2s ease;
}

.ticker__toggle:hover,
.ticker__toggle:focus-visible { color: var(--text); }

.ticker__chevron {
  width: .42rem;
  height: .42rem;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-.1em) rotate(225deg);
  transition: transform .25s ease;
}

/* Collapsed twice over: by the class the controller sets, and by the attribute
   the head script sets before the first paint. The second is what stops the
   strip appearing and then dropping out on every page load for somebody who
   already said no. */
.ticker.is-collapsed { --ticker-h: 34px; }

.marquee--ticker[hidden],
[data-news-ticker="hidden"] .marquee--ticker { display: none; }

.ticker.is-collapsed .ticker__chevron,
[data-news-ticker="hidden"] .ticker__chevron { transform: translateY(.15em) rotate(45deg); }

/* With the rail gone the label and the control would sit at opposite ends of an
   empty strip. Pulling them together is what makes the collapsed state read as
   a closed thing rather than a broken one. */
.ticker.is-collapsed .ticker__toggle,
[data-news-ticker="hidden"] .ticker__toggle { margin-left: auto; }

/* Stopped, not slowed. What is left is a row of headlines that can be scrolled
   sideways by hand, which is the same content without the motion. */
@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
  .marquee--ticker { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
  .ticker__live { animation: none; }
  .ticker__chevron { transition: none; }
}

/* On a phone the label is the first thing to go: the pulse and the word cost
   more room than they earn beside a headline. The control stays, because a
   strip that cannot be dismissed is worse on a small screen, not better. */
@media (max-width: 46rem) {
  .ticker { gap: .75rem; padding-inline: var(--gutter); }
  body:has(.ticker) { --ticker-h: 44px; }
  .ticker__label { display: none; }
  .ticker.is-collapsed .ticker__label,
  [data-news-ticker="hidden"] .ticker__label { display: flex; }
}

/* ── Admin: news curation ────────────────────────────────────────────── */
.admin-note {
  max-width: 60ch;
  margin: 0 0 1.25rem;
  font-size: .9rem;
  line-height: 1.65;
  color: var(--text-dim);
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  padding: 1rem 1rem 0;
}

/* ── Previews on a phone ─────────────────────────────────────────────────
   These consoles are drawn to be read at desk width. Stacked into one column
   they run past a thousand pixels, and offering a button to open all of it only
   traded four lines of scrolling for four screens of a layout that was never
   meant for a phone.

   So the preview is cut to about a third of a screen with a caption saying what
   it is. Both come from the controller: with no JavaScript the preview is shown
   in full, which is the right fallback, and the caption never appears on a
   desktop where it would be untrue. */
.console.is-compact .mock--console {
  max-height: 17rem;
  overflow: hidden;
  /* Faded rather than cut, so the edge reads as "there is more of this" instead
     of as a screenshot that ends mid-row. */
  mask-image: linear-gradient(#000 62%, transparent);
  -webkit-mask-image: linear-gradient(#000 62%, transparent);
}

.console__note {
  margin: .85rem 0 0;
  padding-left: 1.1rem;
  position: relative;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--text-dim);
}

/* A dot rather than an icon: this is a footnote about the drawing above it, not
   a warning about anything. */
.console__note::before {
  content: "";
  position: absolute;
  left: 0;
  top: .55em;
  width: .35rem;
  height: .35rem;
  border-radius: 50%;
  background: var(--violet);
}

/* ── The type floor on a phone ───────────────────────────────────────────
   These are sized to sit inside an interface at desk width, where small reads
   as chrome. On a phone the same rem values land under eleven pixels, which is
   not small, it is unreadable. Lifted to a floor rather than redesigned: they
   are still the smallest thing on the screen, just legible.

   The first group is our own copy and gets the higher floor; the second is
   labelling inside the previews, which may stay quieter. */
@media (max-width: 46rem) {
  .scan__label,
  .scan__deliverable,
  .vendor,
  .chart-rows__meta,
  .bench__name,
  .bothsides__node { font-size: .74rem; }

  .panel__cell-label,
  .dashboards__label,
  .inputs__age,
  .chart-rows__pill,
  .platform__fit,
  .bench__work-label { font-size: .7rem; }

  .cred__label { font-size: .74rem; }

  /* Set again later in the file, at their own specificity, so they would have
     walked straight past the floor above. */
  .band--tight .scan__deliverable { font-size: .74rem; }
  .mock--mini .chart-rows__meta { font-size: .7rem; }
  .demos .console__main .panel__cell-label { font-size: .7rem; }

  /* Three columns and the middle one was being squeezed to nothing, so the
     line that says what the row is never appeared at all. Name and verdict
     keep the top line; the description takes the width under them. */
  .chart-rows__row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: .15rem;
  }

  .chart-rows__meta { grid-column: 1 / -1; }
}

/* ── The founder ─────────────────────────────────────────────────────────
   A photograph where there is one, initials in a frame where there is not.
   Both are finished, so the page never looks like it is waiting for artwork. */
.person__photo {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: center 22%;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
}

/* A quote, not a biography. Set larger than the copy around it and hung off a
   rule, so it reads as something said rather than something written about. */
.person__quote {
  margin: .2rem 0 .9rem;
  padding-left: 1rem;
  border-left: 2px solid var(--violet-deep);
}

.person__quote p {
  margin: 0;
  max-width: 48ch;
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--text);
}

/* ── Where the bench is ──────────────────────────────────────────────────
   A globe with the six places around it, and deliberately a diagram rather
   than a map: the six span more than half the planet, so no one view of a
   sphere could hold them all, and a globe that quietly dropped two of them
   would be worse than one that never claimed to be a projection. */
.benchmap {
  position: relative;
  margin-top: 2.5rem;
  min-height: 44rem;
  display: grid;
  place-items: center;
}

/* Big enough to be the thing on the screen rather than an ornament beside the
   copy. Capped in rem as well as in percent so it does not become absurd on a
   very wide monitor. */
.benchmap__globe { width: min(34rem, 62%); }
.benchmap__globe svg { display: block; width: 100%; height: auto; }

.benchmap__limb {
  fill: rgba(105, 67, 230, .07);
  stroke: var(--border-bright);
  stroke-width: 1;
}

/* Each ring is a dashed ellipse whose dash offset animates, so the dots travel
   around it. Normalising every path to 100 is what keeps the angular speed the
   same from the equator to the pole, which is what makes the rings read as one
   turning body rather than a stack of hoops. */
/* Round caps and a short dash: each mark is a dot rather than a segment, which
   is what lets a handful of them per ring read as a surface. */
.benchmap__ring {
  fill: none;
  stroke: var(--violet-bright);
  stroke-width: 2.4;
  stroke-opacity: .75;
  stroke-linecap: round;
  stroke-dasharray: 0.35 calc(var(--period) - 0.35);
  animation: benchmap-turn calc(var(--period) * 3.4s) linear infinite;
}

@keyframes benchmap-turn {
  to { stroke-dashoffset: calc(var(--period) * -1); }
}

.benchmap__nodes {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Six nodes on an ellipse, placed by index. The trig is done here rather than
   in the markup so the ring can be reshaped by changing two numbers. */
.benchspot {
  --angle: calc(var(--i) * 60deg - 90deg);
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  gap: .5rem;
  /* An ellipse wider than it is tall, so the six sit around the sphere rather
     than on it: the vertical radius clears the limb, the horizontal one gives
     the long names room. */
  translate:
    calc(cos(var(--angle)) * 22rem - 50%)
    calc(sin(var(--angle)) * 18.5rem - 50%);
  padding: .4rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  white-space: nowrap;
  cursor: default;
  transition: border-color .2s ease, background .2s ease;
}

.benchspot:hover,
.benchspot:focus-visible {
  border-color: var(--violet-deep);
  background: var(--surface-2);
  outline: none;
  z-index: 3;
}

.benchspot__dot {
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent);
}

.benchspot__place { font-size: .84rem; color: var(--text); }

/* The panel. Opens on hover and on focus, so it is reachable with a keyboard
   and not only with a mouse - and it is CSS rather than a controller, because
   a panel that appears on hover is the one thing CSS has always done. */
.benchspot__card {
  position: absolute;
  top: calc(100% + .5rem);
  left: 50%;
  translate: -50% 0;
  width: 14rem;
  padding: .85rem .95rem 1rem;
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  background: rgba(6, 3, 14, .96);
  box-shadow: 0 16px 40px rgba(0, 0, 0, .55);
  white-space: normal;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-.3rem);
  transition: opacity .2s ease, transform .2s ease, visibility .2s;
}

/* The three nodes on the lower half of the ring open upward. Opening downward
   put their panel through the bottom of the section and over whatever follows,
   which for the last one was the footer. Positions are fixed by index, so which
   three they are is known here rather than measured at runtime. */
.benchspot:nth-child(3) .benchspot__card,
.benchspot:nth-child(4) .benchspot__card,
.benchspot:nth-child(5) .benchspot__card {
  top: auto;
  bottom: calc(100% + .5rem);
  transform: translateY(.3rem);
}

.benchspot:hover .benchspot__card,
.benchspot:focus-visible .benchspot__card,
.benchspot:focus-within .benchspot__card,
.benchspot.is-live .benchspot__card {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* The node the walk is currently on, marked the same way a pointed-at one is,
   so the two states do not look like different things happening. */
.benchspot.is-live {
  border-color: var(--violet-deep);
  background: var(--surface-2);
  z-index: 2;
}

.benchspot.is-live .benchspot__dot {
  animation: benchspot-ping 1.6s ease-out infinite;
}

@keyframes benchspot-ping {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 45%, transparent); }
  70%  { box-shadow: 0 0 0 .55rem color-mix(in srgb, var(--green) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 0%, transparent); }
}

.benchspot__role {
  margin: 0;
  font-size: .92rem;
  font-weight: 600;
  color: var(--text);
}

.benchspot__count {
  margin: .1rem 0 .8rem;
  font-size: .82rem;
  color: var(--text-muted);
}

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

.benchspot__meter {
  display: block;
  height: 4px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .09);
  overflow: hidden;
}

.benchspot__meter-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--violet-deep), var(--violet-bright));
}

.benchspot__check {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  margin: .8rem 0 0;
  font-size: .76rem;
  color: var(--text-muted);
}

.benchspot__mark { color: var(--green); }
.benchspot__check--in-review .benchspot__mark { color: var(--violet); }

/* Below the breakpoint the ring has nowhere to go, and hovering is not a
   gesture anyone has on a phone. The nodes become cards with everything
   already showing, which is the same information without the interaction. */
@media (max-width: 62rem) {
  .benchmap {
    min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
  }

  .benchmap__globe { width: 9rem; }

  .benchmap__nodes {
    position: static;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: .75rem;
    width: 100%;
  }

  .benchspot {
    position: static;
    translate: none;
    display: block;
    padding: 1rem 1.1rem 1.15rem;
    border-radius: var(--radius-lg);
    white-space: normal;
  }

  .benchspot__dot { display: inline-block; margin-right: .5rem; }

  .benchspot__card {
    position: static;
    translate: none;
    width: auto;
    margin-top: .7rem;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .benchmap__ring { animation: none; }
  .benchspot__card { transition: none; }
  .benchspot__dot { animation: none; }
}
