/* zkghost kit — the look every page shares.
 *
 * Everything here is drawn on a pixel grid, the same grid the ghosts are drawn
 * on: corners are staircases, bottom edges are cloak hems, gradients are 4×4
 * Bayer dither, connectors are dotted trails. No curves, no blur, no glow.
 *
 * Defaults are wrapped in :where() so a page can override any of them with a
 * single class and no specificity fight.
 *
 * Contents
 *   tokens · reset · type · layout · pixel headings · stepped shapes · buttons
 *   chips · nodes + null trail · cards · fields · hem · dither · header/footer
 *   ghosts · utilities · motion
 */

/* ── Tokens ────────────────────────────────────────────────────────────── */

:root {
  /* Zcash theme: warm black, Zcash yellow, white. (--teal kept as the name of the
     action colour so every component follows; its value is Zcash yellow.) */
  --bg: #0c0b09; --bg-2: #15130f; --bg-3: #1c1914;         /* warm black layers */
  --ink: #faf7f0; --dim: #a8a192; --faint: #6e6859; --ghost: #2a261d;
  --line: #26221a; --line-2: #3a3426;
  --teal: #f4b728; --teal-hi: #ffd166; --teal-lo: #8a6512;   /* actions, ghostlisted: Zcash yellow */
  --gold: #f4b728; --gold-hi: #ffe29a; --gold-lo: #8a6512;   /* proofs, seals */
  --leak: #ff3b4e;                                           /* errors, not this round, turnstile */
  /* No green anywhere in the interface (Zcash theme): Sapling's chip and accent are a warm light tone. */
  --sapling: #d9cfb8; --orchard: #7d86ea; --indigo: #5a5fd0; --exposed: #e9fbff;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Helvetica, Arial, sans-serif;

  /* Grid. --u is one UI pixel: the step of a corner, the width of a frame.
     --gut is the page's side gutter (16px on a phone, as the brief asks). */
  --u: 3px;
  --wrap: 1180px;
  --gut: clamp(16px, 4vw, 40px);
  --head-h: 64px;

  color-scheme: dark;
  scrollbar-color: var(--ghost) var(--bg);
}

/* ── Reset ─────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-padding-top: calc(var(--head-h) + 24px); }
body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font: 400 16px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* The pool and a few hero ghosts deliberately bleed past their column; the
     page itself must never scroll sideways on a phone. */
  overflow-x: clip;
}
:where(h1, h2, h3, h4, p, figure, blockquote, ol, ul, dl, dd) { margin: 0; }
:where(ol, ul)[role="list"], :where(.plain) { list-style: none; padding: 0; }
:where(img, svg, canvas, video) { display: block; max-width: 100%; }
:where(button, input, textarea, select) { font: inherit; color: inherit; }
:where(button) { cursor: pointer; }
[hidden] { display: none !important; }
::selection { background: var(--teal); color: var(--bg); }

:where(a) {
  color: var(--teal-hi);
  text-decoration: underline;
  text-decoration-color: var(--teal-lo);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}
:where(a:hover) { color: var(--ink); text-decoration-color: var(--teal); }

/* One focus look everywhere: a gold-hi square outline, offset so it never sits
   on top of the thing it marks. Stepped shapes draw their own (see buttons). */
:focus-visible { outline: 2px solid var(--gold-hi); outline-offset: 3px; }
:focus:not(:focus-visible) { outline: none; }

/* ── Type ──────────────────────────────────────────────────────────────── */

:where(h1, h2, h3) { font-weight: 700; line-height: 1.15; letter-spacing: -0.01em; }
:where(h1) { font-size: clamp(32px, 5vw, 56px); }
:where(h2) { font-size: clamp(26px, 3.4vw, 40px); }
:where(h3) { font-size: 20px; }
:where(p) { max-width: 62ch; }
:where(strong, b) { font-weight: 650; color: var(--ink); }
:where(code, kbd, samp) { font-family: var(--mono); font-size: 0.92em; }

.lede { font-size: clamp(18px, 1.1vw + 13px, 22px); line-height: 1.5; color: var(--dim); }
.small { font-size: 14px; line-height: 1.55; }
/* Small labels are mono in sentence case at normal tracking — quiet, not
   shouty. They never become the dominant voice of a page. */
.mono { font-family: var(--mono); font-size: 13px; line-height: 1.5; letter-spacing: 0; }
.tnum { font-variant-numeric: tabular-nums; }

.ink { color: var(--ink); }  .dim { color: var(--dim); }  .faint { color: var(--faint); }
.teal { color: var(--teal); } .gold { color: var(--gold); } .leak { color: var(--leak); }

/* ── Layout ────────────────────────────────────────────────────────────── */

.wrap { width: 100%; max-width: calc(var(--wrap) + 2 * var(--gut)); margin-inline: auto; padding-inline: var(--gut); }
.stack > * + * { margin-top: var(--gap, 16px); }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap, 12px); }

/* ── Pixel headings ────────────────────────────────────────────────────────
   pixeltype.js swaps [data-px] text for an inline SVG drawn from
   pixelfont.json and adds .px. The SVG fills with currentColor, so `color`
   styles a heading. Before that runs (or if the font never arrives) the text
   shows as bold mono at a similar size, so nothing jumps far or disappears. */

:where([data-px]) {
  --px-scale: 4;
  font-family: var(--sans);
  font-weight: 800;
  font-size: calc(var(--px-scale) * 9px);
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--ink);
  overflow-wrap: anywhere;
}
:where(h1[data-px]) { --px-scale: 6; }
:where(h2[data-px]) { --px-scale: 5; }
:where(h3[data-px], h4[data-px]) { --px-scale: 3; }
@media (max-width: 639px) {
  :where(h1[data-px]) { --px-scale: 4; }
  :where(h2[data-px]) { --px-scale: 3; }
}
.px { line-height: 0; overflow-wrap: normal; }
.px > svg { display: inline-block; vertical-align: top; max-width: none; shape-rendering: crispEdges; }
.px-fallback { font-family: var(--sans); font-weight: 800; }

/* ── Stepped shapes ────────────────────────────────────────────────────────
   Two clip-path polygons do all the corner work. They go on pseudo-elements,
   so the element itself is never clipped (clip-path would also eat its focus
   outline and anything that pokes out of it).

   stair  a filled box with a two-step staircase cut from each corner.
   ring   the same outline, hollow: an outer and an inner staircase joined
          under evenodd. Eroding a staircase by --t gives the same staircase
          moved in by --t, so the frame is an even --t all the way round.

   Inputs, set on the pseudo-element or inherited from its element:
     --s  step size    --t  ring thickness    --i  inset (default 0)
     --sb bottom step (a hem sets 0 to square the bottom corners)

   Selectors that need the other shape later (a hovered outline button fills
   in, a done node fills in) are listed with enough specificity to win. */

:where(.btn, .chip, .node, .card, .well, .note-portrait) { --sb: initial; --tb: initial; }

.btn::before, .btn::after, .chip::after, .node::before, .card::before, .card::after, .well::before, .stair,
.note-portrait::before, .note-portrait .sprite,
.btn.ghost:hover::before, .node.done::before {
  --a0: var(--i, 0px);
  --a1: calc(var(--a0) + var(--s));
  --a2: calc(var(--a0) + 2 * var(--s));
  --c1: calc(var(--a0) + var(--sb, var(--s)));
  --c2: calc(var(--a0) + 2 * var(--sb, var(--s)));
  clip-path: polygon(
    var(--a0) var(--a2), var(--a1) var(--a2), var(--a1) var(--a1), var(--a2) var(--a1), var(--a2) var(--a0),
    calc(100% - var(--a2)) var(--a0), calc(100% - var(--a2)) var(--a1), calc(100% - var(--a1)) var(--a1), calc(100% - var(--a1)) var(--a2), calc(100% - var(--a0)) var(--a2),
    calc(100% - var(--a0)) calc(100% - var(--c2)), calc(100% - var(--c1)) calc(100% - var(--c2)), calc(100% - var(--c1)) calc(100% - var(--c1)), calc(100% - var(--c2)) calc(100% - var(--c1)), calc(100% - var(--c2)) calc(100% - var(--a0)),
    var(--c2) calc(100% - var(--a0)), var(--c2) calc(100% - var(--c1)), var(--c1) calc(100% - var(--c1)), var(--c1) calc(100% - var(--c2)), var(--a0) calc(100% - var(--c2))
  );
}

.btn.ghost::before, .btn:focus-visible::after, .chip::before, .node::before, .ring,
.btn.ghost:disabled:hover::before, .btn.ghost[aria-disabled="true"]:hover::before {
  --a0: var(--i, 0px);
  --a1: calc(var(--a0) + var(--s));
  --a2: calc(var(--a0) + 2 * var(--s));
  --b0: calc(var(--a0) + var(--t));
  --b1: calc(var(--b0) + var(--s));
  --b2: calc(var(--b0) + 2 * var(--s));
  clip-path: polygon(evenodd,
    var(--a0) var(--a2), var(--a1) var(--a2), var(--a1) var(--a1), var(--a2) var(--a1), var(--a2) var(--a0),
    calc(100% - var(--a2)) var(--a0), calc(100% - var(--a2)) var(--a1), calc(100% - var(--a1)) var(--a1), calc(100% - var(--a1)) var(--a2), calc(100% - var(--a0)) var(--a2),
    calc(100% - var(--a0)) calc(100% - var(--a2)), calc(100% - var(--a1)) calc(100% - var(--a2)), calc(100% - var(--a1)) calc(100% - var(--a1)), calc(100% - var(--a2)) calc(100% - var(--a1)), calc(100% - var(--a2)) calc(100% - var(--a0)),
    var(--a2) calc(100% - var(--a0)), var(--a2) calc(100% - var(--a1)), var(--a1) calc(100% - var(--a1)), var(--a1) calc(100% - var(--a2)), var(--a0) calc(100% - var(--a2)),
    var(--a0) var(--a2),
    var(--b0) var(--b2), var(--b1) var(--b2), var(--b1) var(--b1), var(--b2) var(--b1), var(--b2) var(--b0),
    calc(100% - var(--b2)) var(--b0), calc(100% - var(--b2)) var(--b1), calc(100% - var(--b1)) var(--b1), calc(100% - var(--b1)) var(--b2), calc(100% - var(--b0)) var(--b2),
    calc(100% - var(--b0)) calc(100% - var(--b2)), calc(100% - var(--b1)) calc(100% - var(--b2)), calc(100% - var(--b1)) calc(100% - var(--b1)), calc(100% - var(--b2)) calc(100% - var(--b1)), calc(100% - var(--b2)) calc(100% - var(--b0)),
    var(--b2) calc(100% - var(--b0)), var(--b2) calc(100% - var(--b1)), var(--b1) calc(100% - var(--b1)), var(--b1) calc(100% - var(--b2)), var(--b0) calc(100% - var(--b2)),
    var(--b0) var(--b2)
  );
}

/* ── Buttons ───────────────────────────────────────────────────────────────
   ::before is the face, ::after the darker base it stands on. On hover the
   button rises one UI pixel and the base stays put, so a strip of it shows
   underneath: a pixel lift, not a shadow. Pressing sets it back down. */

.btn {
  --s: var(--u);
  --t: 2px;
  --face: var(--teal);
  --base: var(--teal-lo);
  --label: var(--bg);
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 46px;
  padding: 0 22px;
  border: 0;
  background: none;
  color: var(--label);
  font: 650 15px/1.2 var(--sans);
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.btn::before, .btn::after { content: ""; position: absolute; inset: 0; pointer-events: none; }
.btn::before { z-index: -1; background: var(--face); }
.btn::after { z-index: -2; background: var(--base); }
.btn:hover { color: var(--label); translate: 0 calc(-1 * var(--u)); }
.btn:hover::after { translate: 0 var(--u); }
.btn:active, .btn:active::after { translate: none; }

/* Focus: the base gives way to a stepped gold-hi ring just outside the face. */
.btn:focus-visible { outline: none; }
.btn:focus-visible::after { inset: calc(-2 * var(--u)); z-index: 1; background: var(--gold-hi); translate: none; }

.btn.ghost { --label: var(--teal-hi); }
.btn.ghost::after { background: none; }
.btn.ghost:hover { --label: var(--bg); }
.btn.ghost:hover::after { background: var(--base); }
.btn.gold { --face: var(--gold); --base: var(--gold-lo); --label: #140d00; }
.btn.gold.ghost { --label: var(--gold-hi); }
.btn.gold.ghost:hover { --label: #140d00; }
.btn.quiet { --face: var(--ghost); --base: var(--line); --label: var(--ink); }
.btn.leak { --face: var(--leak); --base: #8a1a26; --label: #1a0206; }
.btn.leak.ghost { --label: var(--leak); }

.btn.sm { --s: 2px; min-height: 36px; padding: 0 14px; font-size: 14px; }
.btn.lg { min-height: 56px; padding: 0 30px; font-size: 17px; }
.btn.wide { width: 100%; }
.btn .arrow { font-family: var(--mono); font-weight: 400; }

.btn:disabled, .btn[aria-disabled="true"] {
  --face: var(--ghost); --base: var(--ghost); --label: var(--faint);
  cursor: not-allowed; translate: none;
}
.btn.ghost:disabled, .btn.ghost[aria-disabled="true"] { --face: var(--line-2); --label: var(--faint); }
.btn:disabled::after, .btn[aria-disabled="true"]::after { translate: none; }
.btn.ghost:disabled:hover::after, .btn.ghost[aria-disabled="true"]:hover::after { background: none; }

/* A text-weight action for secondary moves ("switch", "refresh"). */
.link {
  padding: 0; border: 0; background: none;
  color: var(--teal-hi); font: inherit; cursor: pointer;
  text-decoration: underline; text-decoration-color: var(--teal-lo);
  text-decoration-thickness: 2px; text-underline-offset: 4px;
}
.link:hover { color: var(--ink); text-decoration-color: var(--teal); }

/* ── Chips ─────────────────────────────────────────────────────────────────
   A stepped ring (::before) around a faint tint of the same colour (::after),
   with a pixel plus in front painted by the chip's own background. State
   chips are the one place the three state colours sit side by side, so they
   have to be told apart at a glance, even in greyscale: pending is the only
   one with a hollow centre in its pip. */

.chip {
  --c: var(--dim);
  --s: 2px;
  --t: 2px;
  --pip:
    linear-gradient(var(--c) 0 0) 12px calc(50% - 1px) / 2px 6px no-repeat,
    linear-gradient(var(--c) 0 0) 10px calc(50% - 1px) / 6px 2px no-repeat;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 11px 0 24px;
  background: var(--pip);
  color: var(--c);
  font: 500 13px/1 var(--mono);
  letter-spacing: 0;
  white-space: nowrap;
  vertical-align: middle;
}
.chip::before, .chip::after { content: ""; position: absolute; inset: 0; pointer-events: none; }
.chip::before { z-index: -1; background: var(--c); }
.chip::after { --i: var(--t); z-index: -2; background: color-mix(in srgb, var(--c) 11%, transparent); }
.chip.bare { padding-left: 11px; background: none; }
.chip.pending     { --c: var(--gold); }
.chip.ghostlisted { --c: var(--teal); }
.chip.round       { --c: var(--leak); }
.chip.orchard     { --c: var(--orchard); }
.chip.sapling     { --c: var(--sapling); }
.chip.transparent { --c: var(--faint); }
.chip.transparent, .chip.transparent * { text-decoration: line-through 1px; }
.chip.teal { --c: var(--teal); } .chip.gold { --c: var(--gold); } .chip.leak { --c: var(--leak); }
.chip.lg { min-height: 34px; font-size: 14px; }
/* hollow pip for pending: a ring of 2px squares instead of a plus */
.chip.pending {
  --pip:
    linear-gradient(var(--c) 0 0) 10px calc(50% - 3px) / 6px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 10px calc(50% + 1px) / 6px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 10px calc(50% - 1px) / 2px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 14px calc(50% - 1px) / 2px 2px no-repeat;
}
.chip.round {
  /* an x of 2px squares */
  --pip:
    linear-gradient(var(--c) 0 0) 10px calc(50% - 3px) / 2px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 14px calc(50% - 3px) / 2px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 12px calc(50% - 1px) / 2px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 10px calc(50% + 1px) / 2px 2px no-repeat,
    linear-gradient(var(--c) 0 0) 14px calc(50% + 1px) / 2px 2px no-repeat;
}

/* "sealed": a chip filled with 50% dither, standing in for a value not out
   yet. Never a black bar. */
.chip.sealed { --c: var(--gold); padding-left: 11px; background: none; color: var(--gold-hi); }
.chip.sealed::after {
  background: repeating-conic-gradient(color-mix(in srgb, var(--gold) 30%, transparent) 0 25%, transparent 0 50%) 0 0 / 4px 4px;
}

/* ── Nodes + null trail ────────────────────────────────────────────────────
   .node is a stepped pixel square: faint outline (to do), gold outline (now),
   teal and filled (done). .trail lays its children in a row and joins them
   with dotted pixel connectors drawn as each child's ::after, so children
   are plain wrappers (li), not chips or buttons, which own their ::after.
   .trail-line is a standalone connector; .v makes either vertical. */

.node {
  --c: var(--faint);
  --s: 2px;
  --t: 2px;
  position: relative;
  isolation: isolate;
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  color: var(--c);
  font: 700 12px/1 var(--mono);
}
.node::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--c); }
.node.now  { --c: var(--gold); }
.node.done { --c: var(--teal); color: var(--bg); }

.trail-line, .trail > :not(:last-child)::after {
  content: "";
  display: block;
  height: 4px;
  background: linear-gradient(90deg, var(--trail, var(--line-2)) 0 4px, transparent 4px) 0 0 / 10px 4px repeat-x;
}
.trail-line.v, .trail.v > :not(:last-child)::after {
  width: 4px;
  height: auto;
  min-height: 24px;
  background: linear-gradient(var(--trail, var(--line-2)) 0 4px, transparent 4px) 0 0 / 4px 10px repeat-y;
}
.trail { display: flex; align-items: center; margin: 0; padding: 0; list-style: none; }
.trail > * { display: flex; align-items: center; gap: 10px; flex: 1 1 0; min-width: 0; }
.trail > :last-child { flex: 0 0 auto; }
.trail > :not(:last-child)::after { flex: 1 1 auto; min-width: 16px; margin: 0 12px; }
.trail > .done:not(:last-child)::after { --trail: var(--teal-lo); }
.trail.v { flex-direction: column; align-items: stretch; }
.trail.v > * { flex-direction: column; align-items: flex-start; }
.trail.v > :not(:last-child)::after { margin: 8px 0 8px 10px; }

/* ── Cards ─────────────────────────────────────────────────────────────────
   ::before paints the frame colour over the whole stair, ::after paints the
   fill one frame-width inside it with the same stair. On a .hem card the fill
   runs to the bottom and its bottom corners are square, so the hem cuts cloth
   rather than a border. */

.card {
  --s: 4px;
  --t: 2px;
  --frame: var(--line-2);
  --fill: var(--bg-2);
  position: relative;
  isolation: isolate;
  padding: clamp(18px, 3vw, 28px);
}
.card::before, .card::after { content: ""; position: absolute; pointer-events: none; }
.card::before { inset: 0; z-index: -2; background: var(--frame); }
.card::after { inset: var(--t) var(--t) var(--tb, var(--t)); z-index: -1; background: var(--fill); }
/* A hem card is not masked as a whole (that would also cut anything pressed
   over its edge, like a note's seal). The cloth (::after) takes the hem; the
   frame (::before) stops above the teeth and, over the lower half of the
   card, breaks up in dither — the inverse ramp, made by XOR with a solid
   layer — the way the art's cloaks dissolve at the hem. */
.card.hem {
  --sb: 0px;
  --tb: 0px;
  --fill: var(--bg-3);
  padding-bottom: calc(clamp(18px, 3vw, 28px) + 4 * var(--hem-u));
  -webkit-mask: none;
  mask: none;
}
.card.hem::after {
  -webkit-mask:
    linear-gradient(#000 0 0) top left / 100% calc(100% - 4 * var(--hem-u) + 1px) no-repeat,
    var(--hem-tile) bottom center / calc(10 * var(--hem-u)) calc(4 * var(--hem-u)) repeat-x;
  mask:
    linear-gradient(#000 0 0) top left / 100% calc(100% - 4 * var(--hem-u) + 1px) no-repeat,
    var(--hem-tile) bottom center / calc(10 * var(--hem-u)) calc(4 * var(--hem-u)) repeat-x;
}
.card.hem::before {
  bottom: calc(4 * var(--hem-u));
  -webkit-mask:
    var(--ramp-4) 0 100% / 100% 50% no-repeat,
    linear-gradient(#000 0 0) 0 100% / 100% 50% no-repeat,
    linear-gradient(#000 0 0) 0 0 / 100% 51% no-repeat;
  -webkit-mask-composite: xor, source-over;
  mask:
    var(--ramp-4) 0 100% / 100% 50% no-repeat exclude,
    linear-gradient(#000 0 0) 0 100% / 100% 50% no-repeat add,
    linear-gradient(#000 0 0) 0 0 / 100% 51% no-repeat;
}
.card.lit { --frame: var(--teal-lo); }
.card.gold { --frame: var(--gold-lo); }
.card.leak { --frame: color-mix(in srgb, var(--leak) 55%, var(--bg-2)); }
.card.flat { --frame: var(--fill); }

/* A recessed panel inside a card: a quote scrap, a code block. */
.well {
  --s: 2px;
  position: relative;
  isolation: isolate;
  padding: 16px;
}
.well::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--bg); }

/* ── Fields ────────────────────────────────────────────────────────────────
   Inputs cannot carry pseudo-elements, so their stepped corner is built from
   the border (clipped at each corner) plus four 2px corner blocks painted by
   the background: a one-pixel staircase in the frame colour. */

:where(input:not([type]), input[type="text"], input[type="search"], input[type="url"], input[type="email"], textarea) {
  --frame: var(--line-2);
  --fill: var(--bg-2);
  display: block;
  width: 100%;
  min-height: 48px;
  padding: 11px 14px;
  border: 2px solid var(--frame);
  border-radius: 0;
  background:
    linear-gradient(var(--frame) 0 0) 0 0 / 2px 2px no-repeat,
    linear-gradient(var(--frame) 0 0) 100% 0 / 2px 2px no-repeat,
    linear-gradient(var(--frame) 0 0) 0 100% / 2px 2px no-repeat,
    linear-gradient(var(--frame) 0 0) 100% 100% / 2px 2px no-repeat,
    var(--fill);
  background-origin: padding-box;
  clip-path: polygon(4px 0, calc(100% - 4px) 0, calc(100% - 4px) 2px, calc(100% - 2px) 2px, calc(100% - 2px) 4px, 100% 4px,
    100% calc(100% - 4px), calc(100% - 2px) calc(100% - 4px), calc(100% - 2px) calc(100% - 2px), calc(100% - 4px) calc(100% - 2px), calc(100% - 4px) 100%,
    4px 100%, 4px calc(100% - 2px), 2px calc(100% - 2px), 2px calc(100% - 4px), 0 calc(100% - 4px),
    0 4px, 2px 4px, 2px 2px, 4px 2px);
  color: var(--ink);
  font-size: 16px;               /* 16px keeps iOS from zooming the page on focus */
  line-height: 1.45;
  caret-color: var(--teal);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
:where(textarea) { min-height: 96px; resize: vertical; font-family: var(--mono); font-size: 15px; word-break: break-all; }
:where(input, textarea)::placeholder { color: var(--faint); opacity: 1; }
:where(input, textarea):hover { --frame: var(--ghost); }
:where(input, textarea):focus, :where(input, textarea):focus-visible { --frame: var(--teal); --fill: var(--bg-3); outline: none; }
:where(input, textarea)[aria-invalid="true"], .field.bad :where(input, textarea) { --frame: var(--leak); }

.field { display: grid; gap: 8px; min-width: 0; }
.field > label, .field > .label { color: var(--dim); font: 500 14px/1.3 var(--sans); }
.field .hint { color: var(--faint); font: 13px/1.5 var(--mono); }
.field .error, .field.bad .hint { color: var(--leak); font: 13px/1.5 var(--mono); }
/* An input with its action attached: "@handle [View]" */
.field .inline { display: flex; gap: 10px; align-items: stretch; }
.field .inline > :first-child { flex: 1 1 auto; min-width: 0; }
.field .inline > .btn { min-height: 48px; }
/* A leading glyph ("@") sitting inside the input's frame */
.affix { position: relative; display: block; flex: 1 1 auto; min-width: 0; }
.affix > b {
  position: absolute; left: 14px; top: 50%; translate: 0 -50%;
  z-index: 1;
  color: var(--faint); font: 500 16px/1 var(--mono); pointer-events: none;
}
.affix > input { padding-left: 32px; }

/* ── Sealed note ───────────────────────────────────────────────────────────
   note.js builds this. A memo card cut like a ghost: stepped shoulders, a
   frame that dissolves in dither toward a cloak hem, and a gold seal pressed
   over the top corner. The seal is a faceless ghost debossed in gold: proof
   of presence, nothing about the person. State lives in --state, so frame,
   portrait ring and chip always change together. */

.note {
  --state: var(--gold);
  --fill: var(--bg-3);
  --frame: color-mix(in srgb, var(--state) 42%, var(--line-2));
  --hem-u: 4px;
  width: 100%;
  max-width: 440px;
  padding: 32px 24px calc(30px + 4 * var(--hem-u));
  text-align: center;
}
.note.is-approved { --state: var(--teal); }
.note.is-rejected { --state: var(--leak); }

.note-seal {
  position: absolute;
  top: -20px;
  right: -14px;
  z-index: 2;
  width: 60px;
  height: 60px;
  shape-rendering: crispEdges;
}
.note-head { display: grid; justify-items: center; gap: 10px; }
.note-title { color: var(--ink); }
.note-ref { color: var(--dim); font: 14px/1.3 var(--mono); }
.note-ref b { color: var(--ink); font-weight: 500; }

/* The portrait: the ghost's own art, corners stepped, in a ring of the
   state colour. Nothing is laid over the art. */
.note-portrait {
  --s: 4px;
  --t: 3px;
  position: relative;
  isolation: isolate;
  display: grid;
  width: max-content;
  margin: 24px auto 22px;
  padding: var(--t);
}
.note-portrait::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--state) 62%, var(--bg));
}
.note-void { width: var(--gpx, 256px); height: var(--gpx, 256px); background: var(--bg); }
@media (max-width: 359px) {
  .note:not(.compact) .note-portrait .sprite, .note:not(.compact) .note-void { --gpx: 192px !important; }
}

.note-who { display: grid; justify-items: center; gap: 10px; }
.note-handle { color: var(--ink); font: 650 22px/1.2 var(--sans); overflow-wrap: anywhere; }
.note-line { max-width: 34ch; color: var(--dim); font-size: 15px; line-height: 1.5; }
.note .chip { --c: var(--state); }

.note-addr { position: relative; margin-top: 22px; padding-top: 14px; text-align: left; }
.note-addr::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--line-2) 0 2px, transparent 2px) 0 0 / 6px 2px repeat-x;
}
.note-addr-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 40px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--dim);
  text-align: left;
}
.note-addr-btn .k { color: var(--faint); font: 13px/1 var(--mono); }
.note-addr-btn .v { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font: 14px/1 var(--mono); }
.note-addr-btn:hover .v { color: var(--teal-hi); }
/* pixel caret: 2px squares in a chevron, flipped once open */
.note-addr-btn .caret {
  flex: 0 0 auto;
  width: 10px;
  height: 6px;
  background:
    linear-gradient(var(--dim) 0 0) 0 0 / 2px 2px no-repeat,
    linear-gradient(var(--dim) 0 0) 2px 2px / 2px 2px no-repeat,
    linear-gradient(var(--dim) 0 0) 4px 4px / 2px 2px no-repeat,
    linear-gradient(var(--dim) 0 0) 6px 2px / 2px 2px no-repeat,
    linear-gradient(var(--dim) 0 0) 8px 0 / 2px 2px no-repeat;
}
.note-addr-btn[aria-expanded="true"] .caret { transform: scaleY(-1); }
.note-rx { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.note-rx .chip { --c: var(--dim); }
.note-rx .chip.orchard { --c: var(--orchard); }
.note-rx .chip.sapling { --c: var(--sapling); }
.note-rx .chip.transparent { --c: var(--faint); }
.note-addr-full {
  margin-top: 12px;
  padding: 12px 14px;
  background: var(--bg);
  color: var(--ink);
  font: 13px/1.6 var(--mono);
  word-break: break-all;
  user-select: all;
}

/* Compact: the viewing-key answer. Portrait beside the words, no address. */
.note.compact {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 24px;
  align-items: center;
  max-width: 560px;
  padding-top: 26px;
  text-align: left;
}
.note.compact .note-portrait { grid-row: 1 / span 2; margin: 0; }
.note.compact .note-head, .note.compact .note-who { justify-items: start; }
.note.compact .note-head { padding-right: 36px; }
.note.compact .note-who { margin-top: 12px; }
.note.compact .note-handle { font-size: 19px; }
.note.compact .note-seal { width: 45px; height: 45px; top: -14px; right: -10px; }
@media (max-width: 479px) {
  .note { padding-inline: 18px; }
  .note.compact { grid-template-columns: 1fr; text-align: center; }
  .note.compact .note-portrait { grid-row: auto; margin: 18px auto 14px; }
  .note.compact .note-head, .note.compact .note-who { justify-items: center; padding-right: 0; }
}

/* ── Ghost hem ─────────────────────────────────────────────────────────────
   The bottom edge of a ghost's cloak: rounded pixel lobes hanging from the
   cloth with narrow gaps between, cut with a mask so whatever is behind shows
   through. One tile is 10 × 4 hem pixels, a lobe centred in it; --hem-u sets
   the size of a hem pixel. */

:root { --hem-u: 4px; }
.hem {
  --hem-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 4' shape-rendering='crispEdges'%3E%3Cpath d='M0 0H9V2H8V3H7V4H3V3H2V2H1V0Z'/%3E%3C/svg%3E");
  -webkit-mask:
    linear-gradient(#000 0 0) top left / 100% calc(100% - 4 * var(--hem-u) + 1px) no-repeat,
    var(--hem-tile) bottom center / calc(10 * var(--hem-u)) calc(4 * var(--hem-u)) repeat-x;
  mask:
    linear-gradient(#000 0 0) top left / 100% calc(100% - 4 * var(--hem-u) + 1px) no-repeat,
    var(--hem-tile) bottom center / calc(10 * var(--hem-u)) calc(4 * var(--hem-u)) repeat-x;
}
.hem-sm { --hem-u: 3px; }
.hem-lg { --hem-u: 6px; }

/* ── Dither ────────────────────────────────────────────────────────────────
   A 4×4 Bayer ramp, the same ordered dither the art uses, as an SVG mask:
   17 bands top to bottom, band k lighting every pixel whose Bayer threshold
   is below k. It is a mask, so the colour stays a CSS variable.

   .dither-down  behind the content: transparent at the top, --dither at the bottom
   .dither-up    the same, flipped
   .dither-fade  the element itself dissolves away toward its bottom edge
   .dither-fade-top  … toward its top edge
   .dither-fine  2px dither pixels instead of 4px                            */

:root {
  --ramp-4: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' shape-rendering='crispEdges'%3E%3Cpattern id='a' width='16' height='16' patternUnits='userSpaceOnUse'%3E%3Crect width='4' height='4'/%3E%3C/pattern%3E%3Cpattern id='b' href='%23a' patternTransform='translate(8 8)'/%3E%3Cpattern id='c' href='%23a' patternTransform='translate(8 0)'/%3E%3Cpattern id='d' href='%23a' patternTransform='translate(0 8)'/%3E%3Cpattern id='e' href='%23a' patternTransform='translate(4 4)'/%3E%3Cpattern id='f' href='%23a' patternTransform='translate(12 12)'/%3E%3Cpattern id='g' href='%23a' patternTransform='translate(12 4)'/%3E%3Cpattern id='h' href='%23a' patternTransform='translate(4 12)'/%3E%3Cpattern id='i' href='%23a' patternTransform='translate(4 0)'/%3E%3Cpattern id='j' href='%23a' patternTransform='translate(12 8)'/%3E%3Cpattern id='k' href='%23a' patternTransform='translate(12 0)'/%3E%3Cpattern id='l' href='%23a' patternTransform='translate(4 8)'/%3E%3Cpattern id='m' href='%23a' patternTransform='translate(0 4)'/%3E%3Cpattern id='n' href='%23a' patternTransform='translate(8 12)'/%3E%3Cpattern id='o' href='%23a' patternTransform='translate(8 4)'/%3E%3Cpattern id='p' href='%23a' patternTransform='translate(0 12)'/%3E%3Crect y='5.88%25' width='100%25' height='100%25' fill='url(%23a)'/%3E%3Crect y='11.76%25' width='100%25' height='100%25' fill='url(%23b)'/%3E%3Crect y='17.65%25' width='100%25' height='100%25' fill='url(%23c)'/%3E%3Crect y='23.53%25' width='100%25' height='100%25' fill='url(%23d)'/%3E%3Crect y='29.41%25' width='100%25' height='100%25' fill='url(%23e)'/%3E%3Crect y='35.29%25' width='100%25' height='100%25' fill='url(%23f)'/%3E%3Crect y='41.18%25' width='100%25' height='100%25' fill='url(%23g)'/%3E%3Crect y='47.06%25' width='100%25' height='100%25' fill='url(%23h)'/%3E%3Crect y='52.94%25' width='100%25' height='100%25' fill='url(%23i)'/%3E%3Crect y='58.82%25' width='100%25' height='100%25' fill='url(%23j)'/%3E%3Crect y='64.71%25' width='100%25' height='100%25' fill='url(%23k)'/%3E%3Crect y='70.59%25' width='100%25' height='100%25' fill='url(%23l)'/%3E%3Crect y='76.47%25' width='100%25' height='100%25' fill='url(%23m)'/%3E%3Crect y='82.35%25' width='100%25' height='100%25' fill='url(%23n)'/%3E%3Crect y='88.24%25' width='100%25' height='100%25' fill='url(%23o)'/%3E%3Crect y='94.12%25' width='100%25' height='100%25' fill='url(%23p)'/%3E%3C/svg%3E");
  --ramp-2: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' shape-rendering='crispEdges'%3E%3Cpattern id='a' width='8' height='8' patternUnits='userSpaceOnUse'%3E%3Crect width='2' height='2'/%3E%3C/pattern%3E%3Cpattern id='b' href='%23a' patternTransform='translate(4 4)'/%3E%3Cpattern id='c' href='%23a' patternTransform='translate(4 0)'/%3E%3Cpattern id='d' href='%23a' patternTransform='translate(0 4)'/%3E%3Cpattern id='e' href='%23a' patternTransform='translate(2 2)'/%3E%3Cpattern id='f' href='%23a' patternTransform='translate(6 6)'/%3E%3Cpattern id='g' href='%23a' patternTransform='translate(6 2)'/%3E%3Cpattern id='h' href='%23a' patternTransform='translate(2 6)'/%3E%3Cpattern id='i' href='%23a' patternTransform='translate(2 0)'/%3E%3Cpattern id='j' href='%23a' patternTransform='translate(6 4)'/%3E%3Cpattern id='k' href='%23a' patternTransform='translate(6 0)'/%3E%3Cpattern id='l' href='%23a' patternTransform='translate(2 4)'/%3E%3Cpattern id='m' href='%23a' patternTransform='translate(0 2)'/%3E%3Cpattern id='n' href='%23a' patternTransform='translate(4 6)'/%3E%3Cpattern id='o' href='%23a' patternTransform='translate(4 2)'/%3E%3Cpattern id='p' href='%23a' patternTransform='translate(0 6)'/%3E%3Crect y='5.88%25' width='100%25' height='100%25' fill='url(%23a)'/%3E%3Crect y='11.76%25' width='100%25' height='100%25' fill='url(%23b)'/%3E%3Crect y='17.65%25' width='100%25' height='100%25' fill='url(%23c)'/%3E%3Crect y='23.53%25' width='100%25' height='100%25' fill='url(%23d)'/%3E%3Crect y='29.41%25' width='100%25' height='100%25' fill='url(%23e)'/%3E%3Crect y='35.29%25' width='100%25' height='100%25' fill='url(%23f)'/%3E%3Crect y='41.18%25' width='100%25' height='100%25' fill='url(%23g)'/%3E%3Crect y='47.06%25' width='100%25' height='100%25' fill='url(%23h)'/%3E%3Crect y='52.94%25' width='100%25' height='100%25' fill='url(%23i)'/%3E%3Crect y='58.82%25' width='100%25' height='100%25' fill='url(%23j)'/%3E%3Crect y='64.71%25' width='100%25' height='100%25' fill='url(%23k)'/%3E%3Crect y='70.59%25' width='100%25' height='100%25' fill='url(%23l)'/%3E%3Crect y='76.47%25' width='100%25' height='100%25' fill='url(%23m)'/%3E%3Crect y='82.35%25' width='100%25' height='100%25' fill='url(%23n)'/%3E%3Crect y='88.24%25' width='100%25' height='100%25' fill='url(%23o)'/%3E%3Crect y='94.12%25' width='100%25' height='100%25' fill='url(%23p)'/%3E%3C/svg%3E");
}

:where(.dither-down, .dither-up) { position: relative; isolation: isolate; }
.dither-down::before, .dither-up::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--dither, var(--bg-3));
  -webkit-mask: var(--ramp) 0 0 / 100% 100% no-repeat;
  mask: var(--ramp) 0 0 / 100% 100% no-repeat;
}
.dither-up::before { transform: scaleY(-1); }
.dither-fade {
  -webkit-mask: var(--ramp) 0 0 / 100% 100% no-repeat, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: var(--ramp) 0 0 / 100% 100% no-repeat exclude, linear-gradient(#000 0 0);
}
.dither-fade-top { -webkit-mask: var(--ramp) 0 0 / 100% 100% no-repeat; mask: var(--ramp) 0 0 / 100% 100% no-repeat; }
.dither-down, .dither-up, .dither-fade, .dither-fade-top { --ramp: var(--ramp-4); }
.dither-fine { --ramp: var(--ramp-2); }

/* ── Header + footer ───────────────────────────────────────────────────────
   common.js header()/footer() build this markup; a page may also write it by
   hand with the same classes. */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--head-h);
  background: var(--bg);
}
/* The header does not end on a line: it dissolves into the page through a
   strip of dither, so content scrolling under it breaks up pixel by pixel. */
.site-header { border-bottom: 1px solid var(--line); }
.site-header .bar {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3vw, 36px);
  height: 100%;
  max-width: calc(var(--wrap) + 2 * var(--gut));
  margin-inline: auto;
  padding-inline: var(--gut);
}
.brand { display: inline-flex; align-items: center; gap: 12px; color: var(--ink); text-decoration: none; }
.brand:hover { color: var(--teal-hi); }
.brand img { width: 26px; height: 26px; image-rendering: pixelated; }
.brand .word { color: inherit; font: 800 20px/1 var(--sans); letter-spacing: -0.03em; }
.status { display: inline-flex; align-items: center; gap: 9px; margin-left: auto; color: var(--dim); font: 12px/1 var(--mono); }
.status i { width: 6px; height: 6px; background: var(--teal); animation: status-blink 2.4s steps(2, jump-none) infinite; }
@keyframes status-blink { 50% { opacity: .25; } }
.site-header .x { color: var(--dim); font: 500 14px/1 var(--sans); text-decoration: none; }
.site-header .x:hover { color: var(--ink); }
.site-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; font-size: 15px; }
.site-nav a { padding: 8px 10px; color: var(--dim); text-decoration: none; }
.site-nav a:hover, .site-nav a[aria-current] { color: var(--ink); }
.site-nav a[aria-current] { text-decoration: underline; text-decoration-color: var(--teal); text-decoration-thickness: 2px; text-underline-offset: 8px; }
.site-nav .sep { color: var(--faint); user-select: none; }
.site-header .btn { margin-left: 8px; }
@media (max-width: 479px) {
  .site-header .x { display: none; }
}

.site-footer { position: relative; isolation: isolate; margin-top: clamp(64px, 10vw, 120px); padding: 56px 0 40px; color: var(--dim); }
.site-footer::before {
  /* the void thickens toward the bottom of the page */
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--bg-2);
  -webkit-mask: var(--ramp-4) 0 0 / 100% 100% no-repeat;
  mask: var(--ramp-4) 0 0 / 100% 100% no-repeat;
}
.site-footer .bar {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 20px 28px;
  max-width: calc(var(--wrap) + 2 * var(--gut));
  margin-inline: auto;
  padding-inline: var(--gut);
}
.site-footer .mark { width: 64px; height: 64px; image-rendering: pixelated; }
.site-footer .say { color: var(--teal); font-size: 28px; }
.site-footer .links { display: flex; gap: 20px; font-size: 15px; }
.site-footer .links a { color: var(--dim); }
.site-footer .links a:hover { color: var(--ink); }
.site-footer .fine { grid-column: 1 / -1; padding-top: 20px; color: var(--faint); font: 13px/1.5 var(--mono); }
.site-footer .trail-line { grid-column: 1 / -1; --trail: var(--line-2); }
@media (max-width: 639px) {
  .site-footer .bar { grid-template-columns: auto 1fr; }
  .site-footer .links { grid-column: 1 / -1; }
}

/* ── Ghosts ────────────────────────────────────────────────────────────────
   ghost.js makes <div class="sprite"> with the 12-frame sheet as background.
   --gpx is the display size (a multiple of 64, so every art pixel stays a
   whole number of CSS pixels) and --frame the frame shown. */

.sprite {
  --gpx: 256px;
  --frame: 0;
  flex: 0 0 auto;
  width: var(--gpx);
  height: var(--gpx);
  background-repeat: no-repeat;
  background-size: calc(12 * var(--gpx)) var(--gpx);
  background-position: calc(-1 * var(--frame) * var(--gpx)) 0;
  image-rendering: pixelated;
}
/* drift(): bob by whole grid pixels. One grid pixel = --gpx / 64. */
.sprite.drift {
  animation: ghost-drift var(--drift-period, 3.2s) steps(var(--drift-n, 1), end) var(--drift-delay, 0s) infinite;
}
.sprite.drift.sway { animation-name: ghost-sway; }
@keyframes ghost-drift {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 calc(var(--gpx) / -64 * var(--drift-n, 1)); }
}
@keyframes ghost-sway {
  0%, 100% { translate: 0 0; }
  50% { translate: calc(var(--gpx) / 64 * var(--drift-n, 1)) 0; }
}

/* ── Utilities ─────────────────────────────────────────────────────────── */

.sr {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.pixelated { image-rendering: pixelated; }
.skip { position: absolute; left: 12px; top: -80px; z-index: 100; }
.skip:focus { top: 12px; }

/* Era accents, for anything tagged data-era="…" */
[data-era="Shielded"]    { --accent: var(--teal); }
[data-era="Sapling"]     { --accent: var(--sapling); }
[data-era="Orchard"]     { --accent: var(--orchard); --accent-2: var(--gold); }
[data-era="Viewing"]     { --accent: var(--indigo); }
[data-era="Turnstile"]   { --accent: var(--leak); }
[data-era="Transparent"] { --accent: var(--exposed); }
[data-era="Legendary"]   { --accent: var(--gold); }

/* ── Motion ────────────────────────────────────────────────────────────────
   Nothing drifts, bobs, pulses or lifts for someone who asked for less
   motion. ghost.js checks the same query and jumps frames instead. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .sprite.drift { animation: none !important; }
  .btn:hover, .btn:hover::after { translate: none; }
}
