@font-face {
  font-family: 'Saira';
  src: url('/assets/fonts/Saira.ttf') format('truetype-variations');
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('/assets/fonts/Geist.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  --bg: #1b1f27;
  --bg2: #14171d;
  --ink: #f7f9fc;
  --muted: #8b93a8;
  --dim: #565d70;
  /* --dim is a decoration tone: it only reaches 2.5:1 on --bg, so it can carry a
     rule or a border but never text. --dim-ink is the same family lightened to
     4.9:1, quieter than --muted, still readable, for copy that must be read. */
  --dim-ink: #848c9d;
  --dot: #2a3040;
  --magenta: #d81b4e;
  --cyan: #22b8cf;
  --grey: #8d99aa;
  --card: #20242e;
  --border: #2b303c;
  /* --border is a surface divider: 1.4:1 against --bg2 and 1.25:1 against --bg. That
     is fine for separating two panels but not for a control a person has to find:
     SC 1.4.11 wants 3:1, and the --bg2 fill can't supply it either (1.09:1 on --bg).
     --field-border is the same family lifted to 3.7:1 on the fill, 3.4:1 on the page. */
  --field-border: #697285;
  --display: 'Saira', system-ui, sans-serif;
  --sans: 'Geist', system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  min-height: 100%;
}

body {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  overflow-x: hidden;
}

/* ── background dot field ─────────────────────────────────────────────── */
.dotfield {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 22px 22px;
  transition: background-position .4s cubic-bezier(.2,.8,.25,1);
  pointer-events: none;
}
.dotfield::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 70% 55% at 50% 20%, rgba(197,26,74,.08), transparent 60%),
              radial-gradient(ellipse 60% 50% at 80% 70%, rgba(47,182,196,.07), transparent 60%);
}

main {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 90px 24px 60px;
}

/* ── hero ──────────────────────────────────────────────────────────────── */
.hero {
  text-align: center;
  max-width: 560px;
}

.mark-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 148px;
  height: 148px;
  border-radius: 28px;
  background: var(--bg2);
  border: 1px solid var(--border);
  margin-bottom: 28px;
  /* No cursor here on purpose: the mark only becomes clickable once animate.js wires
     the replay up, and animate.js sets the pointer cursor itself at that moment. With
     JS off, or under reduced motion, it never does, so neither should this. */
  filter: drop-shadow(0 14px 40px rgba(0,0,0,.5));
}

.mark { width: 96px; height: 96px; overflow: visible; }
.mark path { fill: none; stroke-linecap: square; stroke-linejoin: miter; }

/* Layers draw in bottom-up-through-top order, echoing what the mark itself
   depicts: a stack building up, with the code symbol landing last. */
.mark .layer-hex {
  stroke: var(--magenta);
  stroke-width: 15;
  stroke-dasharray: 650;
  stroke-dashoffset: 650;
  animation: draw .9s cubic-bezier(.2,.8,.25,1) forwards;
}
.mark .layer-cyan {
  stroke: var(--cyan);
  stroke-width: 15;
  stroke-dasharray: 280;
  stroke-dashoffset: 280;
  animation: draw .5s cubic-bezier(.2,.8,.25,1) forwards;
  animation-delay: .8s;
}
.mark .layer-grey {
  stroke: var(--grey);
  stroke-width: 15;
  stroke-dasharray: 280;
  stroke-dashoffset: 280;
  animation: draw .5s cubic-bezier(.2,.8,.25,1) forwards;
  animation-delay: 1.05s;
}
.mark .code-symbol {
  stroke: var(--ink);
  stroke-width: 14;
  stroke-dasharray: 300;
  stroke-dashoffset: 300;
  animation: draw .6s cubic-bezier(.2,.8,.25,1) forwards, glow 3s ease-in-out infinite;
  animation-delay: 1.4s, 2.4s;
}

@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes glow {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(216,27,78,0)); }
  50% { filter: drop-shadow(0 0 5px rgba(216,27,78,.6)); }
}

h1 {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(38px, 7vw, 56px);
  letter-spacing: -0.02em;
  margin: 0 0 8px;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

.wordmark { margin: 0 0 12px; }
.wordmark svg { width: min(100%, 300px); height: auto; display: block; margin: 0 auto; }
.tagline {
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  color: var(--cyan);
  margin: 0 0 18px;
}
.lede {
  font-size: 15px;
  line-height: 1.7;
  color: var(--muted);
  margin: 0;
}

/* ── platforms ─────────────────────────────────────────────────────────── */
.platforms {
  text-align: center;
  max-width: 560px;
  margin-top: 56px;
}
.platforms .eyebrow-center {
  font-family: var(--mono, var(--sans));
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--cyan);
  margin: 0 0 8px;
}
.platforms .platforms-lede {
  font-size: 14px;
  color: var(--muted);
  margin: 0 0 18px;
}
.platform-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 99px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.chip svg { width: 15px; height: 15px; color: var(--muted); flex-shrink: 0; }

/* ── principle cards ──────────────────────────────────────────────────── */
.principles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  max-width: 800px;
  width: 100%;
  margin-top: 64px;
}
@media (max-width: 720px) {
  .principles { grid-template-columns: 1fr; }
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 22px 20px;
  transition: transform .18s cubic-bezier(.2,.8,.3,1), border-color .18s, box-shadow .18s;
}
.card:hover {
  transform: translateY(-3px);
  border-color: var(--dim);
  box-shadow: 0 14px 34px rgba(0,0,0,.35);
}

.card-icon {
  width: 34px; height: 34px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 14px;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--border);
}
.card-icon svg { width: 17px; height: 17px; }
.card-icon[data-glow="magenta"] { color: var(--magenta); }
.card-icon[data-glow="cyan"] { color: var(--cyan); }
.card-icon[data-glow="grey"] { color: var(--grey); }
.card:hover .card-icon[data-glow="magenta"] { box-shadow: 0 0 16px rgba(216,27,78,.35); border-color: var(--magenta); }
.card:hover .card-icon[data-glow="cyan"] { box-shadow: 0 0 16px rgba(34,184,207,.35); border-color: var(--cyan); }
.card:hover .card-icon[data-glow="grey"] { box-shadow: 0 0 16px rgba(141,153,170,.35); border-color: var(--grey); }

.card h2,
.card h3 {
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  margin: 0 0 6px;
}
.card p {
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0;
}

/* ── site nav ──────────────────────────────────────────────────────────── */
.sitenav {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 30px;
  padding: 26px 24px 0;
  flex-wrap: wrap;
}
.sitenav .brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--display);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: -0.01em;
  margin-right: 6px;
}
.sitenav .brand img { width: 24px; height: 24px; display: block; }
.sitenav a:not(.brand) {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  transition: color .15s;
}
.sitenav a:not(.brand):hover,
.sitenav a[aria-current="page"] { color: var(--cyan); }

/* ── inner pages (work/about/faq/contact/privacy/404) ─────────────────── */
.page {
  position: relative;
  z-index: 1;
  flex: 1;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 56px 24px 60px;
}
.page.centered { text-align: center; }
.page.centered .mark-wrap { margin: 0 auto 28px; }
.page.centered p { max-width: 460px; margin-left: auto; margin-right: auto; }
.page.centered .cta { margin-top: 4px; }
.page h1 {
  font-size: clamp(30px, 6vw, 42px);
  margin: 0 0 22px;
}
.page p {
  font-size: 15px;
  line-height: 1.75;
  color: var(--muted);
  margin: 0 0 18px;
}
.page p:last-child { margin-bottom: 0; }
.page p a { color: var(--cyan); text-decoration: none; }
.page p a:hover { text-decoration: underline; }
.page h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: 19px;
  color: var(--ink);
  margin: 34px 0 10px;
}
.page .eyebrow {
  font-family: var(--mono, var(--sans));
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--cyan);
  margin: 0 0 10px;
}
.page .cta {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 8px;
  padding: 13px 24px;
  /* Not var(--cyan): white on #22b8cf is 2.4:1, so the label and arrow fell below AA
     across the right-hand half of every button. #177f90 holds 4.7:1 under white and
     still 3.5:1 against --bg, so the shape stays visible too. The --cyan token itself
     is untouched: it is cyan-on-dark elsewhere, where it passes comfortably. */
  background: linear-gradient(100deg, var(--magenta), #177f90);
  color: #fff;
  font-family: var(--display);
  font-weight: 700;
  font-size: 14px;
  border-radius: 8px;
  text-decoration: none;
  transition: transform .15s, box-shadow .15s;
}
.page .cta:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(216,27,78,.3); }
.cta svg { width: 16px; height: 16px; flex-shrink: 0; }

/* ── footer ────────────────────────────────────────────────────────────── */
footer {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 22px 24px 28px;
  font-size: 11.5px;
  /* --dim is a decoration tone and would put this line at 2.5:1. The copyright is
     text, so it takes the readable member of the family instead. */
  color: var(--dim-ink);
  font-family: var(--sans);
  letter-spacing: .02em;
}
footer .footer-links { display: flex; gap: 18px; justify-content: center; margin-bottom: 10px; flex-wrap: wrap; }
footer .footer-links a { color: var(--muted); text-decoration: none; font-size: 12px; }
footer .footer-links a:hover { color: var(--cyan); }

/* ── work list ─────────────────────────────────────────────────────────── */
.work-list {
  display: grid;
  gap: 16px;
  margin: 26px 0;
  /* The homepage nests this inside a centred section; entries read as records,
     so they keep their own left alignment regardless of the parent. */
  text-align: left;
}

.work-entry {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 22px 20px;
  transition: transform .18s cubic-bezier(.2,.8,.3,1), border-color .18s, box-shadow .18s;
}
.work-entry:hover {
  transform: translateY(-3px);
  border-color: var(--dim);
  box-shadow: 0 14px 34px rgba(0,0,0,.35);
}

.work-entry-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px 12px;
  margin-bottom: 10px;
}
.work-entry-head h2,
.work-entry-head h3 {
  font-family: var(--display);
  font-weight: 700;
  font-size: 17px;
  color: var(--ink);
  margin: 0;
}
.work-entry p {
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted);
  margin: 0 0 14px;
}
.work-entry p:last-child { margin-bottom: 0; }

.work-list .work-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 4px 12px 4px 10px;
  background: rgba(34,184,207,.08);
  border: 1px solid var(--border);
  border-radius: 99px;
  font-family: var(--display);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: .04em;
  color: var(--cyan);
}
.work-list .work-status::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 0 3px rgba(34,184,207,.16);
  flex-shrink: 0;
}

/* ── case study ────────────────────────────────────────────────────────── */
.case { counter-reset: case-stage; }

.case-head { margin-bottom: 6px; }
.case-head .lede {
  font-size: 17px;
  line-height: 1.7;
  color: var(--muted);
  margin: 0 0 20px;
}
.case-head .platform-chips { justify-content: flex-start; }

.case-tag {
  padding: 7px 13px;
  font-size: 12px;
}
.case-tag svg { color: var(--cyan); }

.case-stage {
  margin-top: 36px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.case-stage h2 {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 12px;
}
/* Numbered so the problem → built → platform → now → ongoing run reads as one
   sequence rather than five unrelated headings. */
.case-stage h2::before {
  counter-increment: case-stage;
  content: counter(case-stage);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
  color: var(--cyan);
}

.case-meta {
  display: grid;
  grid-template-columns: minmax(110px, 150px) minmax(0, 1fr);
  column-gap: 20px;
  row-gap: 12px;
  margin: 0 0 20px;
  padding: 18px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.case-meta dt {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cyan);
  padding-top: 2px;
}
.case-meta dd {
  margin: 0;
  font-size: 14px;
  line-height: 1.7;
  color: var(--muted);
  /* Version strings and hyphenated crypto names are longer than a 320px column. */
  overflow-wrap: break-word;
}
@media (max-width: 720px) {
  .case-meta { grid-template-columns: minmax(0, 1fr); row-gap: 4px; }
  .case-meta dt { padding-top: 0; }
  .case-meta dd { margin-bottom: 14px; }
  .case-meta dd:last-child { margin-bottom: 0; }
}

.case-disclaimer {
  margin: 28px 0;
  padding: 14px 16px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--dim);
  border-radius: 8px;
}
.case-disclaimer p {
  font-size: 13px;
  line-height: 1.7;
  color: var(--dim-ink);
  margin: 0;
}

/* ── faq ───────────────────────────────────────────────────────────────── */
.faq {
  border-top: 1px solid var(--border);
  margin: 0 0 30px;
}
.faq-item { border-bottom: 1px solid var(--border); }

.faq-item summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 2px;
  min-height: 44px;
  font-family: var(--display);
  font-weight: 600;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { color: var(--cyan); }

.faq-item summary::after {
  content: '';
  width: 8px;
  height: 8px;
  margin: -4px 4px 0 auto;
  flex-shrink: 0;
  border-right: 2px solid var(--cyan);
  border-bottom: 2px solid var(--cyan);
  transform: rotate(45deg);
  transition: transform .2s cubic-bezier(.2,.8,.25,1);
}
.faq-item[open] summary::after {
  margin-top: 2px;
  transform: rotate(-135deg);
}

.faq-item p {
  font-size: 14.5px;
  line-height: 1.75;
  color: var(--muted);
  margin: 0 0 16px;
  padding-right: 22px;
}

/* ── contact expectations ──────────────────────────────────────────────── */
.expect {
  list-style: none;
  display: grid;
  gap: 12px;
  margin: 0 0 28px;
  padding: 0;
}
/* A fixed icon column, so a wrapped second line stays clear of the icon. */
.expect-item {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}
.expect-item svg {
  width: 17px;
  height: 17px;
  margin-top: 3px;
  color: var(--cyan);
  flex-shrink: 0;
}

/* ── contact form ──────────────────────────────────────────────────────── */

/* Only ever rendered with scripting off, and only when the human check is switched on,
   so it has to carry itself with none of the interactive affordances the rest of the
   form has. Sized and coloured like body copy rather than like a form note, because it
   is the difference between a message getting through and being refused, not a footnote.
   Same treatment as the error state of .form-status, which is the other place on this
   page that has to say something went wrong. */
.page .noscript-note {
  margin: 26px 0 0;
  padding: 12px 14px;
  background: rgba(216,27,78,.10);
  border: 1px solid var(--border);
  border-left: 3px solid var(--magenta);
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
}

.form {
  display: grid;
  gap: 16px;
  margin: 28px 0 0;
}
/* .form sets display, which would otherwise beat the browser's [hidden] rule
   when contact.js swaps the form out for the thank-you line. */
.form[hidden] { display: none; }

.field { display: grid; gap: 7px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) {
  .field-row { grid-template-columns: 1fr; }
}

.form label {
  font-family: var(--display);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}

.field input,
.field textarea {
  width: 100%;
  padding: 12px 13px;
  background: var(--bg2);
  border: 1px solid var(--field-border);
  border-radius: 8px;
  color: var(--ink);
  font-family: var(--sans);
  /* 16px: anything smaller makes iOS Safari zoom the page on focus. */
  font-size: 16px;
  line-height: 1.5;
  transition: border-color .15s, box-shadow .15s;
}
.field textarea {
  resize: vertical;
  min-height: 150px;
}
.field input::placeholder,
.field textarea::placeholder { color: var(--muted); }

/* Lighter than the resting --field-border, never darker. --dim would read as the
   field fading out under the cursor rather than responding to it. */
.field input:hover,
.field textarea:hover { border-color: var(--muted); }
.field input:focus-visible,
.field textarea:focus-visible {
  border-color: var(--cyan);
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}
/* contact.js marks server-rejected fields this way. */
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--magenta);
  box-shadow: 0 0 0 3px rgba(216,27,78,.14);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  justify-self: start;
  min-height: 48px;
  padding: 13px 24px;
  /* Same darkened cyan stop as .page .cta, for the same contrast reason. */
  background: linear-gradient(100deg, var(--magenta), #177f90);
  color: #fff;
  font-family: var(--display);
  font-weight: 700;
  font-size: 14px;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: transform .15s, box-shadow .15s;
}
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(216,27,78,.3); }
/* Two selectors, one look. :disabled is still here for any button that legitimately has
   nothing to do; [aria-disabled] is what the Send button uses while the human check is
   outstanding, because a natively disabled button drops out of the focus order and takes
   the explanation with it. Both must be listed after :hover, not before, since they share
   its specificity and only source order decides which one paints. */
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--card);
  border-color: var(--border);
  color: var(--muted);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.page .form-note {
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--dim-ink);
  margin: 4px 0 0;
}

/* Turnstile renders a fixed 300px iframe, and .page leaves a 272px content box at a
   320px viewport. Left alone, that 300px became the min-content floor of a grid item,
   which raised the whole form's min-content, and because main.page centres its children
   the form is sized fit-content: every input, the textarea and the Send button grew to
   300px and hung 14px past the gutter on each side, out of line with the heading above
   them. body has overflow-x: hidden, so nothing looked broken, it was just wrong.
   overflow-x: auto is doing the real work here rather than max-width: a scrollable box
   has a min-content width of zero, so the iframe stops being able to push its ancestors
   about, and the widget scrolls inside its own box instead. */
#turnstileField { min-width: 0; }
#turnstileWidget {
  max-width: 100%;
  overflow-x: auto;
}

/* Below that, let the widget borrow the page gutter instead of scrolling, so the whole
   checkbox stays visible. Negative margins on the grid item rather than a narrower .page
   padding, because the gutter is right for every other page and every other element and
   should not be traded away for one widget. 272 + 14 + 14 = 300, exactly what it needs. */
@media (max-width: 359px) {
  #turnstileField { margin-left: -14px; margin-right: -14px; }
}

/* Space is held from the start: the status line appears mid-submit, and a box
   that grows from nothing would shove the button out from under the cursor. */
.page .form-status {
  min-height: 50px;
  margin: 10px 0 0;
  padding: 12px 14px;
  background: transparent;
  border: 1px solid transparent;
  border-left-width: 3px;
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  transition: background-color .2s, border-color .2s;
}
.page .form-status:empty { padding: 0; }
.page .form-status[data-state="ok"] {
  background: rgba(34,184,207,.10);
  border-color: var(--border);
  border-left-color: var(--cyan);
}
.page .form-status[data-state="error"] {
  background: rgba(216,27,78,.10);
  border-color: var(--border);
  border-left-color: var(--magenta);
}

/* The honeypot. It has to be unreachable for people and for screen readers,
   yet still present and fillable in the DOM, and a bot that fills it identifies
   itself. display:none and visibility:hidden are the obvious way to hide it
   and exactly the two things the bots worth catching already check for, so it
   is parked off-screen and clipped instead. This is deliberate, not a mistake. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── focus states ──────────────────────────────────────────────────────── */
a:focus-visible,
summary:focus-visible,
button:focus-visible,
.mark-wrap:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .mark .layer-hex, .mark .layer-cyan, .mark .layer-grey, .mark .code-symbol {
    animation: none !important; stroke-dashoffset: 0;
  }
  /* Killing the transition is only half of it: a :hover transform still jumps the
     element, it just does so instantly. Every hover lift in the file is neutralised
     alongside its transition. */
  .card { transition: none; }
  .card:hover { transform: none; }
  .dotfield { transition: none; }
  .work-entry { transition: none; }
  .work-entry:hover { transform: none; }
  .sitenav a:not(.brand) { transition: none; }
  .faq-item summary::after { transition: none; }
  .field input, .field textarea { transition: none; }
  .btn { transition: none; }
  .btn:hover { transform: none; }
  .page .cta { transition: none; }
  .page .cta:hover { transform: none; }
  .page .form-status { transition: none; }
}
