/* Wyrd portal.
   Palette and type come from repos/plans/brand: dark system, grey instead of
   white, blue for interface, green for protected state, red only for alarm.

   The one loud element is the token artifact: what a customer buys here is a
   string they paste into their own server, so that string is treated as the
   object it is rather than as a form field. Everything around it stays quiet. */

@import url("/static/fonts.css");

:root {
  --void: #080D14;
  --carbon: #0E1826;
  --graphite: #132234;
  --hairline: #22384F;

  --deep-blue: #10263F;
  --blue: #4A9FDA;
  --blue-dk: #2E6FA8;
  --mark-net: #3A7EBB;
  --mark-node: #A5D6F5;

  --deep-green: #0F2B1C;
  --green: #86B86A;

  --alert: #D64152;
  --alert-dk: #A93544;
  --alert-lt: #EE7A86;

  --fog: #D4DADE;
  --ash: #93A0AC;
  --steel: #5F6E7C;
  --iron: #33424E;

  --font-display: "Oswald", sans-serif;
  --font-text: "Manrope", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;

  --gap: 1.25rem;
  --rail: 2px;
  /* The left edge of a block. Shared, so an artifact and the notice under it
     line up instead of stepping. */
  --edge: 3px;
}

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

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

body {
  margin: 0;
  background: var(--void);
  color: var(--fog);
  font-family: var(--font-text);
  font-size: 16px;
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--mark-node);
  outline-offset: 2px;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.1;
  margin: 0 0 0.6rem;
}

h1 { font-size: clamp(1.8rem, 4vw, 2.6rem); }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.05rem; }

p { margin: 0 0 1rem; }

/* ---- shell ---------------------------------------------------------- */

.top {
  border-bottom: 1px solid var(--hairline);
  background: linear-gradient(180deg, var(--carbon), var(--void));
  /* Above the background mark, which is fixed and would otherwise paint over
     the bar. */
  position: relative;
  z-index: 1;
}

.top-in, .page, .foot-in {
  width: min(1120px, 100% - 2.5rem);
  margin-inline: auto;
}

.top-in {
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: 0.9rem 0;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  color: var(--fog);
  font-family: var(--font-display);
  font-size: 1.3rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.brand:hover { text-decoration: none; color: var(--mark-node); }

.nav {
  display: flex;
  gap: 0;
  align-items: center;
  margin-left: auto;
  flex-wrap: wrap;
}

.nav a, .nav button {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.6;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ash);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.nav a:hover, .nav button:hover { color: var(--fog); text-decoration: none; }
.nav button { font-family: var(--font-mono); text-decoration: none; }
/* A button is an inline-block, so on a text baseline it drags a descender's
   worth of space with it and sits lower than the links beside it. Laying the
   wrapping form out as a flex row takes the baseline out of the picture. */
.nav > form { display: flex; align-items: center; }

/* Several items are two or three words long, so a gap alone never separates
   them: the space inside an item competes with the space between items. A
   hairline says where one ends. */
.nav > * + *::before {
  content: "";
  display: inline-block;
  width: 1px;
  height: 0.9em;
  margin: 0 1rem;
  background: var(--hairline);
  vertical-align: -0.08em;
}
/* The language switch has a border of its own and needs no rule beside it. */
.nav > .lang::before { display: none; }
.nav > .lang { margin-left: 1.2rem; }

.nav a.on { color: var(--mark-node); }

/* Waiting work, counted in the navigation. It carries a colour the rest of the
   bar does not use, because the point is to be seen from a page about
   something else. */
.badge {
  display: inline-block;
  min-width: 1.1em;
  padding: 0 0.3em;
  border-radius: 2px;
  background: var(--blue-dk);
  color: var(--void);
  text-align: center;
}

.lang {
  display: inline-flex;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  overflow: hidden;
}
.lang a {
  padding: 0.15rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--steel);
}
.lang a.on { background: var(--deep-blue); color: var(--mark-node); }

/* Wrapped onto several lines a rule would land at the start of a line and
   read as a stray mark, so each item becomes a chip instead - the same
   treatment the filter bar already uses, so the two read as one system. */
@media (max-width: 720px) {
  .nav { gap: 0.5rem; }
  .nav > * + *::before { display: none; }
  .nav > .lang { margin-left: 0; }
  .nav a, .nav button {
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 0.25rem 0.6rem;
  }
  .nav a.on { border-color: var(--blue-dk); background: var(--deep-blue); }
  .nav .lang a { border: 0; border-radius: 0; padding: 0.15rem 0.5rem; }
}

.page { padding: 2.5rem 0 4rem; flex: 1; }

.foot {
  border-top: 1px solid var(--hairline);
  color: var(--steel);
  font-size: 0.82rem;
  padding: 1.4rem 0;
  position: relative;
  z-index: 1;
  background: var(--void);
}
.foot-in { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: space-between; }

/* ---- generic pieces -------------------------------------------------- */

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--mark-net);
  margin: 0 0 0.7rem;
}

.lede { font-size: 1.08rem; color: var(--ash); max-width: 56ch; }

.card > h2:first-child, .card > h3:first-child { margin-top: 0; }
/* A caption introduces the block below it, so it needs air above. */
.card > * + .label { margin-top: 1.1rem; }
.card {
  background: var(--graphite);
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 1.3rem;
}

.danger-card { border-color: var(--alert); }

/* A destructive action asks for a gesture that a hand cannot make by
   accident: the slider has to be carried all the way across. */
dialog.ask {
  background: var(--graphite);
  color: var(--fog);
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 1.4rem;
  max-width: 27rem;
  width: calc(100% - 2rem);
}
dialog.ask::backdrop { background: rgba(8, 13, 20, 0.8); }
dialog.ask h3 { margin-top: 0; }
dialog.ask .row { margin-top: 1rem; }

.slider {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 2.6rem;
  padding: 0;
  background: transparent;
  border: 0;
  margin-top: 1rem;
}
.slider::-webkit-slider-runnable-track {
  height: 2.6rem;
  border-radius: 3px;
  border: 1px solid var(--hairline);
  background: linear-gradient(to right,
    var(--alert-dk) var(--fill, 0%), var(--carbon) var(--fill, 0%));
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 3.6rem;
  height: 2.4rem;
  margin-top: 1px;
  border-radius: 2px;
  background: var(--alert);
  cursor: grab;
}
.slider::-moz-range-track {
  height: 2.6rem;
  border-radius: 3px;
  background: var(--carbon);
  border: 1px solid var(--hairline);
}
.slider::-moz-range-progress {
  height: 2.6rem;
  border-radius: 3px 0 0 3px;
  background: var(--alert-dk);
}
.slider::-moz-range-thumb {
  width: 3.6rem;
  height: 2.4rem;
  border: 0;
  border-radius: 2px;
  background: var(--alert);
  cursor: grab;
}

/* One offer card, editable in place. Fields and actions wrap rather than
   forcing the panel sideways. */
.offer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding: 0.7rem 0.8rem;
}
.offer form { display: contents; }
.offer .row { gap: 0.5rem; }
.offer input[type=number] { text-align: right; }

/* One field with its caption above it. The buttons line up with the inputs
   rather than with the captions. */
.cell { display: flex; flex-direction: column; gap: 0.2rem; }
.cell-cap {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel);
}
.offer, .row.fields { align-items: flex-end; }
/* The fields keep to the left and the actions sit at the far edge, so a row
   reads as "what the card says" and then "what to do with it". */
.offer .pushed { margin-left: auto; }

.grid { display: grid; gap: var(--gap); }
.grid.three { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
/* Offer cards are sized to their content: one card must not stretch to the
   full width just because it is alone. */
.grid.cards { grid-template-columns: repeat(auto-fill, minmax(250px, 330px)); }
.grid.two { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

/* Vertical rhythm lives here rather than in the templates, so a page carries
   structure and not a scattering of one-off margins. */
.page > * + * { margin-top: 2.6rem; }
section { margin-top: 2.2rem; }
section:first-child { margin-top: 0; }
.stack > * + * { margin-top: var(--gap); }
.card + .card { margin-top: 1.2rem; }
/* Containers that already space their children with gap must not get the
   stacked-card margin on top of it, or the second card sits lower than the
   first in the same row. */
.grid > .card + .card, .side > .card + .card, .stack > .card + .card { margin-top: 0; }
.lede + .cta, .lede + .row, .lede + .artifact { margin-top: 1.4rem; }
h2 + .muted, h2 + p, h3 + p { margin-top: 0; }
h1 + .artifact, h1 + .card, h1 + .row,
h2 + .grid, h2 + .list, h2 + .scroll, h2 + .empty, h2 + .card,
.muted + .grid, .muted + .list, .muted + .scroll, .muted + .row,
p + .grid, p + .list, p + .scroll { margin-top: 1rem; }
.artifact + .card, .notice + .card, .banner + .split, .chat + .card { margin-top: 1.4rem; }
.card + .row, .list + .row, .empty + .row { margin-top: 1.4rem; }
.list + .card, .scroll + .card, .list + form.card { margin-top: 1.8rem; }
/* A block that follows a bar of controls needs its own air, or it reads as
   part of the bar. */
.row + .card, .split + .card, .card > .row + form, .card > .row + .row { margin-top: 1.2rem; }
/* Inside a form nothing touches: a control is never flush against the label
   above it, and an action is never flush against the last field. */
form.card > * + * { margin-top: 1.2rem; }
form.card > .field + .field { margin-top: 1rem; }
form.card > input[type=hidden] + * { margin-top: 0; }
form.card > .label + *, form.card > .help { margin-top: 0.35rem; }
form.card > .row:last-child, form.card > button:last-child, form.card > .btn:last-child { margin-top: 1.2rem; }

/* Column widths, named for what they hold. */
.col-narrow { max-width: 26rem; }
.col-form { max-width: 34rem; }
.col-text { max-width: 44rem; }
.col-wide { max-width: 52rem; }
.col-page { max-width: 60rem; }
.measure { max-width: 56ch; }
.centered { margin-inline: auto; }

.muted { color: var(--ash); }
.dim { color: var(--steel); }
.mono { font-family: var(--font-mono); }

.label {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ash);
  display: block;
  margin-bottom: 0.35rem;
}
.field .label { color: var(--fog); font-size: 0.78rem; }

.tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  color: var(--ash);
}
.tag.ok { border-color: var(--green); color: var(--green); background: var(--deep-green); }
.tag.wait { border-color: var(--blue-dk); color: var(--blue); background: var(--deep-blue); }
.tag.bad { border-color: var(--alert); color: var(--alert-lt); }
.tag.off { color: var(--steel); }

/* ---- controls -------------------------------------------------------- */

.btn {
  display: inline-block;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.62rem 1.15rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  background: transparent;
  color: var(--fog);
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
}
.btn:hover { border-color: var(--blue); color: var(--mark-node); text-decoration: none; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn.primary {
  background: var(--deep-blue);
  border-color: var(--blue-dk);
  color: var(--mark-node);
}
.btn.primary:hover { background: var(--blue-dk); color: var(--void); }

.btn.danger { border-color: var(--alert); color: var(--alert-lt); }
.btn.danger:hover { background: var(--alert); color: var(--void); border-color: var(--alert); }

.btn.small { padding: 0.35rem 0.7rem; font-size: 0.72rem; }

input[type=text], input[type=password], input[type=number], input[type=file],
input[type=search], select, textarea {
  width: 100%;
  background: var(--carbon);
  border: 1px solid var(--hairline);
  border-radius: 2px;
  color: var(--fog);
  font-family: var(--font-text);
  font-size: 0.95rem;
  padding: 0.6rem 0.7rem;
}
input:focus, select:focus, textarea:focus { border-color: var(--blue); outline: none; }
textarea { resize: vertical; min-height: 6.5rem; }
input.mono, textarea.mono { font-family: var(--font-mono); }

/* The native file button ships light-on-grey, which is jarring against the
   rest of the page and on a dark screen at night. */
input[type=number] { appearance: textfield; -moz-appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { appearance: none; -webkit-appearance: none; margin: 0; }

input[type=file] { padding: 0.35rem; cursor: pointer; }
input[type=file]::file-selector-button {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fog);
  background: var(--deep-blue);
  border: 1px solid var(--blue-dk);
  border-radius: 2px;
  padding: 0.35rem 0.8rem;
  margin-right: 0.7rem;
  cursor: pointer;
}
input[type=file]::file-selector-button:hover { background: var(--blue-dk); color: var(--void); }

.field + .field { margin-top: 1rem; }
.field .help { font-size: 0.8rem; color: var(--steel); margin: 0.35rem 0 0; }

.row { display: flex; gap: 0.7rem; flex-wrap: wrap; align-items: center; }
.row.tight { gap: 0.4rem; }
/* Buttons offering the same kind of thing share the width when they fit and
   stack when they do not, rather than ending up different sizes. */
.row.even > .btn { flex: 1 1 13rem; text-align: center; }
/* A button must keep its own width: shrunk inside a narrow cell its label
   spills out and lands on the control beside it. */
.row > .btn, .row > form, .actions > .btn, .actions > form { flex-shrink: 0; }
.row.top { align-items: flex-start; }
.row .grow { flex: 1; min-width: 14rem; }
.row form, .actions form { display: inline; }

.rule { border: 0; border-top: 1px solid var(--hairline); margin: 1rem 0; }

.total {
  font-family: var(--font-display);
  font-size: 1.6rem;
  color: var(--mark-node);
}

/* The identifier that belongs to the heading above it. */
.subid {
  font-family: var(--font-mono);
  color: var(--steel);
  margin: 0.4rem 0 0;
  overflow-wrap: anywhere;
}
h1 + .subid, h2 + .subid, h3 + .subid { margin-top: -0.4rem; }
.split + .subid, .row + .subid { margin-top: 0.9rem; }

.notice, .error {
  border-left: var(--edge) solid var(--blue);
  background: var(--deep-blue);
  padding: 0.8rem 1rem;
  margin-bottom: var(--gap);
  font-size: 0.92rem;
}
form.card > .notice, form.card > .error { margin-bottom: 0; }
.error { border-left-color: var(--alert); background: rgba(214, 65, 82, 0.09); color: var(--alert-lt); }

/* ---- the signature: a token as an object ----------------------------- */

/* The thread down the left edge is the brand mark doing work: the web that
   holds the nodes. Three nodes, one per dot-separated field of the token. */
.artifact {
  position: relative;
  background: var(--carbon);
  border: 1px solid var(--hairline);
  /* The left side is the thread the nodes hang on, so it is the brand's own
     colour and the width the notices share, and the block is closed on all
     four sides like everything else on the page. */
  border-left: var(--edge) solid var(--mark-net);
  border-radius: 3px;
  padding: 1.1rem 1.2rem 1.1rem 2.2rem;
  overflow: hidden;
}
.artifact::before { content: none; }
/* With one node there is nothing for a thread to connect: the solid edge and
   the single dot carry the block. */
.artifact.single .token { position: relative; }
.artifact.single .token::before {
  content: "";
  position: absolute;
  left: calc(-1.35rem - 3px);
  top: 0.85em;
  transform: translateY(-50%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--mark-node);
  box-shadow: 0 0 0 3px var(--carbon);
}

.token {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  line-height: 1.75;
  word-break: break-all;
  color: var(--fog);
  margin: 0;
  white-space: pre-wrap;
}
.token .head { color: var(--mark-node); font-weight: 600; }
.token .body { color: var(--fog); }

.artifact-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.8rem;
  margin-bottom: 0.7rem;
  flex-wrap: wrap;
}

/* A glyph inside a button sits on the text baseline rather than beside it,
   which reads as a dropped letter. Aligning the two centres fixes it and
   keeps the button the same height as one without a glyph. */
.btn .glyph {
  vertical-align: -0.15em;
  margin-right: 0.4em;
}

/* An identifier the reader has to carry somewhere else: bigger than the rest
   of the page and holding the eye, because everything around it is quiet. */
.copyline .token { font-size: 1.05rem; letter-spacing: 0.02em; }

/* The search box is mostly fed identifiers pasted out of a conversation, so
   it is set in the same face they are printed in, and the glyph inside says
   what the field is without a caption above it taking a line. */
.search-field {
  position: relative;
  display: block;
  flex: 1 1 20rem;
  max-width: 28rem;
}
.search-field .glyph {
  position: absolute;
  left: 0.65rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--steel);
  pointer-events: none;
  transition: color 120ms ease;
}
.search-field:focus-within .glyph { color: var(--blue); }

.search-field input[type=search] {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  /* Matches the height of the small buttons beside it. */
  padding: 0.45rem 0.7rem 0.45rem 2.1rem;
}
.search-field input[type=search]::placeholder {
  color: var(--steel);
  font-family: var(--font-text);
  font-size: 0.88rem;
}
/* The browser's own clear cross is grey-on-grey here and sits at a different
   size in every engine. */
.search-field input[type=search]::-webkit-search-cancel-button { display: none; }

/* ---- the way in ------------------------------------------------------- */

/* The mark is background decor: far larger than the window, pushed off to the
   side the form is not on, and running past the edge so it reads as a wall
   behind the page rather than a picture on it. It is the one thing on these
   pages that reacts, so it also carries the state of an attempt. */
.entry { position: relative; }
.entry > .col-narrow, .entry > .col-form { position: relative; z-index: 1; }

.sigil {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 150vh;
  height: 150vh;
  /* Taller than the window on purpose: the ring runs off the top and the
     bottom, so it reads as the wall behind the page. The nudge sideways is
     small - it is the background, not something parked in a corner. */
  transform: translate(calc(-50% + 11vw), -50%);
  color: var(--iron);
  opacity: 0.4;
  pointer-events: none;
  z-index: 0;
  transition: color 700ms ease, opacity 700ms ease;
}
.sigil svg { width: 100%; height: 100%; display: block; }

/* Blue while the attempt is on its way, and it stays blue until the answer
   arrives rather than fading out under it. */
.sigil.trying {
  color: var(--blue);
  opacity: 0.62;
  transition: color 350ms ease, opacity 350ms ease;
}
.sigil.failed { animation: sigil-refused 1800ms ease; }

@keyframes sigil-refused {
  0% { color: var(--iron); opacity: 0.4; }
  14% { color: var(--alert); opacity: 0.7; }
  46% { color: var(--alert); opacity: 0.7; }
  100% { color: var(--iron); opacity: 0.4; }
}

@media (max-width: 900px) {
  /* Still the background, just quiet enough to read the form over it. */
  .sigil { transform: translate(calc(-50% + 18vw), -50%); opacity: 0.2; }
}

/* ---- offer cards ------------------------------------------------------ */

.tier { display: flex; flex-direction: column; }
.tier .price {
  font-family: var(--font-display);
  font-size: 2rem;
  color: var(--mark-node);
  line-height: 1;
  margin: 0.3rem 0 0.6rem;
}
.cur { margin-right: 0.14em; }
.price .cur, .total .cur { font-size: 0.62em; vertical-align: 0.16em; color: var(--blue-dk); }
.tier .price small { font-family: var(--font-mono); font-size: 0.72rem; color: var(--steel); display: block; letter-spacing: 0.1em; }
/* The individual tier has no number to show, so it does not pretend to. */
.tier .price.tbd { font-size: 1.25rem; color: var(--ash); }
.tier p { color: var(--ash); font-size: 0.94rem; flex: 1; }

/* ---- tables and lists ------------------------------------------------ */

.list { display: grid; gap: 0.6rem; }

.item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.8rem 1.2rem;
  align-items: start;
  background: var(--graphite);
  border: 1px solid var(--hairline);
  border-left: var(--edge) solid var(--hairline);
  border-radius: 2px;
  padding: 0.95rem 1.1rem;
}
@media (max-width: 620px) {
  .item { grid-template-columns: 1fr; }
  .item .actions { justify-content: flex-start; }
}
.item.live { border-left-color: var(--green); }
.item.wait { border-left-color: var(--blue); }
.item.dead { border-left-color: var(--iron); }
.item h3 { margin-bottom: 0.2rem; }
/* A heading and the badge beside it sit on one centre line: inline they would
   follow the heading's baseline and hang below it. */
.row.titled { margin-bottom: 0.2rem; }
.row.titled h3 { margin: 0; }
.item .meta { font-family: var(--font-mono); font-size: 0.76rem; color: var(--steel); margin: 0; }
.item .meta + .meta { margin-top: 0.2rem; }
a.item { color: inherit; text-decoration: none; }
a.item:hover { border-color: var(--blue); text-decoration: none; }
.item .actions { display: flex; gap: 0.5rem; align-items: center; }

table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
input.cell-name, select.cell-name { width: 8.5rem; }
input.cell-num, select.cell-num { width: 5rem; }
input.cell-mid, select.cell-mid { width: 7.5rem; }
input.cell-wide, select.cell-wide { width: 11rem; }
td form { display: inline; }
th, td { text-align: left; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--hairline); }
th {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel);
  font-weight: 400;
}
td.num, th.num { text-align: right; font-family: var(--font-mono); }
.scroll { overflow-x: auto; }

/* ---- support thread -------------------------------------------------- */

.chat { display: grid; gap: 0.9rem; margin: 1.4rem 0 1.5rem; }

.msg {
  max-width: 46rem;
  border: 1px solid var(--hairline);
  border-radius: 3px;
  padding: 0.85rem 1rem;
  background: var(--graphite);
}
.msg.mine { margin-left: auto; background: var(--deep-blue); border-color: var(--blue-dk); }
.msg .who {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--steel);
  display: flex;
  gap: 0.6rem;
  justify-content: space-between;
  margin-bottom: 0.4rem;
}
.msg .body { white-space: pre-wrap; overflow-wrap: anywhere; }
.msg .body:empty { display: none; }

.files { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.7rem; }
.shot { position: relative; }
.shot img {
  display: block;
  max-width: 220px;
  max-height: 180px;
  border: 1px solid var(--hairline);
  border-radius: 2px;
}
/* The delete control sits on the image, so it needs its own backdrop to stay
   legible over whatever was uploaded. */
.shot form { position: absolute; top: 4px; right: 4px; }
.shot form .btn { background: rgba(8, 13, 20, 0.85); backdrop-filter: blur(2px); }
.gone {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  color: var(--steel);
  border: 1px dashed var(--iron);
  border-radius: 2px;
  padding: 0.5rem 0.7rem;
}

/* ---- staff contour --------------------------------------------------- */

.staff { --gap: 1rem; }
.staff .page { padding-top: 1.6rem; }
.staff .top { background: var(--carbon); }
.staff .brand { font-size: 1.1rem; }
.staff h1 { font-size: 1.5rem; }

.staff .item { padding: 0.7rem 0.9rem; }

.banner {
  border: 1px solid var(--alert);
  border-left: 4px solid var(--alert);
  background: rgba(214, 65, 82, 0.12);
  color: var(--alert-lt);
  padding: 0.8rem 1rem;
  margin-bottom: var(--gap);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.filters { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: var(--gap); }
.filters a {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--hairline);
  border-radius: 2px;
  color: var(--ash);
}
.filters a.on { background: var(--deep-blue); border-color: var(--blue-dk); color: var(--mark-node); }
.filters a:hover { text-decoration: none; border-color: var(--blue); }

.cols { display: grid; grid-template-columns: 1fr 22rem; gap: 1.5rem; align-items: start; }
/* A grid track will not shrink below the widest thing inside it unless told
   to: without this a wide table drags the whole page sideways on a phone
   instead of scrolling inside its own container. */
.cols > *, .grid > * { min-width: 0; }
@media (max-width: 980px) { .cols { grid-template-columns: 1fr; } }

.side { position: sticky; top: 1rem; display: grid; gap: 1rem; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 0.3rem 0.8rem; font-size: 0.88rem; }
.kv dt { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--steel); align-self: center; }
.kv dd { margin: 0; overflow-wrap: anywhere; }
.kv dd.mono { word-break: break-all; }
/* In a narrow column a UUID split across two lines is unreadable, so the
   label sits above the value and the value gets the full width. */
.kv.stacked { grid-template-columns: 1fr; gap: 0 0; }
.kv.stacked dd { margin-bottom: 0.6rem; }

.qr { background: var(--fog); padding: 0.6rem; border-radius: 3px; display: inline-block; }
.qr img { display: block; width: 200px; height: 200px; }

.tiny { font-size: 0.76rem; }
.secret {
  font-family: var(--font-mono);
  font-size: 1rem;
  letter-spacing: 0.12em;
  background: var(--carbon);
  border: 1px dashed var(--hairline);
  padding: 0.6rem 0.8rem;
  border-radius: 2px;
  word-break: break-all;
  display: inline-block;
  margin-top: 0.3rem;
}

.captcha { border: 1px solid var(--hairline); border-radius: 2px; display: block; margin-bottom: 0.6rem; max-width: 100%; }

.empty {
  border: 1px dashed var(--iron);
  border-radius: 3px;
  padding: 1.6rem;
  color: var(--steel);
  text-align: center;
}

.split { display: flex; gap: 1rem; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; }
.right { margin-left: auto; }
.nowrap { white-space: nowrap; }
