/* =========================================================================
   soc2certifications.com

   One page. One correction. Delivered as a late-night broadcast PSA.

   Hand written CSS, no framework, no build step. If you are adding to this,
   read docs/design.md first: the restraint here is deliberate and most of the
   "missing" flourishes were considered and cut.
   ========================================================================= */

/* ---------- 1. Fonts ------------------------------------------------------

   Two variable faces, self hosted, latin subset only (~129 KB total). No
   Google Fonts link: the site would work either way, but a third party font
   request on a page whose entire subject is "read the fine print" felt wrong.

   Raleway replaced Archivo (2026-07-30, owner's call, matching the reference
   site doesnickoffermansayfremulon.com). It carries NO width axis, so every
   font-stretch declaration that used to tune Archivo's wdth was removed
   rather than left as a silent no-op. The display punch now comes from weight
   and tracking instead: 900 for the hero, 800 for section heads, 700 for
   card heads and labels. Raleway's 900 is genuinely heavy and its 200 is
   genuinely thin, which is the whole range the reference leans on.

   If you reintroduce font-stretch here, check the family has a wdth axis
   first. Raleway does not, and the declaration will do nothing.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Raleway";
  src: url("../fonts/raleway-latin-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Literata";
  src: url("../fonts/literata-latin-var.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

/* ---------- 2. Tokens ---------------------------------------------------- */

:root {
  /* Surfaces. Every neutral is tinted toward Sage indigo (#251F6F is roughly
     oklch(0.28 0.16 279)) so the palette holds together even where the brand
     colour itself never appears. */
  --ink-900: oklch(0.145 0.038 279); /* page */
  --ink-850: oklch(0.170 0.042 279); /* alternating band */
  --ink-800: oklch(0.205 0.045 279); /* raised panel */
  --ink-700: oklch(0.262 0.046 279); /* chips, table zebra */
  --ink-600: oklch(0.330 0.042 279); /* borders */
  --ink-500: oklch(0.420 0.038 279); /* hairlines on panels. NOT for text */
  /* --ink-500 measured 2.24:1 as the report numerals and failed outright. It
     stays where it is because as a hairline it is correct and a border only
     owes 3:1 as a UI component. Text got its own token instead: 4.11:1, over
     the 3:1 large-text threshold (21.6px at weight 700) with margin, and still
     quiet enough to read as a spine rather than a heading. */
  --ink-text-quiet: oklch(0.560 0.038 279);

  /* Type. Broadcast cream, never pure white: #fff on this surface buzzes. */
  --paper: oklch(0.962 0.013 85);
  --paper-2: oklch(0.790 0.020 279);
  --paper-3: oklch(0.635 0.024 279);

  /* Accents. Used sparingly and for one meaning each. */
  --signal: oklch(0.805 0.148 158); /* correct / Sage green on dark */
  /* 0.60, not 0.56. At 0.56 this measured 4.35:1 on --ink-850 and the green
     chyron is 11px uppercase, i.e. small text needing 4.5:1. 0.60 measures
     5.11:1. Measured with a canvas readback, not eyeballed: getComputedStyle
     returns oklch() as oklch() in Chrome, so string-parsing it for a contrast
     check silently produces nonsense. */
  --signal-dim: oklch(0.600 0.105 158);
  --alarm: oklch(0.690 0.180 25); /* the misused phrase */
  --amber: oklch(0.830 0.150 78); /* the live tally, the star */

  /* The rainbow. Appears exactly once, in the star band. That is the point of
     it: an accent used twice is decoration, used once it is an event. */
  --rb-1: oklch(0.680 0.200 25);
  --rb-2: oklch(0.780 0.170 60);
  --rb-3: oklch(0.880 0.160 95);
  --rb-4: oklch(0.790 0.180 145);
  --rb-5: oklch(0.700 0.150 230);
  --rb-6: oklch(0.610 0.170 285);

  /* Light slate, for the one section that stops joking. */
  --slate-bg: oklch(0.968 0.008 85);
  --slate-ink: oklch(0.255 0.050 279);
  --slate-ink-2: oklch(0.450 0.030 279);
  --slate-rule: oklch(0.880 0.012 279);

  /* Type families */
  --font-display: "Raleway", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-body: "Literata", Georgia, "Times New Roman", serif;

  /* Type scale. Fluid on a marketing page; ratio never below 1.25 between
     steps so the hierarchy reads at a glance from a phone screenshot. */
  --t-hero: clamp(2.75rem, 1.15rem + 6.6vw, 6.5rem);
  --t-h2: clamp(1.95rem, 1.35rem + 2.5vw, 3.35rem);
  --t-h3: clamp(1.2rem, 1.06rem + 0.6vw, 1.5rem);
  --t-lede: clamp(1.15rem, 1.04rem + 0.45vw, 1.4rem);
  --t-body: clamp(1.0rem, 0.972rem + 0.14vw, 1.125rem);
  --t-small: 0.9375rem;
  --t-label: 0.6875rem;

  /* Space. 4pt scale, semantic names. */
  --s-2xs: 0.25rem;
  --s-xs: 0.5rem;
  --s-sm: 0.75rem;
  --s-md: 1rem;
  --s-lg: 1.5rem;
  --s-xl: 2rem;
  --s-2xl: 3rem;
  --s-3xl: 4rem;
  --s-4xl: 6rem;
  --s-section: clamp(3.5rem, 2rem + 6vw, 7.5rem);

  --measure: 66ch;
  --shell: 74rem;
  --pad: clamp(1.25rem, 0.5rem + 3vw, 3.5rem);

  /* Motion. Exponential deceleration; nothing bounces. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---------- 3. Reset and base -------------------------------------------- */

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

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

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

body {
  margin: 0;
  background: var(--ink-900);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--t-body);
  /* Light type on a dark ground reads a weight lighter than it is, so the
     leading is opened past the usual 1.6. */
  line-height: 1.72;
  /* lining-nums is LOAD BEARING, not a nicety. Raleway defaults to old-style
     figures, so "SOC 2" rendered with a 2 at 81% of cap height, sitting at
     the x-height like a lowercase letter. On a page whose headline, logo and
     domain all hinge on that exact string, it read as a typo. Measured:
     digit ascent 69.7 vs cap 85.9 before, matched after.
     If the display face is ever swapped again, re-check this. */
  font-variant-numeric: lining-nums tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1,
h2,
h3,
.display {
  font-family: var(--font-display);
  /* 800, not 700. Raleway needs more weight than Archivo did to hold the same
     visual authority, because it has no expanded width to lean on. */
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}

p {
  margin: 0;
  max-width: var(--measure);
}

/* There is no italic face in the build, and a faux-slanted Literata looks
   exactly like what it is. Emphasis is carried by weight and colour instead.
   If an italic is ever genuinely needed, add the real Literata italic file
   rather than deleting this rule. */
em {
  font-style: normal;
  font-weight: 600;
  color: var(--paper);
}

a {
  color: var(--signal);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: color 0.18s var(--ease-soft);
}

a:hover {
  color: var(--paper);
}

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

img,
svg {
  max-width: 100%;
  display: block;
}

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

.skip-link {
  position: absolute;
  top: -100%;
  left: var(--s-md);
  z-index: 100;
  padding: var(--s-sm) var(--s-md);
  background: var(--paper);
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus {
  top: var(--s-md);
}

/* ---------- 4. Broadcast texture ----------------------------------------

   Two fixed overlays: horizontal scanlines and a corner vignette. Both are
   pointer-events:none and sit under nothing that matters. They are static.
   An animated scanline roll was built, looked authentic for about four
   seconds, and then made the copy genuinely hard to read; it is not coming
   back.
   -------------------------------------------------------------------------- */

.tube-lines,
.tube-vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.tube-lines {
  background-image: repeating-linear-gradient(
    to bottom,
    oklch(1 0 0 / 0.025) 0px,
    oklch(1 0 0 / 0.025) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: overlay;
}

.tube-vignette {
  background: radial-gradient(
    120% 90% at 50% 42%,
    transparent 45%,
    oklch(0.09 0.03 279 / 0.55) 100%
  );
}

/* ---------- 5. Shell and section rhythm ---------------------------------- */

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

.band {
  position: relative;
  z-index: 3;
  padding-block: var(--s-section);
}

.band--alt {
  background: var(--ink-850);
}

/* The chyron label. Sits above every section heading. All caps is fine here:
   it is a two or three word broadcast slug, never a sentence. */
.chyron {
  display: inline-flex;
  align-items: center;
  gap: var(--s-xs);
  font-family: var(--font-display);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper-3);
  margin-bottom: var(--s-md);
}

.chyron::before {
  content: "";
  width: 1.75rem;
  height: 1px;
  background: var(--ink-500);
}

.chyron--signal {
  color: var(--signal-dim);
}

.chyron--signal::before {
  background: var(--signal-dim);
}

.section-head {
  margin-bottom: var(--s-2xl);
}

.section-head h2 {
  font-size: var(--t-h2);
  max-width: 18ch;
}

.section-head .lede {
  margin-top: var(--s-lg);
  font-size: var(--t-lede);
  line-height: 1.6;
  color: var(--paper-2);
  max-width: 58ch;
}

/* ---------- 6. Hero ------------------------------------------------------ */

.hero {
  position: relative;
  z-index: 3;
  min-height: min(96svh, 60rem);
  display: flex;
  flex-direction: column;
  padding-top: clamp(1.25rem, 0.5rem + 3vw, 2.5rem);
}

/* Station ident strip across the top */
.ident {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
  font-family: var(--font-display);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper-3);
  padding-bottom: var(--s-md);
  border-bottom: 1px solid var(--ink-700);
}

.ident__live {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  color: var(--paper-2);
}

.tally {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--alarm);
  box-shadow: 0 0 0 3px oklch(0.69 0.18 25 / 0.18);
  animation: tally 2.6s var(--ease-soft) infinite;
}

@keyframes tally {
  0%,
  55%,
  100% {
    opacity: 1;
  }
  70% {
    opacity: 0.25;
  }
}

.ident__domain {
  color: var(--paper-3);
}

/* On a phone the two idents collide and "Public Service Announcement" wraps
   to two lines against a vertically centred tally dot. The domain is the
   line that goes: the hero's first sentence names it anyway. */
@media (max-width: 32rem) {
  .ident__domain {
    display: none;
  }
}

.hero__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: clamp(2.5rem, 1rem + 7vw, 6rem);
}

.hero__head {
  font-size: var(--t-hero);
  /* 900 is the whole reason Raleway is here. The reference site sets its
     punchline at 900 and its subline at 200, and that spread is the voice. */
  font-weight: 900;
  letter-spacing: -0.035em;
  /* 1.06, not 1.0. The cross-out overshoots its word vertically, and at 1.0
     the lines sit so close that its lower tail landed on "do not exist.". */
  line-height: 1.06;
}

/* Each line is its own block, which retired a fiddly max-width: 10.5ch hack.
   That value existed only to force "certifications" onto a line by itself
   through wrapping, and it was measured against Archivo's ch width, so the
   font swap would have required re-measuring it. Explicit markup lines break
   where they are told, in any face, at any size. */
.hero__head .beat {
  display: block;
}

/* The word the entire site exists to remove, crossed out by hand.
 *
 * This replaced a single horizontal strikethrough. A strike reads as "edited";
 * two crossing marker strokes read as "no", which is the actual sentiment, and
 * they take long enough to draw that you watch it happen.
 *
 * Built from two pseudo-element bars rather than an SVG on purpose. An SVG
 * stretched over a word this wide needs preserveAspectRatio="none", which
 * distorts stroke width, and the usual fix (vector-effect: non-scaling-stroke)
 * makes stroke-dasharray lengths resolve in screen units, so a hardcoded dash
 * value can no longer be trusted to cover the whole line. Two rotated bars
 * scaled from 0 to 1 have none of those problems and are em-sized, so they
 * track the fluid headline at every viewport.
 *
 * The bars pivot from their left end (transform-origin: 0 50%), one starting
 * high and sweeping down, one starting low and sweeping up, so they cross near
 * the middle of the word. --x-tilt steepens on narrow screens because the word
 * gets proportionally taller there and a desktop angle would look almost flat.
 */
.xword {
  position: relative;
  display: inline-block;
  color: var(--alarm);
  white-space: nowrap;
  /* Steep enough to read as a refusal. At 6.5deg the two bars barely
     diverged across a word this wide and it looked like a double
     strikethrough, i.e. an editing mark rather than a No. */
  --x-tilt: 9.5deg;
}

.xmark {
  position: absolute;
  /* Overshoots the glyphs on all four sides, the way a marker stroke does
     when somebody means it. Stopping exactly at the word edges looked
     mechanical. */
  left: -0.09em;
  right: -0.09em;
  top: -0.06em;
  bottom: -0.06em;
  pointer-events: none;
}

.xmark::before,
.xmark::after {
  content: "";
  position: absolute;
  left: 0;
  width: 101%;
  height: 0.085em;
  background: var(--alarm);
  border-radius: 0.05em;
  transform-origin: 0 50%;
  animation: x-draw 0.62s var(--ease-soft) both;
}

/* The rotation is passed through --x-rot, NOT set as its own transform
   declaration. A keyframe that animates `transform` replaces the property
   outright for the duration, so a separate `transform: rotate()` on the
   element is discarded the moment the animation starts and both bars would
   draw dead flat. The keyframe has to carry the rotation itself. */
.xmark::before {
  top: 20%;
  --x-rot: var(--x-tilt);
  animation-delay: 1.75s;
}

.xmark::after {
  top: 80%;
  --x-rot: calc(var(--x-tilt) * -1);
  animation-delay: 2.05s;
}

@keyframes x-draw {
  from {
    transform: rotate(var(--x-rot, 0deg)) scaleX(0);
  }
  to {
    transform: rotate(var(--x-rot, 0deg)) scaleX(1);
  }
}

/* There is deliberately NO breakpoint override for --x-tilt.
 *
 * An earlier version steepened it to 15deg on narrow screens, reasoning that
 * the word gets proportionally taller there. It does not. A rotated bar spans
 * width x tan(tilt) vertically, and BOTH the word width and the line box
 * scale with font-size, so their ratio is constant at every viewport. One
 * angle is correct everywhere.
 *
 * The override made the cross span ~148% of the line box on a phone instead
 * of ~92%, so it speared "SOC 2" above and "do not exist." below. If the X
 * ever looks wrong at one width, the fix is the single value above, not a
 * media query.
 */

.hero__lede {
  margin-top: var(--s-xl);
  font-size: var(--t-lede);
  line-height: 1.62;
  color: var(--paper-2);
  max-width: 54ch;
}

.hero__lede strong {
  color: var(--paper);
  font-weight: 600;
}

.hero__meta {
  margin-top: var(--s-2xl);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-lg);
}

/* The staged reveal, after doesnickoffermansayfremulon.com.
 *
 * That site fades in "Yes." at 1s, "Yes he does." at 2s and its explanation at
 * 3s, and the pacing IS the joke: a line that arrives alone lands harder than
 * three that arrive together. Same idea here, with the cross-out landing
 * between the second and third beats so you watch the word get denied before
 * being told what is true.
 *
 * Keyframes, NOT transitions plus a JS class. animation-fill-mode runs whether
 * or not psa.js loads, so this hero physically cannot end up stuck at opacity
 * 0 the way the old .js-gated version could. That is why there is no .js
 * prefix on these rules and why removing it would be a regression.
 *
 * Timeline, if you are retiming it, keep the gaps or it stops being funny:
 *   0.25s  SOC 2
 *   0.85s  certifications
 *   1.75s  first cross stroke
 *   2.05s  second cross stroke
 *   2.60s  do not exist.
 *   3.15s  the lede
 *   3.55s  the buttons
 */
.beat {
  opacity: 0;
  animation: beat-in 0.75s var(--ease-soft) both;
}

@keyframes beat-in {
  from {
    opacity: 0;
    transform: translateY(0.3em);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.beat--1 {
  animation-delay: 0.25s;
}
.beat--2 {
  animation-delay: 0.85s;
}
.beat--3 {
  animation-delay: 2.6s;
}
.beat--4 {
  animation-delay: 3.15s;
}
.beat--5 {
  animation-delay: 3.55s;
}

/* Ticker: a chyron of the phrase in the wild. Decorative, aria-hidden,
   pausable, and completely static under reduced motion. */
.ticker {
  position: relative;
  z-index: 3;
  border-block: 1px solid var(--ink-700);
  background: var(--ink-850);
  overflow: hidden;
  padding-block: var(--s-sm);
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    #000 6%,
    #000 94%,
    transparent
  );
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 6%,
    #000 94%,
    transparent
  );
}

.ticker__track {
  display: flex;
  width: max-content;
  gap: var(--s-2xl);
  padding-right: var(--s-2xl);
  animation: ticker 48s linear infinite;
}

.ticker:hover .ticker__track,
.ticker:focus-within .ticker__track {
  animation-play-state: paused;
}

.ticker__item {
  font-family: var(--font-display);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper-3);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: var(--s-md);
}

.ticker__item::after {
  content: "";
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--alarm);
  flex: none;
}

@keyframes ticker {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

/* ---------- 7. Buttons --------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-xs);
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 0.72em 1.15em;
  /* 44px floor. The em padding alone gave ~37px, under the WCAG 2.2 AAA
     target-size minimum and under what Lighthouse wants for tap targets. */
  min-height: 44px;
  border-radius: 2px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color 0.18s var(--ease-soft),
    color 0.18s var(--ease-soft),
    border-color 0.18s var(--ease-soft),
    transform 0.18s var(--ease-soft);
}

.btn:active {
  transform: translateY(1px);
}

.btn--solid {
  background: var(--paper);
  color: var(--ink-900);
}

.btn--solid:hover {
  background: var(--signal);
  color: var(--ink-900);
}

.btn--ghost {
  background: transparent;
  color: var(--paper-2);
  border-color: var(--ink-600);
}

.btn--ghost:hover {
  color: var(--paper);
  border-color: var(--paper-3);
}

.btn--ghost[data-copied="true"] {
  color: var(--signal);
  border-color: var(--signal-dim);
}

/* ---------- 8. Comparison table -----------------------------------------

   A real <table>, not a pair of cards. The row spine is what makes the
   distinction legible: certification and attestation differ line by line and
   the eye needs to track across.
   -------------------------------------------------------------------------- */

.compare-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border: 1px solid var(--ink-600);
  border-radius: 3px;
  background: var(--ink-800);
}

.compare {
  width: 100%;
  min-width: 46rem;
  border-collapse: collapse;
  font-size: var(--t-small);
  line-height: 1.55;
}

.compare th,
.compare td {
  text-align: left;
  vertical-align: top;
  padding: var(--s-md) var(--s-lg);
  border-bottom: 1px solid var(--ink-700);
}

.compare tbody tr:last-child th,
.compare tbody tr:last-child td {
  border-bottom: 0;
}

.compare thead th {
  position: sticky;
  top: 0;
  background: var(--ink-700);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
  color: var(--paper);
  padding-block: var(--s-lg);
  border-bottom: 1px solid var(--ink-500);
}

.compare thead th small {
  display: block;
  margin-top: 0.35em;
  font-family: var(--font-display);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-3);
}

.compare tbody th {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--paper-3);
  width: 22%;
  white-space: normal;
}

.compare td {
  color: var(--paper-2);
  width: 39%;
}

.compare td em {
  font-style: normal;
  color: var(--paper);
}

.compare .col-attest {
  background: oklch(0.805 0.148 158 / 0.07);
}

.compare thead .col-attest {
  background: oklch(0.805 0.148 158 / 0.14);
}

.scroll-hint {
  margin-top: var(--s-sm);
  font-family: var(--font-display);
  font-size: var(--t-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--paper-3);
}

@media (min-width: 47rem) {
  .scroll-hint {
    display: none;
  }
}

/* Narrow screens: the table stops being a table.

   A two-column comparison is read ACROSS the row, and at 390px a horizontally
   scrolling table shows the row label plus a sliver of one column, so the
   comparison, which is the entire point, becomes impossible. Each attribute
   becomes its own block instead, with both answers stacked and labelled from
   the data-col attribute (thead is hidden, so the column names have to come
   from somewhere). The markup stays a real <table>, so screen readers and
   copy-paste keep the row and column relationships either way. */
@media (max-width: 46.99rem) {
  .compare-wrap {
    overflow: visible;
    border: 0;
    background: none;
  }

  .compare,
  .compare tbody,
  .compare tr,
  .compare tbody th,
  .compare td {
    display: block;
    width: auto;
  }

  .compare {
    min-width: 0;
  }

  .compare thead {
    display: none;
  }

  .compare tbody tr {
    border: 1px solid var(--ink-600);
    border-radius: 3px;
    background: var(--ink-800);
    padding: var(--s-md) var(--s-lg) var(--s-lg);
  }

  .compare tbody tr + tr {
    margin-top: var(--s-md);
  }

  .compare tbody th {
    padding: 0 0 var(--s-sm);
    border-bottom: 1px solid var(--ink-600);
    color: var(--paper);
  }

  .compare td {
    padding: var(--s-md) 0 0;
    border-bottom: 0;
  }

  .compare td::before {
    content: attr(data-col);
    display: block;
    margin-bottom: 0.3em;
    font-family: var(--font-display);
    font-size: var(--t-label);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--paper-3);
  }

  .compare .col-attest {
    background: transparent;
  }

  .compare .col-attest::before {
    color: var(--signal);
  }

  .scroll-hint {
    display: none;
  }
}

/* A flat statement under the table. Not a card, not a callout box: a rule and
   a sentence, which is all it needs.

   Two children rather than a nested span, because the claim wants a short
   measure (it reads as a headline) and the explanation wants a long one. A
   span inside the claim inherits the claim's max-width and cannot escape it. */
.verdict {
  margin-top: var(--s-2xl);
  padding-top: var(--s-lg);
  border-top: 1px solid var(--ink-600);
}

.verdict__head {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-h3);
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--paper);
  max-width: 34ch;
}

.verdict__body {
  color: var(--paper-3);
  font-size: var(--t-small);
  line-height: 1.68;
  margin-top: var(--s-sm);
  max-width: 62ch;
}

/* ---------- 9. Report anatomy -------------------------------------------

   The contents page of a real SOC 2 report, set as an ordered list with a
   hairline spine. Deliberately not five cards in a grid.
   -------------------------------------------------------------------------- */

.anatomy {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: none;
}

.anatomy li {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: var(--s-lg);
  padding-block: var(--s-lg);
  border-top: 1px solid var(--ink-700);
}

/* Wide screens get a genuine contents-page layout: numeral, section title,
   and description in three tracks. At one track the right half of a 1440px
   viewport sat empty for the length of the section. */
@media (min-width: 66rem) {
  .anatomy li {
    grid-template-columns: 4.5rem minmax(0, 21rem) minmax(0, 1fr);
    gap: var(--s-xl);
    align-items: baseline;
  }

  .anatomy li > div {
    display: contents;
  }

  .anatomy h3 {
    margin-bottom: 0;
  }
}

.anatomy li:last-child {
  border-bottom: 1px solid var(--ink-700);
}

.anatomy__num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.35rem;
  line-height: 1.15;
  color: var(--ink-text-quiet);
  letter-spacing: -0.01em;
}

.anatomy h3 {
  font-size: var(--t-h3);
  margin-bottom: var(--s-xs);
}

.anatomy p {
  color: var(--paper-2);
  font-size: var(--t-small);
  line-height: 1.68;
  max-width: 62ch;
}

.anatomy li[data-emphasis="true"] .anatomy__num {
  color: var(--signal);
}

.anatomy li[data-emphasis="true"] h3 {
  color: var(--paper);
}

.tag {
  display: inline-block;
  margin-left: 0.6em;
  padding: 0.22em 0.6em;
  border-radius: 2px;
  border: 1px solid var(--ink-500);
  font-family: var(--font-display);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper-3);
  vertical-align: 0.28em;
  white-space: nowrap;
}

.tag--signal {
  color: var(--signal);
  border-color: var(--signal-dim);
}

/* Two supporting blocks under the anatomy. Different widths on purpose so
   this does not read as another card pair. */
.split {
  display: grid;
  gap: var(--s-2xl);
  margin-top: var(--s-3xl);
  grid-template-columns: 1fr;
}

@media (min-width: 54rem) {
  .split {
    grid-template-columns: 1.15fr 1fr;
    gap: var(--s-4xl);
  }
}

.split h3 {
  font-size: var(--t-h3);
  margin-bottom: var(--s-md);
}

.deflist {
  margin: 0;
  font-size: var(--t-small);
}

.deflist dt {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--paper);
  margin-top: var(--s-md);
  letter-spacing: -0.005em;
}

.deflist dt:first-child {
  margin-top: 0;
}

.deflist dd {
  margin: var(--s-2xs) 0 0;
  color: var(--paper-2);
  line-height: 1.65;
  max-width: 52ch;
}

.crit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--t-small);
}

/* Grid, not flex with a min-width: "Processing Integrity" overruns any min
   that fits the other four, and flex then pushed that one row's description
   out of alignment with its neighbours. */
.crit-list li {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: var(--s-sm);
  align-items: baseline;
  padding-block: var(--s-sm);
  border-bottom: 1px solid var(--ink-700);
  color: var(--paper-2);
}

.crit-list li:last-child {
  border-bottom: 0;
}

.crit-list b {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--paper);
}

.crit-list li[data-required="true"] b {
  color: var(--signal);
}

.note {
  margin-top: var(--s-lg);
  font-size: var(--t-small);
  color: var(--paper-3);
  line-height: 1.65;
}

/* ---------- 10. Who can issue -------------------------------------------- */

.issuer {
  display: grid;
  gap: var(--s-2xl);
  grid-template-columns: 1fr;
}

@media (min-width: 54rem) {
  .issuer {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-4xl);
  }
}

.requires {
  list-style: none;
  margin: 0;
  padding: 0;
}

.requires li {
  padding-block: var(--s-md);
  border-bottom: 1px solid var(--ink-700);
}

.requires li:first-child {
  border-top: 1px solid var(--ink-700);
}

.requires b {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
  color: var(--paper);
  margin-bottom: var(--s-2xs);
}

.requires span {
  font-size: var(--t-small);
  color: var(--paper-2);
  line-height: 1.65;
  display: block;
  max-width: 46ch;
}

/* The "cannot" list. Set as a run of struck words rather than a bulleted
   list of shame, so it reads as one gag and not seven jabs. */
.cannot {
  align-self: start;
  padding: var(--s-xl);
  background: var(--ink-800);
  border: 1px solid var(--ink-600);
  border-radius: 3px;
}

.cannot h3 {
  font-size: var(--t-h3);
  margin-bottom: var(--s-md);
}

.cannot__items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-xs);
  margin-bottom: var(--s-lg);
  padding: 0;
  list-style: none;
}

.cannot__items li {
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--paper-3);
  padding: 0.3em 0.7em;
  border: 1px solid var(--ink-600);
  border-radius: 2px;
  text-decoration: line-through;
  text-decoration-color: var(--alarm);
  text-decoration-thickness: 2px;
}

.cannot p {
  font-size: var(--t-small);
  color: var(--paper-2);
  line-height: 1.68;
}

.cannot p + p {
  margin-top: var(--s-md);
}

.cannot em {
  font-style: normal;
  color: var(--paper);
}

/* ---------- 11. Say this instead ----------------------------------------- */

.swaps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}

.swaps > li {
  padding-block: var(--s-xl);
  border-top: 1px solid var(--ink-700);
  display: grid;
  gap: var(--s-lg);
  grid-template-columns: 1fr;
  align-items: start;
}

.swaps > li:last-child {
  border-bottom: 1px solid var(--ink-700);
}

@media (min-width: 60rem) {
  .swaps > li {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--s-2xl);
  }

  .swaps__why {
    grid-column: 1 / -1;
  }
}

.quote {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.3rem);
  line-height: 1.32;
  letter-spacing: -0.015em;
  margin: 0;
}

.quote__tag {
  display: block;
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: var(--s-xs);
}

.quote--said .quote__tag {
  color: var(--alarm);
}

.quote--said q {
  color: var(--paper-3);
  quotes: "\201C" "\201D";
}

.quote--meant .quote__tag {
  color: var(--signal);
}

.quote--meant q {
  color: var(--paper);
  quotes: "\201C" "\201D";
}

.swaps__why {
  font-size: var(--t-small);
  color: var(--paper-2);
  line-height: 1.68;
  max-width: 76ch;
}

.swaps__why b {
  color: var(--paper);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-right: 0.55em;
}

/* ---------- 12. The star band -------------------------------------------

   The one moment of spectacle, and the only place the rainbow appears.
   -------------------------------------------------------------------------- */

.psa {
  position: relative;
  z-index: 3;
  padding-block: clamp(3.5rem, 2rem + 7vw, 7rem);
  background: oklch(0.115 0.035 279);
  border-block: 1px solid var(--ink-700);
  overflow: hidden;
  text-align: center;
}

.psa__stage {
  position: relative;
  width: 100%;
  max-width: 46rem;
  margin-inline: auto;
}

.psa__svg {
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Each rainbow band draws itself in, fastest band first, so the arc appears
   to be trailing the star rather than waiting for it. */
.arc {
  fill: none;
  stroke-linecap: round;
}

/* --len is replaced with the measured path length by psa.js. The 800 here is
   only a near-miss fallback for the instant before that runs. */
.js .arc {
  stroke-dasharray: var(--len, 800) var(--len, 800);
  stroke-dashoffset: var(--len, 800);
}

.js .is-live .arc {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.35s var(--ease) var(--arc-delay, 0s);
}

/* Innermost band first, so the arc appears to trail the star rather than wait
   for it. nth-of-type, not an inline style, for the CSP reason noted above. */
.arc:nth-of-type(1) {
  --arc-delay: 0s;
}
.arc:nth-of-type(2) {
  --arc-delay: 0.04s;
}
.arc:nth-of-type(3) {
  --arc-delay: 0.08s;
}
.arc:nth-of-type(4) {
  --arc-delay: 0.12s;
}
.arc:nth-of-type(5) {
  --arc-delay: 0.16s;
}
.arc:nth-of-type(6) {
  --arc-delay: 0.2s;
}

.star {
  offset-path: path(
    "M 40 268 C 190 268 300 236 404 176 C 508 116 592 46 706 32"
  );
  offset-distance: 100%;
  offset-rotate: 0deg;
}

.js .star {
  offset-distance: 0%;
  opacity: 0;
}

.js .is-live .star {
  offset-distance: 100%;
  opacity: 1;
  transition:
    offset-distance 1.4s var(--ease),
    opacity 0.25s linear;
}

.psa__wordmark {
  margin-top: var(--s-lg);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 0.9rem + 2.6vw, 3rem);
  letter-spacing: 0.02em;
  line-height: 1.05;
  color: var(--paper);
}

.js .psa__wordmark {
  opacity: 0;
  transform: translateY(0.75rem) scale(0.97);
}

.js .is-live .psa__wordmark {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.6s var(--ease-soft) 1.05s,
    transform 0.8s var(--ease) 1.05s;
}

.psa__line {
  margin: var(--s-lg) auto 0;
  max-width: 44ch;
  color: var(--paper-2);
  font-size: var(--t-lede);
  line-height: 1.55;
}

.js .psa__line {
  opacity: 0;
}

.js .is-live .psa__line {
  opacity: 1;
  transition: opacity 0.7s var(--ease-soft) 1.35s;
}

.psa__replay {
  margin-top: var(--s-xl);
}

/* ---------- 13. Sponsor slate -------------------------------------------

   The page stops joking here. Light panel, real logo, real firm, plain type.
   The tonal switch is the design: after six sections of broadcast play, the
   credential arrives sober.
   -------------------------------------------------------------------------- */

.sponsor {
  position: relative;
  z-index: 3;
  padding-block: var(--s-section);
  background: var(--ink-900);
}

.slate {
  background: var(--slate-bg);
  color: var(--slate-ink);
  border-radius: 4px;
  padding: clamp(2rem, 1rem + 4vw, 4rem);
  max-width: 56rem;
  margin-inline: auto;
  text-align: center;
}

.slate__eyebrow {
  font-family: var(--font-display);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--slate-ink-2);
  margin-bottom: var(--s-lg);
}

.slate__logo {
  width: min(19rem, 68%);
  height: auto;
  margin: 0 auto var(--s-lg);
}

.slate__desc {
  margin: 0 auto;
  max-width: 46ch;
  color: var(--slate-ink-2);
  font-size: var(--t-body);
  line-height: 1.68;
}

.slate__desc strong {
  color: var(--slate-ink);
  font-weight: 600;
}

.slate__actions {
  margin-top: var(--s-xl);
  padding-top: var(--s-xl);
  border-top: 1px solid var(--slate-rule);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
  justify-content: center;
}

.slate .btn--solid {
  background: var(--slate-ink);
  color: var(--slate-bg);
}

.slate .btn--solid:hover {
  background: oklch(0.42 0.13 158);
  color: var(--slate-bg);
}

.slate .btn--ghost {
  color: var(--slate-ink-2);
  border-color: var(--slate-rule);
}

.slate .btn--ghost:hover {
  color: var(--slate-ink);
  border-color: var(--slate-ink-2);
}

.slate .btn--ghost[data-copied="true"] {
  color: oklch(0.42 0.13 158);
  border-color: oklch(0.42 0.13 158);
}

.slate__fine {
  margin: var(--s-lg) auto 0;
  max-width: 52ch;
  font-size: var(--t-label);
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: var(--slate-ink-2);
  opacity: 0.85;
}

/* ---------- 14. Footer --------------------------------------------------- */

.site-foot {
  position: relative;
  z-index: 3;
  padding-block: var(--s-2xl) var(--s-3xl);
  border-top: 1px solid var(--ink-700);
  font-size: var(--t-small);
  color: var(--paper-3);
}

.foot-grid {
  display: grid;
  gap: var(--s-2xl);
  grid-template-columns: 1fr;
}

@media (min-width: 54rem) {
  .foot-grid {
    grid-template-columns: 1.2fr 1fr;
    gap: var(--s-4xl);
  }
}

.site-foot h2 {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper-3);
  margin-bottom: var(--s-md);
  line-height: 1.4;
}

.sources {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sources li {
  padding-block: var(--s-sm);
  border-top: 1px solid var(--ink-700);
  line-height: 1.55;
}

.sources li:last-child {
  border-bottom: 1px solid var(--ink-700);
}

.colophon p {
  color: var(--paper-3);
  line-height: 1.7;
  max-width: 48ch;
}

.foot-note {
  margin-top: var(--s-lg);
  padding-top: var(--s-md);
  border-top: 1px solid var(--ink-700);
  font-size: var(--t-label);
  line-height: 1.8;
  letter-spacing: 0.01em;
  color: var(--paper-3);
  max-width: 46ch;
}

.colophon p + p {
  margin-top: var(--s-md);
}

.foot-legal {
  margin-top: var(--s-2xl);
  padding-top: var(--s-lg);
  border-top: 1px solid var(--ink-700);
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  line-height: 1.8;
  color: var(--paper-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-xs) var(--s-lg);
  justify-content: space-between;
}

/* ---------- 15. Reduced motion ------------------------------------------

   Everything animated above resolves to its finished state. The star band is
   the important one: it must arrive assembled, not blank with a replay button
   that appears to do nothing.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }

  /* The hero is keyframe-driven now, so the blanket animation-duration
     override above already lands it on its finished state: .beat holds
     opacity 1 and both cross-out bars hold scaleX(1), instantly. These two
     rules make that explicit rather than relying on the override, because
     `.beat { opacity: 0 }` is the base state and a future change to the
     blanket rule would otherwise leave the whole hero invisible for anyone
     who asked for less motion. */
  .beat {
    opacity: 1;
    transform: none;
  }

  .xmark::before,
  .xmark::after {
    transform: rotate(var(--x-rot, 0deg)) scaleX(1);
  }

  .js .arc {
    stroke-dashoffset: 0;
  }

  .js .star {
    offset-distance: 100%;
    opacity: 1;
  }

  .js .psa__wordmark,
  .js .psa__line {
    opacity: 1;
    transform: none;
  }

  .ticker__track {
    animation: none;
    transform: translate3d(-6%, 0, 0);
  }

  .tally {
    animation: none;
  }
}
