/* ==========================================================================
   UZH BCC UI Kit — shared CSS
   ==========================================================================
   Distilled from four real, shipping UZH Blockchain Center interfaces:
     - CryptoCoffee kiosk (full-screen touch payment terminal, dark)
     - Public Goods Game (wallet-connected dApp, light cards)
     - UZHETHs <-> UZHADA Bridge (light bordered form)
     - Prediction-market leaderboard (dense sortable data table)

   The tendency across all four, independent of light/dark: flat colour, no
   gradients except where a real signal (a vote share, a rank) needs one,
   generously-padded blocks that don't reflow around a pointer, big tap
   targets, and layout that stays where you tapped rather than migrating
   under a cursor-only hover.

   Two registers, one token set:
     - "web-app" mode (default): light canvas, white cards — Public Goods
       Game / Bridge / leaderboard's register. What most projects want.
     - "kiosk" mode (`<body class="uzh-kiosk">` or wrap in `.uzh-kiosk`):
       full-screen dark, fixed zones, no scrolling — CryptoCoffee's
       register, for an unattended touchscreen nobody scrolls or hovers.

   Reconciliations made explicit here (don't silently diverge from what's
   already shipping — see each note):

   1. TWO BLUES IN THE WILD. The official UZH CI blue, taken directly from
      three independent live stylesheets (Public Goods Game, the Bridge,
      the leaderboard) is #0028A5. CryptoCoffee's --uzh-blue is #0023aa,
      picked up from a logo file's own background rect, not the CI value.
      This kit's --uzh-blue is the CI value (#0028A5) — use it for any new
      project. CryptoCoffee is NOT retro-fitted to match: its WCAG contrast
      math and shipped, tested code depend on its own exact hex, and the
      difference is imperceptible at a glance. If you ever revisit
      CryptoCoffee's palette on purpose, this is the value to move to.

   2. PRIMARY BUTTON COLOUR DIFFERED BETWEEN THE TWO LIVE SAMPLES: the
      Bridge uses solid --uzh-blue for its primary button; Public Goods
      Game uses a near-black (#2b2b2b) with blue only on hover. This kit
      standardises on blue-solid as the default primary (`.uzh-btn`) — it
      reads as more "branded" and matches the Bridge, the leaderboard's
      accent, and BCC's own blue lockups — and offers the near-black look
      as an explicit opt-in variant (`.uzh-btn--dark`) for a quieter,
      utility-heavy screen (Public Goods Game's admin panel is exactly
      that case). This is a normalisation for FUTURE projects; it does not
      change either live site.

   3. CARD RADIUS/SHADOW: Public Goods Game uses 12px radius +
      `0 2px 10px rgba(0,0,0,.1)`; the Bridge uses 8px + a 2px blue border
      + `0 4px 8px rgba(0,0,0,.1)`. This kit standardises on 10px radius
      and a slightly softer `0 2px 12px rgba(0,0,0,.08)` shadow for the
      plain card, and keeps the bordered treatment as `.uzh-card--outline`
      for the one card on a page that most wants attention (a form about
      to move funds, say).

   4. STAT DISPLAYS come in two real shapes that solve different problems,
      so both are kept rather than merged: Public Goods Game's boxed tile
      grid (a handful of prominent KPIs, `.uzh-stat-grid`) and the
      leaderboard's plain flex row of big numbers (many stats in one
      header strip, `.uzh-stat-row`).

   Font: Source Sans (self-hosted as "Source Sans 3" woff2 — see
   ../fonts/ — falls back to "Source Sans Pro" then system sans, since
   the two live samples use the two different family names Adobe has
   published this typeface under; they are visually the same design).
   ========================================================================== */

@font-face {
  font-family: "Source Sans 3";
  src: url("../fonts/SourceSans3-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("../fonts/SourceSans3-Semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("../fonts/SourceSans3-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* THE MONO COMPANION. Source Code Pro is Source Sans's own monospace
   sibling -- same designer, same skeleton, same SIL OFL 1.1 licence --
   so a hash beside a label looks like the label's relative rather than
   like a different document.

   Self-hosted for exactly the reason the sans faces are: --uzh-mono
   used to be a system-font list ("SF Mono", Menlo, Consolas), which
   renders as three different typefaces on three different machines and
   at three different apparent sizes. A kit that ships its own sans and
   borrows its mono is a kit whose addresses and token names look
   deliberate on the author's laptop and accidental everywhere else.

   Latin subset, 11.6KB each against the sans faces' 155KB: monospace
   here sets addresses, hashes, class names and amounts, none of which
   leave ASCII. Semibold is included because <strong> inside a <code>
   is a real thing in this kit's own documentation. */
@font-face {
  font-family: "Source Code Pro";
  src: url("../fonts/SourceCodePro-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Source Code Pro";
  src: url("../fonts/SourceCodePro-Semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  /* -- brand: the official UZH CI palette, confirmed from three
     independent live stylesheets (Public Goods Game, Bridge, leaderboard)
     — see reconciliation note 1 above re: CryptoCoffee's own --uzh-blue */
  --uzh-blue: #0028A5;
  --uzh-blue-60: #667EC9;   /* leaderboard's tint, for large blue rules/fills at lower emphasis */
  --uzh-blue-20: #DBE4F7;   /* leaderboard's chip background */
  --uzh-cyan: #4AC9E3;
  --uzh-apple: #A5D233;
  --uzh-gold: #FFC845;
  --uzh-orange: #FC4C02;
  --uzh-berry: #BF0D3E;

  /* -- neutrals: --uzh-grey is the PGG/Bridge default for labels and body
     copy; --uzh-ink/--uzh-mut are the leaderboard's sharper pair for
     dense data (headings read darker, meta text reads cooler-grey) — kept
     distinct on purpose, pick the one that matches the surrounding density */
  --uzh-grey: #666666;
  --uzh-ink: #1A1A1A;
  /* #7596A7 measured 3.15:1 on white -- below the 4.5 a body-sized label
     needs, and this token sets most of the muted text there is: field
     labels, balances, table heads, notes. #59747F keeps the blue-grey
     and reads at 4.97. */
  --uzh-mut: #59747F;
  /* #DFE5EA measured ~1.15:1 on white, which this file's own older
     comment called essentially invisible -- and then used it for every
     hairline in the kit. Three separate "the field has no edge until I
     click in it" reports traced back to this one value. */
  --uzh-line: rgba(0, 0, 0, 0.16);
  --uzh-light-grey: #FAFAFA;
  --uzh-bg: var(--uzh-light-grey);

  /* -- semantic (data/status) -- */
  --uzh-pos: #1F7A3D;
  --uzh-neg: var(--uzh-berry);
  /* The sans's own mono sibling, self-hosted beside it. The system-font
     list that used to be here is kept as the fallback, for a page that
     links the CSS without copying fonts/. */
  --uzh-mono: "Source Code Pro", "SF Mono", ui-monospace, Menlo, Consolas, monospace;

  /* -- shape/elevation -- */
  --uzh-radius: 10px;
  --uzh-radius-sm: 6px;
  --uzh-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);

  font-family: "Source Sans 3", "Source Sans Pro", -apple-system, "Segoe UI", Arial, sans-serif;
}

/* ==========================================================================
   Base / page shell (web-app mode — the default)
   ========================================================================== */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--uzh-bg);
  color: var(--uzh-grey);
  font: 15px/1.55 "Source Sans 3", "Source Sans Pro", -apple-system, "Segoe UI", Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
}

.uzh-wrap {
  max-width: 900px;
  margin: 0 auto;
  padding: 2.5rem 1.25rem 5rem;
}

.uzh-wrap--narrow { max-width: 620px; }
.uzh-wrap--wide { max-width: 1180px; }

/* Frame header / footer — a solid-blue, white-on-blue band bookending a
   page (a "frame" in the literal sense: the same device top and bottom),
   for a page that has no toned-card of its own to anchor a strong colour
   moment, and whose plain-white content would otherwise start abruptly
   and trail off into a bare footer with no visual close. Not a
   dark/light toggle context — this is one fixed blue surface, used
   exactly like a toned-card band without a body underneath it. */
.uzh-frame-header {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  background: var(--uzh-blue);
  color: #fff;
  border-radius: var(--uzh-radius);
  padding: 1.75rem 2rem;
  margin-bottom: 1.75rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
}
.uzh-frame-header__coin { flex: none; width: 64px; height: 64px; }
.uzh-frame-header h1 { color: #fff; margin: 0 0 0.35rem; text-align: left; }
.uzh-frame-header p { color: rgba(255, 255, 255, 0.85); margin: 0; max-width: 640px; }
@media (max-width: 560px) {
  .uzh-frame-header { flex-direction: column; text-align: center; }
  .uzh-frame-header h1, .uzh-frame-header p { text-align: center; }
}

/* the footer counterpart: sets --tc-neutral-* locally so a plain
   `.uzh-powered-by` dropped inside it (no --light modifier — the mark is
   already a white cut-out and reads fine straight on blue) picks up a
   readable border/caption colour automatically, via the exact same
   "read the surface you're placed on" mechanism the toned-card body
   uses — see the uzh-bcc-interface-style skill. */
.uzh-frame-footer {
  --tc-neutral-border: rgba(255, 255, 255, 0.28);
  --tc-neutral-dim: rgba(255, 255, 255, 0.78);
  background: var(--uzh-blue);
  border-radius: var(--uzh-radius);
  padding: 1.1rem 2rem 1.4rem;
  margin-top: 2rem;
}
.uzh-frame-footer .uzh-powered-by { margin-top: 0; }

h1, h2, h3 {
  margin: 0;
  color: var(--uzh-blue);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* THE SCALE. h1 is left where it has always been -- seventeen pages
   open with one and it is the size people recognise the site by. The
   intermediate steps are the ones that were uneven: 1.9 / 1.3 / 1.1
   steps down by 1.46 then 1.18, so h2 fell away from the title and then
   h3 barely moved from h2. 1.9 / 1.4 / 1.15 / 0.95 steps by 1.36, 1.22,
   1.21 -- each level clearly below the one above it and no two levels
   nearly the same size, which is the only thing a reader needs from a
   scale. */
h1 { font-size: 1.9rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.15rem; }

p, label { margin: 0.5rem 0; color: var(--uzh-grey); }
.uzh-muted { color: var(--uzh-mut); font-size: 0.9rem; }

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

/* ------------------------------------------------------ base elements --

   THE ELEMENTS A PAGE USES WITHOUT ASKING. h1-h3, p, label and a were
   here from the start; the rest were not, and every project therefore
   got the browser's defaults for them -- which is how thirty-five
   <code> tags across one site ended up rendering in the browser's
   monospace at the browser's size, beside Source Sans 3, looking like
   an accident rather than a decision.

   A catalogue that shows components but not these sends the next person
   off inventing their own heading scale. Both are in demo/components
   now, and the rule is the same for both: if the element is in the
   kit's stylesheet it is in the kit's catalogue.

   Web-app register. Kiosk mode (.uzh-kiosk) resizes type against its
   container and overrides what it needs below. */

/* h1-h3 are the scale. h4 exists because documents have a fourth level
   and an unstyled <h4> lands at the browser's bold 1em, which reads as
   a paragraph that went wrong. Deliberately NOT blue: past h3 a heading
   is structure, not a title. */
/* The fourth level, and quieter than the third in every dimension --
   which the first version was not. It came out UPPERCASE, bold, with
   letter-spacing and a 1.25rem top margin, while h1-h3 carry `margin:
   0` above -- so the smallest heading on the page drew the most
   attention and took the most vertical space. A scale that inverts at
   its last step is not a scale.

   Not blue, because past h3 a heading is structure rather than a title;
   but otherwise it simply continues the sequence. Margin matches its
   siblings: spacing above a heading belongs to the block that owns the
   layout, not to the heading. */
h4 {
  margin: 0;
  color: var(--uzh-ink);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.005em;
}

/* Monospace, from the token that already existed and that nothing
   applied to a bare <code>. The tint is mixed from currentColor rather
   than a fixed grey, so the same rule reads correctly on a white card,
   on a light toned body and on a dark one -- the kit's own "read the
   surface you are placed on" principle, which a hard-coded background
   would break the moment the element moved. */
code, kbd, samp {
  font-family: var(--uzh-mono);
  font-size: 0.92em;
  padding: 0.08em 0.32em;
  border-radius: 4px;
  background: color-mix(in srgb, currentColor 9%, transparent);
}
pre {
  font-family: var(--uzh-mono);
  font-size: 0.86rem;
  line-height: 1.5;
  padding: 0.8rem 0.9rem;
  margin: 0.6rem 0;
  overflow-x: auto;
  border-radius: var(--uzh-radius);
  border: 1px solid var(--tc-neutral-border, var(--uzh-line));
  background: color-mix(in srgb, currentColor 5%, transparent);
}
/* A <code> inside a <pre> is already in a box; it must not draw a
   second one. */
pre code { padding: 0; border-radius: 0; background: none; font-size: inherit; }

ul, ol { margin: 0.5rem 0; padding-left: 1.35rem; color: var(--uzh-grey); }
li + li { margin-top: 0.25rem; }

/* A caption, a footnote, a unit. Muted by default so it does not need a
   class to stop competing with the text it qualifies. */
small { font-size: 0.82em; color: var(--tc-neutral-dim, var(--uzh-mut)); }

/* One hairline, in the same token every other rule in the kit uses for
   a resting border -- translucent, so it reads on any surface. */
hr {
  border: 0;
  border-top: 1px solid var(--tc-neutral-border, var(--uzh-line));
  margin: 1.25rem 0;
}

/* Bold is emphasis, not colour: <strong> inside a coloured banner must
   stay that banner's colour rather than jumping to ink. */
strong { font-weight: 700; color: inherit; }

/* ==========================================================================
   Logobar — UZH + BCC + a partner mark, the leaderboard's header lockup
   ========================================================================== */

.uzh-logobar {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 14px 0 16px;
  flex-wrap: wrap;
}
.uzh-logobar img { display: block; height: 44px; }
.uzh-logobar .uzh-logobar__sep { width: 1px; height: 44px; background: var(--uzh-line); }
.uzh-logobar .uzh-logobar__partner { margin-left: auto; }

/* "powered by BDLT" — CryptoCoffee's own real kiosk footer (a "powered
   by" caption + the bdlt-logo.svg mark). bdlt-logo.svg is a solid-white
   cut-out, so on its own it only reads on a dark surface — a fixed dark
   pill was tried first and rejected: it doesn't adapt, it's dark no
   matter what surface it sits on. This version instead lives INSIDE
   `.uzh-toned-card__body` as its last row, on the exact same surface as
   the rest of the panel, and reads its colour straight from that
   surface's own tokens (--tc-neutral-dim for the caption,
   --tc-neutral-border for the divider) — so it's never styled
   separately from its background, it just inherits whichever one is
   active. The mark itself flips with `.is-light` via `filter:invert()`
   (a solid white cut-out inverts to a solid black one losslessly) —
   genuinely "white on black / black on white," not two fixed looks. */
.uzh-powered-by {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.25rem;
  padding-top: 1rem;
  /* falls back to plain web-app-mode tokens when used outside a
     .uzh-toned-card__body (e.g. as an ordinary page footer) — the
     --tc-neutral-* vars only exist inside a toned card */
  border-top: 1px solid var(--tc-neutral-border, var(--uzh-line));
  color: var(--tc-neutral-dim, var(--uzh-mut));
  font-size: 0.8rem;
}
.uzh-powered-by img { height: 16px; width: auto; display: block; }
.uzh-toned-card__body.is-light .uzh-powered-by img { filter: invert(1); }
/* opt-in for standalone use on an ordinary (always-light) page, where
   there's no .is-dark/.is-light toggle to detect automatically */
.uzh-powered-by--light img { filter: invert(1); }

/* ==========================================================================
   Cards — the one shape every one of the four samples reaches for first
   ========================================================================== */

.uzh-card {
  background: #fff;
  border-radius: var(--uzh-radius);
  box-shadow: var(--uzh-shadow);
  padding: 1.75rem;
  margin-bottom: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.uzh-card--outline {
  border: 2px solid var(--uzh-blue);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.uzh-card.uzh-hidden { display: none; }

.uzh-card > p { margin: 0; }

/* ==========================================================================
   Buttons — flat, full-width by default (a touch target, not a link)
   ========================================================================== */

.uzh-btn {
  display: inline-block;
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  padding: 0.75rem 1.5rem;
  width: 100%;
  background: var(--uzh-blue);
  color: #fff;
  border: none;
  border-radius: var(--uzh-radius-sm);
  cursor: pointer;
  transition: background 0.2s, filter 0.1s;
  box-sizing: border-box;
  text-align: center;
}
.uzh-btn:hover:not(:disabled) { background: var(--uzh-cyan); }
.uzh-btn:active:not(:disabled) { filter: brightness(0.95); }
.uzh-btn:focus-visible {
  outline: 3px solid rgba(0, 40, 165, 0.3);
  outline-offset: 1px;
}
.uzh-btn:disabled { background: #cccccc; cursor: not-allowed; }

/* opt-in near-black variant — see reconciliation note 2 */
.uzh-btn--dark { background: #2b2b2b; }
.uzh-btn--dark:hover:not(:disabled) { background: var(--uzh-blue); }

.uzh-btn--secondary {
  background: transparent;
  color: var(--uzh-blue);
  border: 2px solid var(--uzh-blue);
}
.uzh-btn--secondary:hover:not(:disabled) { background: var(--uzh-light-grey); }

.uzh-btn--auto { width: auto; }
.uzh-btn--small { width: auto; padding: 0.4rem 0.9rem; font-size: 0.85rem; }

.uzh-btn-row {
  display: flex;
  gap: 0.75rem;
  margin-top: 0.25rem;
  flex-wrap: wrap;
}
.uzh-btn-row .uzh-btn { width: auto; flex: 1 1 0; }

@media (max-width: 520px) {
  .uzh-btn-row { flex-direction: column; }
  .uzh-btn-row .uzh-btn { width: 100%; }
}

/* ==========================================================================
   Segmented toggle — the Bridge's direction switch
   ========================================================================== */

.uzh-segmented {
  display: flex;
  justify-content: center;
  gap: 0;
  margin: 1.5rem auto;
  max-width: 480px;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid var(--uzh-blue);
  box-sizing: border-box;
}
.uzh-segmented__btn {
  flex: 1 1 0;
  padding: 0.75rem 1.5rem;
  background: #fff;
  color: var(--uzh-blue);
  border: none;
  cursor: pointer;
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  transition: background 0.2s, color 0.2s;
}
.uzh-segmented__btn.is-active { background: var(--uzh-blue); color: #fff; }
.uzh-segmented__btn:hover:not(.is-active) { background: var(--uzh-light-grey); }

/* ==========================================================================
   Form fields — labelled inputs with validation states (from the Bridge)
   ========================================================================== */

.uzh-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
}
.uzh-field label { font-size: 0.92rem; }

input[type="number"], input[type="text"], input[type="email"], select, textarea {
  font: inherit;
  padding: 0.6rem 0.65rem;
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--uzh-grey);
  border-radius: var(--uzh-radius-sm);
  transition: border-color 0.2s;
}
input.uzh-input--valid { border-color: var(--uzh-pos); }
input.uzh-input--invalid { border-color: var(--uzh-berry); outline: none; }

.uzh-field__error {
  color: var(--uzh-berry);
  font-size: 0.82rem;
  min-height: 1.1em;
  margin: 0;
}
.uzh-field__preview {
  color: var(--uzh-cyan);
  font-size: 0.85rem;
  font-weight: 600;
  min-height: 1.2em;
  margin: 0;
}

.uzh-checkbox-row { display: flex; align-items: center; gap: 0.5rem; }
.uzh-checkbox-row input { width: auto; }

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

/* ==========================================================================
   Status / chips / badges — one family, several real jobs:
     wallet connect state (Bridge), game phase (Public Goods Game),
     address role (leaderboard's "admin"/"oracle" tag)
   ========================================================================== */

.uzh-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
  background: var(--uzh-blue-20);
  color: var(--uzh-blue);
  white-space: nowrap;
}
.uzh-chip--ok { background: #e8f5e9; color: var(--uzh-pos); }
.uzh-chip--warn { background: #fff3e0; color: #bf6000; }
.uzh-chip--bad { background: #fdecef; color: var(--uzh-berry); }
.uzh-chip--neutral { background: var(--uzh-light-grey); color: var(--uzh-grey); border: 1px solid var(--uzh-line); }

.uzh-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; background: var(--uzh-grey); }
.uzh-dot--ok { background: var(--uzh-apple); box-shadow: 0 0 8px rgba(165, 210, 51, 0.6); }
.uzh-dot--warn { background: var(--uzh-gold); }
.uzh-dot--bad { background: var(--uzh-berry); }

/* game/pipeline-phase badges (opaque pill, white text — Public Goods Game) */
.uzh-badge {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  color: #fff;
  background: var(--uzh-grey);
}
.uzh-badge--1 { background: var(--uzh-apple); }
.uzh-badge--2 { background: var(--uzh-cyan); }
.uzh-badge--3 { background: var(--uzh-gold); color: #333; }
.uzh-badge--4 { background: var(--uzh-blue); }
.uzh-badge--5 { background: var(--uzh-orange); }

/* ==========================================================================
   Toast messages — Public Goods Game's animated show/hide
   ========================================================================== */

.uzh-toast-stack { position: relative; display: flex; flex-direction: column; gap: 0.5rem; }
.uzh-toast-stack:empty { margin: 0; padding: 0; height: 0; display: none !important; }

.uzh-toast {
  padding: 1rem;
  border-radius: var(--uzh-radius-sm);
  opacity: 0;
  transform: scale(0.98) translateY(-5px);
  max-height: 100px;
  overflow: hidden;
  box-sizing: border-box;
  transition: opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease, padding 0.3s ease, margin 0.3s ease;
  color: #fff;
  background: var(--uzh-grey);
}
.uzh-toast.is-shown { opacity: 1; transform: scale(1) translateY(0); }
.uzh-toast.is-hidden {
  opacity: 0; transform: scale(0.98) translateY(5px); max-height: 0;
  padding-top: 0; padding-bottom: 0; margin: 0; pointer-events: none;
}
.uzh-toast--success { background: var(--uzh-apple); }
.uzh-toast--error { background: var(--uzh-berry); }
.uzh-toast--info { background: var(--uzh-cyan); }

.uzh-notice {
  padding: 1rem;
  background: #fff3cd;
  border-left: 4px solid var(--uzh-gold);
  border-radius: var(--uzh-radius-sm);
  font-size: 0.9rem;
  color: #665200;
}

/* ==========================================================================
   Stat displays — two real shapes, kept separate on purpose (see note 4)
   ========================================================================== */

.uzh-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1rem;
}
.uzh-stat-grid__item {
  background: var(--uzh-light-grey);
  padding: 1rem;
  border-radius: var(--uzh-radius-sm);
  text-align: center;
}
.uzh-stat-grid__item b { display: block; font-size: 1.4rem; color: var(--uzh-ink); font-variant-numeric: tabular-nums; }
.uzh-stat-grid__item span { font-size: 0.78rem; color: var(--uzh-mut); text-transform: uppercase; letter-spacing: 0.05em; }

.uzh-stat-row {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
  margin: 18px 0 26px;
}
.uzh-stat-row__item b {
  display: block; font-size: 22px; font-weight: 600; color: var(--uzh-ink);
  font-variant-numeric: tabular-nums;
}
.uzh-stat-row__item span { font-size: 12px; color: var(--uzh-mut); text-transform: uppercase; letter-spacing: 0.06em; }

/* opt-in: centre the whole strip and each item's own number/label,
   for a page-level KPI summary rather than a left-anchored header stat */
.uzh-stat-row--center { justify-content: center; }
.uzh-stat-row--center .uzh-stat-row__item { text-align: center; }

/* ==========================================================================
   Data table — sortable header, expandable detail row (the leaderboard)
   ========================================================================== */

.uzh-table {
  width: 100%;
  border-collapse: collapse;
  background: #fff;
  border: 1px solid var(--uzh-line);
  border-radius: var(--uzh-radius);
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
.uzh-table thead th {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--uzh-mut);
  text-align: right;
  padding: 10px 12px;
  border-bottom: 1px solid var(--uzh-line);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: #fff;
}
.uzh-table thead th.l { text-align: left; }
.uzh-table thead th:hover { color: var(--uzh-blue); }
.uzh-table thead th.is-sorted::after { content: " " attr(data-dir); font-size: 9px; }

.uzh-table tr.uzh-row-main td {
  padding: 10px 12px;
  border-top: 1px solid var(--uzh-line);
  text-align: right;
  white-space: nowrap;
}
.uzh-table tr.uzh-row-main td.l { text-align: left; }
.uzh-table tr.uzh-row-main { cursor: pointer; }
.uzh-table tr.uzh-row-main:hover td { background: #fbfcff; }

.uzh-table .uzh-rank { color: var(--uzh-mut); width: 38px; }
.uzh-table .uzh-addr { font-family: var(--uzh-mono); font-size: 13px; }
.uzh-table .uzh-arrow { display: inline-block; transition: transform 0.15s; color: var(--uzh-mut); margin-right: 8px; }
.uzh-table tr.is-open .uzh-arrow { transform: rotate(90deg); }

.uzh-table .pos { color: var(--uzh-pos); }
.uzh-table .neg { color: var(--uzh-neg); }

.uzh-streak { display: inline-flex; gap: 3px; vertical-align: -1px; margin-right: 8px; }
.uzh-streak i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.uzh-streak i.w { background: var(--uzh-pos); }
.uzh-streak i.x { background: var(--uzh-neg); }

/* a small inline proportional bar for a table cell — e.g. a stake-pool's
   saturation relative to the largest pool in the current (filtered) set,
   since the true protocol-wide denominator isn't always known. Give the
   cell a real number too (in a sibling span) — the bar is a same-glance
   comparison aid, never the only place the value is legible. */
.uzh-meter {
  display: inline-block;
  width: 64px;
  height: 8px;
  border-radius: 4px;
  background: var(--uzh-light-grey);
  border: 1px solid var(--uzh-line);
  overflow: hidden;
  vertical-align: middle;
  margin-right: 6px;
}
.uzh-meter > span {
  display: block;
  height: 100%;
  background: var(--uzh-blue);
  border-radius: 4px 0 0 4px;
}

/* Pagination strip for a `.uzh-table` too long to show all at once —
   sits below the table, pairs with a "Showing X–Y of Z" status so the
   count is never ambiguous. Page buttons are plain `.uzh-btn` variants;
   this only supplies the row layout + status typography. */
.uzh-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 1.4rem 0 0;
  flex-wrap: wrap;
}
.uzh-pagination__status {
  font-size: 0.85rem;
  color: var(--uzh-mut);
  font-variant-numeric: tabular-nums;
}

.uzh-table tr.uzh-row-detail { display: none; }
.uzh-table tr.uzh-row-detail.is-shown { display: table-row; }
.uzh-table tr.uzh-row-detail > td {
  padding: 0 12px 16px; background: #fbfcff; border-top: none;
}

@media (max-width: 720px) {
  .uzh-hide-sm { display: none; }
}

/* ==========================================================================
   Toned card + coin pair (web-app mode) — carries the good ideas from the
   kiosk exploration (a WCAG-derived accent colour per entity/direction; a
   calm neutral body with tone surviving as one accent) into an ORDINARY
   scrolling page. Kiosk mode is for the CryptoCoffee machine specifically
   — full-screen, fixed, touch-only, nobody scrolls or hovers. A page that
   "runs in general on a computer browser" (the Bridge, the leaderboard,
   the Public Goods Game) is not a kiosk and should never be refit into
   one; this is the web-app-mode equivalent instead, in normal rem/%
   sizing with no container-query/viewport-simulation machinery, because
   a real browser page needs none of that.

   Usage: wrap in `.uzh-toned-card`, which is what holds the accent custom
   properties (--tc-bg/--tc-ink/--tc-ink-dim/--tc-ink-dimmer). Set them
   THERE (the common ancestor of both the band and the body), not on the
   band alone — the body's one accent (primary button, unit-label colour)
   needs to read them too, and CSS custom properties only cascade to
   descendants of wherever they're declared. Feed them from
   uzh-bcc-ui.js's deriveTone(hex) exactly as kiosk mode does. Give
   `.uzh-toned-card__body` an `.is-dark` or `.is-light` modifier for the
   calm neutral surface underneath the accent — pick per project/page,
   both are real fixed palettes (dark reuses CryptoCoffee's own idle
   screen; light reuses this file's own light-mode tokens), not another
   thing the JS has to compute.
   ========================================================================== */

.uzh-toned-card {
  --tc-bg: var(--uzh-blue);
  --tc-ink: #fff;
  --tc-ink-dim: rgba(255, 255, 255, 0.8);
  --tc-ink-dimmer: rgba(255, 255, 255, 0.62);
  position: relative;
  border-radius: var(--uzh-radius);
  overflow: hidden;
  box-shadow: var(--uzh-shadow);
  margin-bottom: 1.25rem;
}

/* a small sun/moon control for the __body's is-dark/is-light choice,
   sat in the band's own top-right corner rather than as a separate
   labelled row above the card — the icon shown is the CURRENT state. */
.uzh-toned-card__theme-toggle {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.16);
  color: var(--tc-ink);
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.uzh-toned-card__theme-toggle:hover { background: rgba(255, 255, 255, 0.28); }
.uzh-toned-card__theme-toggle:focus-visible { outline: 2px solid var(--tc-ink); outline-offset: 2px; }
.uzh-toned-card__theme-toggle svg { width: 18px; height: 18px; display: none; }
.uzh-toned-card__theme-toggle svg.is-shown { display: block; }

.uzh-toned-card__band {
  background: var(--tc-bg);
  color: var(--tc-ink);
  padding: 2rem 1.75rem 1.75rem;
  text-align: center;
  transition: background 420ms ease, color 420ms ease;
}
.uzh-toned-card__band h1,
.uzh-toned-card__band h2 { color: inherit; margin: 0; }

.uzh-toned-card__rate {
  margin-top: 0.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--tc-ink-dim);
  transition: color 420ms ease;
}

.uzh-toned-card__status {
  margin-top: 0.75rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--tc-ink-dimmer);
  transition: color 420ms ease;
}
.uzh-toned-card__status .uzh-dot { background: currentColor; box-shadow: none; }
.uzh-toned-card__status.is-connected { color: var(--tc-ink-dim); }

/* the coin-pair mark — a smaller, non-kiosk sibling of .uzh-coin-frame;
   same shine-sweep mechanics (see the three gotchas below), sized for a
   card header instead of a fixed screen's 46cqmin. */
.uzh-coin-pair {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}
.uzh-coin-pair .uzh-coin-frame { width: 64px; height: 64px; }
.uzh-coin-pair .uzh-arrow {
  width: 22px;
  height: 22px;
  color: inherit;
  flex-shrink: 0;
  transition: transform 280ms ease;
}
.uzh-coin-pair .uzh-arrow.is-flipped { transform: scaleX(-1); }

/* the calm neutral surface under the band — tone survives only as ONE
   accent within it (primary button fill, active toggle state, the
   unit-label colour), same "one strong colour block reads as the one
   thing to do here" logic as CryptoCoffee's disclaimer bar, inverted. */
.uzh-toned-card__body {
  padding: 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  color: var(--tc-neutral-ink);
  transition: background 420ms ease, color 420ms ease;
}
.uzh-toned-card__body.is-dark {
  background: #17130f;
  --tc-neutral-card: #1f1a15;
  /* a translucent white line reads reliably against any dark fill —
     the old flat #332a20 sat only a hair lighter than the #1f1a15 card
     behind it and was nearly invisible at rest, "becoming visible" only
     once :focus-within swapped it for the tone accent. Rest state now
     gets its own real, always-on line; focus/selected states below
     layer a second, stronger line on top of it rather than being the
     only line that ever showed. */
  --tc-neutral-border: rgba(255, 255, 255, 0.22);
  --tc-neutral-ink: #f3ede4;
  --tc-neutral-dim: #a89a89;
}
.uzh-toned-card__body.is-light {
  background: var(--uzh-light-grey);
  --tc-neutral-card: #fff;
  /* same fix, light side: --uzh-line (#DFE5EA) on a white card is a
     ~1.15:1 contrast ratio — essentially invisible. A translucent black
     line reads at rest regardless of the exact card colour underneath. */
  --tc-neutral-border: rgba(0, 0, 0, 0.18);
  --tc-neutral-ink: var(--uzh-ink);
  --tc-neutral-dim: var(--uzh-mut);
}

.uzh-calm-segmented {
  display: flex;
  border-radius: var(--uzh-radius-sm);
  border: 1px solid var(--tc-neutral-border);
  background: var(--tc-neutral-card);
  overflow: hidden;
}
.uzh-calm-segmented button {
  flex: 1 1 0;
  font: inherit;
  font-weight: 700;
  font-size: 0.92rem;
  padding: 0.75rem 1rem;
  border: none;
  background: transparent;
  color: var(--tc-neutral-dim);
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.uzh-calm-segmented button.is-active { background: var(--tc-bg); color: var(--tc-ink); }
.uzh-calm-segmented button:hover:not(.is-active) { background: var(--tc-neutral-border); }
.uzh-calm-segmented button:focus-visible { outline: 2px solid var(--tc-bg); outline-offset: -2px; }

.uzh-calm-field { display: flex; flex-direction: column; gap: 0.4rem; }
.uzh-calm-field label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--tc-neutral-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.uzh-calm-field input {
  font: inherit;
  font-size: 1rem;
  color: var(--tc-neutral-ink);
  width: 100%;
  box-sizing: border-box;
  padding: 0.7rem 0.8rem;
  border-radius: var(--uzh-radius-sm);
  border: 1px solid var(--tc-neutral-border);
  background: var(--tc-neutral-card);
  transition: border-color 0.2s;
}
.uzh-calm-field input::placeholder { color: var(--tc-neutral-dim); }
/* rest state: the always-on line above. Selected/focused: a visibly
   THICKER line in the tone accent colour — accentuated, not the only
   line that ever shows (box-sizing:border-box means the 1px→2px step
   costs no layout shift). */
.uzh-calm-field input:focus {
  outline: none;
  border-color: var(--tc-bg);
  border-width: 2px;
}
/* Everything written under an amount field, in ONE treatment.

   There were two: the preview line came out semi-bold at 0.85rem with
   no top margin, the static note normal at 0.82rem with a NEGATIVE top
   margin to claw back space. Side by side they read as a heading and a
   caption -- which was never the intent, and on the bridge's two sides
   it made one panel look cramped against the other for no reason a
   reader could name.

   They are the same kind of thing: a sentence explaining the field
   above. So they are the same size, the same weight, the same colour
   and the same distance from what they describe, and a stack of them
   has even spacing. Nothing here is a title; the field's own label is
   the title. */
.uzh-calm-field__preview,
.uzh-calm-field__note {
  font-size: 0.82rem;
  line-height: 1.45;
  font-weight: 400;
  color: var(--tc-neutral-dim, var(--uzh-mut));
  margin: 0.45rem 0 0;
}
/* The preview holds its line even while empty, so the controls below it
   do not jump the moment an amount is typed. */
.uzh-calm-field__preview { min-height: 1.2em; }

/* input + trailing unit badge, "consistent to our previous" — a
   denomination is never just a bare number (echoes CryptoCoffee's own
   .amount-row principle). The unit is the row's one spot of accent
   colour, same role it plays in the kiosk build. */
.uzh-calm-inputgroup {
  display: flex;
  align-items: stretch;
  border-radius: var(--uzh-radius-sm);
  border: 1px solid var(--tc-neutral-border);
  background: var(--tc-neutral-card);
  overflow: hidden;
  transition: border-color 0.2s;
}
.uzh-calm-inputgroup:focus-within { border-color: var(--tc-bg); border-width: 2px; }
.uzh-calm-inputgroup input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: transparent;
  padding: 0.7rem 0.8rem;
  font: inherit;
  font-size: 1rem;
  color: var(--tc-neutral-ink);
}
.uzh-calm-inputgroup input:focus { outline: none; }
.uzh-calm-inputgroup input::placeholder { color: var(--tc-neutral-dim); }
.uzh-calm-inputgroup .uzh-calm-unit {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0 0.9rem;
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--tc-bg);
  border-left: 1px solid var(--tc-neutral-border);
  white-space: nowrap;
}

/* DEPRECATED -- superseded by .uzh-pill-btn (see "Buttons" below).
   This site had two button families, so the same act looked like two
   different kinds of thing depending on which half of it you were in.
   --primary maps to --block, --ghost to --quiet. Kept rather than
   deleted because this kit is vendored into other UZH projects; nothing
   in THIS repo uses it, and nothing new should. */
.uzh-calm-btn {
  display: block;
  width: 100%;
  font: inherit;
  font-weight: 700;
  font-size: 0.95rem;
  padding: 0.8rem 1.2rem;
  border-radius: var(--uzh-radius-sm);
  border: none;
  cursor: pointer;
  text-align: center;
  box-sizing: border-box;
  transition: background 0.2s, color 0.2s, opacity 0.15s;
}
/* the ONE strongly-coloured element in the calm body */
.uzh-calm-btn--primary { background: var(--tc-bg); color: var(--tc-ink); }
.uzh-calm-btn--primary:active:not(:disabled) { opacity: 0.85; }
.uzh-calm-btn--primary:disabled { background: var(--tc-neutral-border); color: var(--tc-neutral-dim); cursor: not-allowed; }
.uzh-calm-btn--ghost {
  background: transparent;
  border: 1px solid var(--tc-neutral-border);
  color: var(--tc-neutral-ink);
}
.uzh-calm-btn--ghost:hover { border-color: var(--tc-bg); }
.uzh-calm-btn--ghost:active { filter: brightness(0.92); }
.uzh-calm-btn:focus-visible { outline: 2px solid var(--tc-bg); outline-offset: 2px; }

.uzh-calm-btn-row { display: flex; gap: 0.75rem; }
.uzh-calm-btn-row .uzh-calm-btn { flex: 1 1 0; }
@media (max-width: 480px) {
  .uzh-calm-btn-row { flex-direction: column; }
}

/* ==========================================================================
   Kiosk mode — a genuinely different context: full-screen, dark, no
   scrolling, nobody hovers. CryptoCoffee-only — the physical coffee
   machine kiosk — NOT for anything that runs in an ordinary browser tab
   (see the toned-card section above for that case instead). Wrap the
   page in `.uzh-kiosk` (or put it on <body>) to switch registers. Ported
   from CryptoCoffee — bring your own per-network/per-entity accent via
   --uzh-kiosk-tone (a single hex the ink/dim-ink/track derive from — see
   uzh-bcc-ui.js's deriveTone()).
   ========================================================================== */

.uzh-kiosk {
  --uzh-kiosk-bg: #17130f;
  --uzh-kiosk-ink: #f3ede4;
  --uzh-kiosk-ink-dim: #a89a89;
  --uzh-kiosk-border: #332a20;
  --uzh-kiosk-tone: #a4d233;   /* the one value a screen usually overrides */

  /* container-relative sizing (cqw/cqh/cqmin below), NOT viewport units —
     on a real kiosk the screen IS the viewport so the two families would
     be identical, but embedded in anything smaller than full-viewport (a
     preview frame, a demo page, an iframe) viewport units read off the
     real browser window instead of this element's own box and everything
     overscales — confirmed the hard way reimagining the Bridge in kiosk
     mode. `container-type: size` makes THIS element the query container.

     AND THIS COMMENT ONCE BROKE THE LINE BELOW IT. It used to write the
     two unit families as `cq` star slash `v` star — a sequence that ENDS
     A CSS COMMENT. Everything after it parsed as CSS, the parser
     discarded one malformed declaration running up to the next
     semicolon, and the declaration it swallowed was `container-type:
     size`. So kiosk mode shipped with no query container at all, and
     every cq unit inside it resolved against the wrong box: precisely
     the failure this comment exists to warn about, caused by the
     comment. Measured in Chromium — `containerType` read `normal`
     against `size` once the sequence was removed. Never write a star
     followed by a slash inside a CSS comment, not even in prose. */
  container-type: size;
  container-name: uzh-kiosk;

  position: fixed;
  inset: 0;
  background: var(--uzh-kiosk-bg);
  color: var(--uzh-kiosk-ink);
  overflow: hidden;
  font: inherit;
  user-select: none;
}

.uzh-kiosk .uzh-screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  pointer-events: none;
  transition: opacity 520ms ease;
}
.uzh-kiosk .uzh-screen.is-active { opacity: 1; pointer-events: auto; }

.uzh-kiosk .uzh-zone-mark {
  flex: 0 0 42%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.uzh-kiosk .uzh-zone-heading {
  flex: 0 0 auto;
  padding: 0 8%;
  text-align: center;
}
.uzh-kiosk .uzh-zone-body {
  flex: 1 1 auto;
  padding: 0 8%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1rem;
  overflow: hidden;
}

/* base coin-frame: shared by kiosk mode (46cqmin, set below) and the
   web-app-mode coin-pair (64px, set in the toned-card section above) —
   only the size differs by context, everything else is one definition. */
.uzh-coin-frame {
  border-radius: 50%;
  position: relative;
  overflow: hidden;
}
.uzh-coin-frame img { width: 100%; height: 100%; display: block; }
.uzh-kiosk .uzh-coin-frame { width: 46cqmin; height: 46cqmin; }

/* the "shine sweep": a narrow diagonal highlight crossing a coin every
   few seconds. It is a TRADEMARK rather than decoration -- one moment
   per page, on the header faces only; UzhShine in js/theme.js owns that
   scope, this rule only knows how the bar is drawn.

   THREE GOTCHAS, each cost a real debugging pass:
   1. Animate via transform, NEVER background-position -- background-
      position silently fails to paint on the compositor thread.
   2. The bar is CLIPPED BY THE FRAME ITSELF. .uzh-coin-frame is already
      border-radius:50% + overflow:hidden, so a rectangle laid over it
      is cut to the disc for free. An earlier version instead shaped a
      full-box white rectangle with the coin's own -mask.svg silhouette,
      which made the SWEEP coin-shaped: a white blob wearing the logo's
      outline, blinking on and off, rather than light crossing a face.
      No mask is involved any more -- and with it goes the old third
      gotcha, that an external-SVG mask renders blank under file://.
   3. The bar must be NARROW and soft-edged. Full-width or hard-edged
      reads as the coin flashing; ~20% of the face with transparent ends
      reads as a raking light passing over metal, which is the effect. */
.uzh-coin-frame::after {
  /* OFF unless switched on. CSS cannot ask "is this variable set", but
     it can use one whose absent value hides the element -- so the sweep
     draws exactly where it was configured and nowhere else. Without
     this gate every 18px coin in a market table would shine too, and a
     trademark repeated forty times on a page is texture. */
  display: var(--uzh-coin-shine, none);
  content: "";
  position: absolute;
  /* overshoot top and bottom so the rotated bar still covers the disc
     at its corners instead of stopping short mid-sweep. */
  top: -30%;
  bottom: -30%;
  left: 0;
  width: 20%;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.72) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  /* 22deg off vertical, i.e. a bar at about 68 degrees -- the angle a
     raking light makes across a coin held upright. */
  transform: translateX(-200%) rotate(22deg);
  animation: uzh-shine 4.5s ease-in-out infinite;
  pointer-events: none;
}
/* Translate percentages are of the BAR's own width (20% of the frame),
   so -200% clears the left edge and 520% clears the right. Most of the
   cycle is spent parked off-frame: the sweep is an event, not a loop
   the eye has to keep watching. */
@keyframes uzh-shine {
  0%, 62%   { transform: translateX(-200%) rotate(22deg); }
  88%, 100% { transform: translateX(520%) rotate(22deg); }
}
@media (prefers-reduced-motion: reduce) {
  .uzh-coin-frame::after { animation: none; display: none; }
}

/* ==================================================================
   Step tracker and status banner
   ==================================================================

   A multi-step operation that the chain runs at its own pace needs the
   same three things everywhere: where you are in the sequence, what is
   happening right now, and the one thing to do next. This is the
   grammar the Optimism withdrawal on bridge.html arrived at, promoted
   here so that the arbitrage panel and anything after it read the same
   way rather than each inventing a dialect.

   Three rules it encodes:

   - The tracker is a PROGRESS BAR: equal columns across the full width,
     so its shape does not depend on how long the step words happen to
     be, and the eye reads distance travelled rather than a row of pills.
   - Shape carries the state and colour only reinforces it: not yet is a
     dashed ring, now is a filled dot with a halo, done is filled flat.
     A page read in greyscale, or by someone who cannot separate red from
     green, still says the same thing.
   - The banner is ONE rectangle of FIXED height whatever the stage.
     Only its tone and LED change. A status box that grows and shrinks
     moves every control under it, which is how a reader ends up clicking
     the button that was there a moment ago (convention 38).

   Markup:
     <ol class="uzh-steps">
       <li class="uzh-step" data-step="1">
         <span class="uzh-step__dot"></span>
         <span class="uzh-step__label">Fund</span></li> ...
     </ol>
     <div class="uzh-status is-wait">
       <span class="uzh-status__led" aria-hidden="true"></span>
       <div class="uzh-status__text">
         <div class="uzh-status__headline">…</div>
         <p class="uzh-status__detail">…</p></div>
     </div>

   Tones: is-wait (something else must happen first), is-ready (it is
   your move), is-error (it will not proceed as asked), is-done. */

.uzh-steps {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 0.45rem;
  margin: 0 0 0.85rem;
  padding: 0;
  list-style: none;
}

.uzh-step {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-width: 0;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--tc-neutral-dim, var(--uzh-mut));
  padding: 0.42rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--tc-neutral-border, var(--uzh-line));
  background: transparent;
}

.uzh-step__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.uzh-step__dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1.5px dashed currentColor;
  background: transparent;
  opacity: 0.55;
  flex: none;
  transition: background 220ms ease, opacity 220ms ease, box-shadow 220ms ease,
    border-color 220ms ease;
}

.uzh-step.is-done {
  color: var(--tc-neutral-ink, var(--uzh-ink));
  border-color: transparent;
  background: rgba(26, 143, 76, 0.12);
}
.uzh-step.is-done .uzh-step__dot {
  background: #1a8f4c;
  border-style: solid;
  border-color: #1a8f4c;
  opacity: 1;
}

.uzh-step.is-current {
  color: var(--tc-neutral-ink, var(--uzh-ink));
  border-color: var(--tc-bg, var(--uzh-blue, #0028A5));
}
.uzh-step.is-current .uzh-step__dot {
  background: var(--tc-bg, var(--uzh-blue, #0028A5));
  border-style: solid;
  border-color: var(--tc-bg, var(--uzh-blue, #0028A5));
  opacity: 1;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tc-bg, #0028A5) 25%, transparent);
}

/* A step this particular run will never reach -- an unwrap on a loop
   that never touches the native coin. Shown rather than hidden, because
   a tracker whose length changes between runs cannot be compared to the
   one the reader saw last time; struck through, because a step that is
   not coming is not the same as one that has not happened yet. */
.uzh-step.is-skipped {
  opacity: 0.45;
  border-style: dashed;
}
.uzh-step.is-skipped .uzh-step__label { text-decoration: line-through; }

.uzh-status {
  --banner-tone: var(--tc-neutral-dim, #8a7d6d);
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 0.7rem;
  width: 100%;
  box-sizing: border-box;
  padding: 0.7rem 0.9rem;
  margin: 0 0 0.9rem;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--banner-tone) 38%, transparent);
  background: color-mix(in srgb, var(--banner-tone) 9%, transparent);
  min-height: 4.6rem;
}

.uzh-status.is-ready { --banner-tone: #1a8f4c; }
.uzh-status.is-done  { --banner-tone: #1a8f4c; }
.uzh-status.is-wait  { --banner-tone: var(--tc-bg, var(--uzh-blue, #0028A5)); }
.uzh-status.is-error { --banner-tone: #c0392b; }
/* Amber: not wrong yet, but longer than it should be. A semaphore with
   only red and green has to decide too early which one a slow thing is,
   and it will be wrong in the direction that teaches people to ignore
   it. */
.uzh-status.is-warn { --banner-tone: #bf6000; }

.uzh-status__led {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  flex: none;
  margin-top: 0.28rem;
  background: var(--banner-tone);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--banner-tone) 22%, transparent);
}

/* Working: the LED breathes, so a transaction in flight is visibly
   different from a stage that is simply waiting on you. */
.uzh-status.is-busy .uzh-status__led { animation: uzh-led-pulse 1.4s ease-in-out infinite; }
@keyframes uzh-led-pulse {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--banner-tone) 22%, transparent); }
  50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--banner-tone) 6%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .uzh-status.is-busy .uzh-status__led { animation: none; }
}

.uzh-status__text { min-width: 0; flex: 1 1 auto; }

.uzh-status__headline {
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--tc-neutral-ink, var(--uzh-ink));
}

.uzh-status__detail {
  color: var(--tc-neutral-dim, var(--uzh-mut));
  font-size: 0.88rem;
  line-height: 1.35;
  margin: 0.15rem 0 0;
  min-height: 2.7em;
}


/* ==================================================================
   The amount field
   ==================================================================

   One way to ask for a quantity of a token, everywhere on the site.

   It came from the AMM trade page, where it earned its shape: a large
   tabular-numeral input, and the token stated as its OWN FACE plus its
   ticker rather than as a word. The coin is what the eye finds first in
   a hurry, and in a place where UZHETHs and wUZHETHs are both spendable
   it is the difference between the two. The wrapper pages had a
   different field with a text unit, which is how a page could say
   "UZHETHs" while meaning the ERC-20.

   The label row is a contract of its own: the label (and anything that
   FILLS the field, like a max or a best-size chip) on the left, and the
   balance pinned to the right edge. Always that way round, so the
   number you are checking against is in the same place on every page
   and never moves when a chip appears beside the label.

       <div class="uzh-amount">
         <div class="uzh-amount__head">
           <span class="chart__label">You pay</span>
           <button class="uzh-pill-btn uzh-pill-btn--tiny">max</button>
           <span class="uzh-amount__bal">balance 12.5</span>   <- pinned right
         </div>
         <div class="uzh-amount__row">
           <input class="uzh-amount__input">
           <span class="uzh-amount__token"><span class="uzh-coin-frame">…</span>UZHETHs</span>
         </div>
       </div>
*/

.uzh-amount { margin-top: 0.9rem; }

/* Label and fill-chips left, the balance hard right.

   `margin-left: auto` on ONE marked child rather than `space-between` on
   the row: space-between spreads three or more children evenly, so the
   balance drifts inward the moment a second chip appears beside the
   label -- which is exactly what happened when "best size" arrived. The
   marker says which child owns the right edge, and the answer is always
   the balance, so it never moves again.

   `__end` is the position; `__bal` is the plain-text balance, which
   takes that position by definition. A balance rendered as a pill
   button keeps its own type styling and just adds `__end`. */
.uzh-amount__head { display: flex; align-items: center; gap: 0.5rem;
  margin-bottom: 0.3rem; flex-wrap: wrap; }
.uzh-amount__end { margin-left: auto; }

/* The caption. It lived in amm.css as `.chart__label` -- a chart axis
   label pressed into service as a field label, which worked only
   because nobody outside the AMM could reach it. Here it is the field's
   own label, so the wrapper pages get the same caption as the trade
   page instead of a plain <label> that looks like neither. */
.uzh-amount__label { font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--tc-neutral-dim, var(--uzh-mut)); margin: 0; }
.uzh-amount__bal { margin-left: auto; font-size: 0.74rem;
  color: var(--tc-neutral-dim, var(--uzh-mut));
  font-variant-numeric: tabular-nums; }

.uzh-amount__row { display: flex; align-items: center; gap: 0.6rem;
  border: 1px solid var(--tc-neutral-border, var(--uzh-line));
  border-radius: 12px; padding: 0.6rem 0.75rem; }
.uzh-amount__row--out { background: color-mix(in srgb, var(--uzh-ink) 4%, transparent); }
.uzh-amount__row:focus-within { border-color: var(--tc-bg, var(--uzh-blue, #0028A5)); }

.uzh-amount__input { flex: 1 1 auto; min-width: 0; font: inherit;
  font-size: 1.35rem; font-weight: 700; font-variant-numeric: tabular-nums;
  border: 0; background: transparent; color: var(--uzh-ink); outline: none; }
:root[data-theme="dark"] .uzh-amount__input { color: #f3ede4; }
.uzh-amount__input--ro { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* A number input's spinners are noise on a token amount. */
.uzh-amount__row input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.uzh-amount__row input[type="number"]::-webkit-outer-spin-button,
.uzh-amount__row input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0; }

.uzh-amount__token { display: inline-flex; align-items: center; gap: 0.4rem;
  font-weight: 700; white-space: nowrap; flex: none; }
.uzh-amount__token .uzh-coin-frame { width: 22px; height: 22px; flex: none; }

/* ==================================================================
   Buttons
   ==================================================================

   ONE family. The site used to have two -- a full-width solid
   `uzh-calm-btn` on the wrapper pages and this pill on the AMM -- which
   meant the same act (send a transaction) looked like two different
   kinds of thing depending on which half of the site you were in.

   Weight is carried by MODIFIERS, not by a second family:

     (plain)     a view, a filter, a tab
     --tiny      an inline chip inside a label row: max, balance, best size
     --primary   the committing action in a group of controls
     --block     the committing action that IS the page: full width, toned

   Colour comes from the ink role rather than a fixed blue, because a
   hard-coded blue disappears on a toned card. */

.uzh-pill-btn {
  font: inherit; font-weight: 700; font-size: 0.75rem; white-space: nowrap;
  padding: 0.25rem 0.75rem; border-radius: 999px;
  border: 1.5px solid var(--tc-neutral-border, var(--uzh-line));
  background: transparent;
  color: var(--uzh-ink); cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
:root[data-theme="dark"] .uzh-pill-btn {
  color: #f3ede4; border-color: rgba(255,255,255,0.22); }
.uzh-pill-btn:hover { border-color: currentColor; }
.uzh-pill-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.uzh-pill-btn[disabled] { opacity: 0.45; cursor: default; }
.uzh-pill-btn.is-active { border-color: currentColor;
  background: color-mix(in srgb, currentColor 14%, transparent); }
.uzh-pill-btn--tiny { font-size: 0.7rem; padding: 0.1rem 0.5rem; }
/* Carries a token face. The coin has to be sized here or it inherits
   whatever the last context set it to, which is how the same pill ended
   up with two different coin sizes on two pages. */
.uzh-pill-btn--coins { display: inline-flex; align-items: center; gap: 0.45rem; }
.uzh-pill-btn--coins .uzh-coin-frame { width: 18px; height: 18px; flex: none; }
/* An aftercare action: big enough to hit on a phone, light enough not to
   compete with the page's own button above it. */
.uzh-pill-btn--coins.uzh-pill-btn--coins { padding: 0.35rem 0.85rem; }
.uzh-pill-btn--tab { font-weight: 700; }

/* The irreversible one looks like it. */
.uzh-pill-btn--primary {
  border-color: var(--uzh-ink);
  background: color-mix(in srgb, var(--uzh-ink) 10%, transparent);
}
.uzh-pill-btn--primary:not([disabled]):hover {
  background: color-mix(in srgb, var(--uzh-ink) 18%, transparent);
}

/* The page's own action: the one thing this page is for. Still a pill --
   same family, same radius, same focus ring -- at the size the act
   deserves, and wearing the product's tone where a card provides one. */
.uzh-pill-btn--block {
  display: block; width: 100%; box-sizing: border-box; text-align: center;
  font-size: 0.95rem; padding: 0.8rem 1.2rem;
  border-color: transparent;
  background: var(--tc-bg, var(--uzh-blue, #0028A5));
  color: var(--tc-ink, #fff);
}
.uzh-pill-btn--block:not([disabled]):hover { filter: brightness(1.06); border-color: transparent; }
.uzh-pill-btn--block:active:not([disabled]) { opacity: 0.85; }
.uzh-pill-btn--block[disabled] {
  background: var(--tc-neutral-border, var(--uzh-line));
  color: var(--tc-neutral-dim, var(--uzh-mut)); opacity: 1; cursor: not-allowed; }

/* A block button that carries a coin. --block sets display:block and
   --coins sets inline-flex, so the two need an explicit answer or the
   later rule silently wins and the face stops aligning with its label. */
.uzh-pill-btn--block.uzh-pill-btn--coins {
  display: flex; align-items: center; justify-content: center;
  gap: 0.6rem; }
.uzh-pill-btn--block.uzh-pill-btn--coins .uzh-coin-frame {
  width: 30px; height: 30px; }

.uzh-pill-btn-row { display: flex; gap: 0.75rem; }
.uzh-pill-btn-row .uzh-pill-btn--block { flex: 1 1 0; }

/* Both halves of the row must own the same box, whichever is which: a
   read-only output and a live input sat a pixel out of line once they
   were side by side -- invisible stacked, obvious in a row. Pinning the
   height on the thing that differs makes the value carry its own box
   either way. */
.uzh-amount__input, .uzh-amount__input--ro { line-height: 1.4; height: 1.9rem; }
.uzh-amount__input--ro { display: block; }

/* This kit styles inputs by ATTRIBUTE selector, which out-specifies a
   bare class -- so `border: 0` above silently never applied and the
   field drew a second box inside the row. Anything living in an amount
   row has to be written this way, or it grows a box of its own. */
.uzh-amount__row .uzh-amount__input,
.uzh-amount__row input[type="text"],
.uzh-amount__row input[type="number"] {
  border: 0;
  padding: 0;
  background: transparent;
}

/* A page action that is NOT the committing one -- "Track", "start a new
   withdrawal instead". Same size and shape as --block so a row of them
   lines up, without the tone that says "this is the act". */
.uzh-pill-btn--quiet {
  background: transparent;
  color: var(--uzh-ink);
  border-color: var(--tc-neutral-border, var(--uzh-line));
}
:root[data-theme="dark"] .uzh-pill-btn--quiet { color: #f3ede4; }
.uzh-pill-btn--quiet:not([disabled]):hover {
  border-color: var(--tc-bg, var(--uzh-blue, #0028A5)); filter: none; }
.uzh-pill-btn--quiet[disabled] { background: transparent; opacity: 0.45; }

/* ==================================================================
   The holdings strip
   ==================================================================

   What you have, and how big the thing you are using is, in one row
   directly above the control that acts on them -- so the numbers you
   need in order to decide are not in a card further down the page you
   have to scroll to and then remember.

   A cell states its quantity the SAME WAY an amount field does, and with
   the same parts: the number, then the token's own face and ticker, as
   one `.uzh-amount__token`. Not a lookalike sized by hand -- the actual
   component, so a change to how this site names a token changes every
   place a token is named. A balance cell and a total-supply cell are
   drawn identically on purpose; what differs is the label, which is the
   honest place for the difference to live.

       <div class="uzh-holdings">
         <div class="uzh-holdings__cell">
           <span class="uzh-holdings__label">Your balance</span>
           <span class="uzh-holdings__value">
             <span class="uzh-holdings__num">12.5</span>
             <span class="uzh-amount__token">
               <span class="uzh-coin-frame">…</span>UZHADA</span>
           </span>
         </div>
         <div class="uzh-holdings__cell uzh-holdings__cell--total"> … </div>
       </div>
*/
/* A FLEX row, not a grid of equal fractions.

   Equal columns are wrong for this: the cells hold quantities of wildly
   different length, so a fraction wide enough for "3'757" is far too
   narrow for "75'299.9 UZHETHw" -- and because the value may not wrap,
   the overflow ran straight across the neighbouring cell. Equally, a
   short value like a supply was handed half the width it had no use
   for.

   Flex lets each cell take exactly the room its content needs and no
   more, and wrap to a second line only when the row genuinely cannot
   hold them. Nothing overlaps, and nothing is padded out with air. */
.uzh-holdings {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem 1.7rem;
  margin: 0 0 0.9rem;
  /* The right gutter is the corner the refresh control sits in: it is
     reserved here rather than left to chance, so a wide value can never
     run underneath the button. */
  padding: 0.55rem 2.6rem 0.55rem 0.75rem;
  position: relative;
  border: 1px solid var(--tc-neutral-border, var(--uzh-line));
  border-radius: 12px;
  background: color-mix(in srgb, var(--tc-neutral-border, #ccc) 12%, transparent);
}
/* Sized by its content: never stretched, never squeezed. */
.uzh-holdings__cell { display: flex; flex-direction: column; gap: 0.1rem;
  flex: 0 0 auto; min-width: 0; }

/* The same label treatment the amount fields use above their own rows,
   so a caption is a caption everywhere on the site. */
.uzh-holdings__label { font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--tc-neutral-dim, var(--uzh-mut)); }
/* One small link, in the caption. It replaces a sentence under the
   strip: the link is the relationship, and it costs a word rather than
   a line of its own. */
.uzh-holdings__link { font-weight: 700; text-transform: none;
  letter-spacing: 0; white-space: nowrap; }

/* The number and the token it counts are ONE statement and stay on one
   line. Letting them wrap produced a cell two rows tall with the ticker
   stranded underneath, which reads worse than either. The number is
   already short by construction -- six significant figures, truncated
   (UzhNum.token) -- so there is nothing to wrap for. Only a phone-width
   display is allowed to break the rule. */
.uzh-holdings__value { display: flex; align-items: baseline; gap: 0.4rem;
  min-width: 0; flex-wrap: nowrap; white-space: nowrap; }
/* Tabular, bold, and a step below the amount field's 1.35rem: this is a
   figure you read, not one you type into. */
.uzh-holdings__num { font-size: 1.05rem; font-weight: 700;
  font-variant-numeric: tabular-nums; }
@media (max-width: 380px) {
  .uzh-holdings__value { flex-wrap: wrap; white-space: normal; }
}
/* The token tag rides at the strip's own scale rather than the amount
   field's, since the number beside it is smaller here. */
.uzh-holdings__value .uzh-amount__token { font-size: 0.85rem; align-self: center; }
.uzh-holdings__value .uzh-coin-frame { width: 20px; height: 20px; }

/* The wrapper's own size, as opposed to yours: separated by a rule
   rather than by colour, because colour here would read as a warning. */
.uzh-holdings__cell--total {
  border-left: 1px solid var(--tc-neutral-border, var(--uzh-line));
  padding-left: 0.9rem; }
@media (max-width: 620px) {
  .uzh-holdings__cell--total { border-left: 0; padding-left: 0;
    border-top: 1px solid var(--tc-neutral-border, var(--uzh-line));
    padding-top: 0.55rem; }
}
/* An optional note, on its own full-width line under the numbers. */
.uzh-holdings__foot { flex: 1 1 100%; display: flex;
  align-items: center; gap: 0.6rem; }
.uzh-holdings__note { margin: 0; font-size: 0.72rem; flex: 1 1 auto;
  min-width: 0; color: var(--tc-neutral-dim, var(--uzh-mut)); }

/* Anything that acts on these numbers, in the box's top-right corner.
   It used to share the strip's last line with the note -- which was
   right while there WAS a note. Once the notes came out, that line held
   one 26px button and a row of empty space: a whole row of height for a
   control that only ever needed a corner. Absolute, against the gutter
   reserved above, so it costs no height at all and sits where a
   refresh control is looked for. */
.uzh-holdings__actions { position: absolute; top: 0.4rem; right: 0.45rem;
  display: flex; align-items: center; gap: 0.4rem; }
/* Smaller here than in a page header: it is an addition to a box, not a
   control in its own right. */
.uzh-holdings__actions .uzh-icon-btn { width: 26px; height: 26px; }

/* ==================================================================
   Disclosure
   ==================================================================

   Reference material that is worth having and not worth reading every
   visit -- how a wrapper works, what a delay is for. It sat open at the
   foot of every page, so the page ended in three paragraphs nobody was
   reading on their second visit, and the controls above them looked
   like a smaller part of the page than they are.

   A plain <details>, styled: no JavaScript, keyboard-operable for free,
   and findable by the browser's own in-page search even while closed in
   the browsers that support that. */
.uzh-disclosure {
  border: 1px solid var(--tc-neutral-border, var(--uzh-line));
  border-radius: 12px;
  padding: 0 0.9rem;
  margin-top: 1.25rem;
  background: transparent;
}
.uzh-disclosure > summary {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.75rem 0;
  font-weight: 700;
  font-size: 0.9rem;
  cursor: pointer;
  list-style: none;
  color: var(--tc-neutral-ink, var(--uzh-ink));
}
.uzh-disclosure > summary::-webkit-details-marker { display: none; }
.uzh-disclosure > summary:focus-visible {
  outline: 2px solid var(--tc-bg, var(--uzh-blue, #0028A5));
  outline-offset: 2px; border-radius: 8px; }
/* The chevron turns to point down when the section is open, so the mark
   says which way the content will move rather than merely that it can. */
.uzh-disclosure__chevron {
  width: 16px; height: 16px; flex: none;
  transition: transform 180ms ease;
  color: var(--tc-neutral-dim, var(--uzh-mut));
}
.uzh-disclosure[open] > summary .uzh-disclosure__chevron { transform: rotate(180deg); }
.uzh-disclosure__body { padding: 0 0 0.9rem; }
.uzh-disclosure__body > :first-child { margin-top: 0; }
.uzh-disclosure__body > :last-child { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) {
  .uzh-disclosure__chevron { transition: none; }
}

/* A transfer in flight: the step tracker and the status banner together,
   under the control that started it. Same two components the Optimism
   withdrawal uses, so a transfer looks the same wherever it is. */
.uzh-progress { margin-top: 1rem; }
.uzh-progress__actions { display: flex; justify-content: flex-end; }
/* A tracker step that needs a transaction from the holder: the button
   is the act, so it spans the box like every other act on the site
   (.uzh-pill-btn--block), instead of hiding in the corner where the
   Dismiss control lives. */
.uzh-progress__actions--wide {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
/* Anything that is not the act keeps its own size and its corner, even
   when it shares the column with a full-width button. */
.uzh-progress__actions--wide .uzh-pill-btn--tiny { align-self: flex-end; }


/* ==================================================================
   THE THEME, AND THE COMPONENTS THAT ARRIVED AFTER THE FIRST DRAFT
   ==================================================================

   Everything below was written downstream, in the wrapped-token site's
   own app.css, and has been moved here because none of it was ever
   about that site. Two kinds of thing:

   - CORRECTIONS. A card with no edge, a table rule at 1.15:1, a muted
     ink at 3.15:1, a field border that only appears once you are
     already typing in it, `[hidden]` losing to any rule that sets a
     display. Each was found on a real page, measured, and patched in
     the project -- which fixed one site and left the kit carrying the
     fault for the next one.

   - THE DARK THEME, and the components that had no dark answer. A kit
     whose light mode is the only mode is a kit that every project has
     to re-theme by hand, and they will not agree.

   The rule the whole section encodes: if a project has to patch the
   kit to make a kit component correct, the patch belongs in the kit.
   ================================================================== */

/* Layered on top of vendor/uzh_bcc_ui_kit/css/uzh-bcc-ui.css — this file only
   adds what the kit doesn't already provide: the four-tile landing grid,
   the site-wide dark/light theme, and a couple of wrapper-page-specific
   tweaks. */

/* ---------------------------------------------------------------------
   Site-wide dark / light theme. The kit's own palette (:root) is the
   light theme; this only redefines the handful of tokens + surfaces our
   pages actually use once data-theme="dark" is set on <html> by
   js/theme.js. The blue/black "frame" header and footer are deliberately
   left alone in both themes — they're a fixed brand surface, not part of
   the toggle (see the kit's own comment on .uzh-frame-header).
   --------------------------------------------------------------------- */
/* THE WRAPPER CARD'S BODY AT NIGHT.
   ------------------------------------------------------------------
   Every wrapper and bridge page writes `.uzh-toned-card__body is-light`
   into its markup, and `is-light` had no dark-mode answer -- so in dark
   mode the main panel of the site's main pages stayed rgb(250,250,250)
   with near-black text, a white slab on a black page. Reported on the
   gallery, but the gallery was being faithful: the real pages do it too.

   The kit already HAS a dark body -- `.uzh-toned-card__body` without
   `is-light` -- with its own --tc-* tokens. The pages simply chose the
   light one in HTML, where a media query cannot reach. So dark mode
   makes `is-light` adopt the dark treatment rather than every page
   being edited to swap a class it cannot swap at runtime.

   The tokens flip with it, which is the point: everything inside --
   the amount rows, the inputgroup borders, the segmented control --
   draws from --tc-neutral-*, so they all follow without a rule each.
   The band above keeps the product's own tone, because that is the
   page's identity and does not belong to the theme.
   ------------------------------------------------------------------ */
:root[data-theme="dark"] .uzh-toned-card__body.is-light {
  background: #17130f;
  color: #f3ede4;
  --tc-neutral-card: #1e1a16;
  --tc-neutral-border: rgba(255, 255, 255, 0.22);
  --tc-neutral-ink: #f3ede4;
  --tc-neutral-dim: #a89a89;
}

/* A CARD MUST HAVE AN EDGE.
   ------------------------------------------------------------------
   The kit gives .uzh-card a shadow and no border. On a white page a
   soft shadow is nearly nothing, so a card read as a region of text
   rather than as a container -- and where two cards sat next to each
   other you could not see where one stopped. Reported 2026-09-22.

   A hairline, not a rule: the shadow still does the lifting, this only
   closes the shape. .uzh-card--outline keeps its own stronger border
   and is unaffected.
   ------------------------------------------------------------------ */
.uzh-card { border: 1px solid rgba(0, 0, 0, 0.16); }
:root[data-theme="dark"] .uzh-card { border-color: rgba(255, 255, 255, 0.16); }

/* THE TABLE NEEDS A GUIDE FOR THE EYE.
   ------------------------------------------------------------------
   The kit rules its rows with --uzh-line (#DFE5EA), the same colour its
   own comment measures at ~1.15:1 on white and calls invisible, so a
   long table was a field of numbers with nothing to follow across or
   down. Three fixes, all subtle on purpose -- a table that shouts its
   grid is harder to read, not easier:

   1. a readable row rule;
   2. zebra striping, which is what actually carries the eye ACROSS a
      row on a wide table;
   3. a stronger line under the head, so the labels separate from the
      data instead of floating above it.

   Columns get no vertical rules. Names are left, numbers are right and
   tabular, and that alignment already separates them; vertical lines
   on top of it read as a spreadsheet.
   ------------------------------------------------------------------ */
.uzh-table tr.uzh-row-main td { border-top-color: rgba(0, 0, 0, 0.12); }
.uzh-table thead th { border-bottom: 2px solid rgba(0, 0, 0, 0.18); }
.uzh-table tbody tr.uzh-row-main:nth-of-type(even) td {
  background: rgba(0, 0, 0, 0.025);
}
:root[data-theme="dark"] .uzh-table tr.uzh-row-main td {
  border-top-color: rgba(255, 255, 255, 0.14);
}
:root[data-theme="dark"] .uzh-table thead th {
  border-bottom-color: rgba(255, 255, 255, 0.24);
}
:root[data-theme="dark"] .uzh-table tbody tr.uzh-row-main:nth-of-type(even) td {
  background: rgba(255, 255, 255, 0.035);
}

/* A FIELD YOU CANNOT TYPE IN MUST NOT LOOK LIKE ONE.
   ------------------------------------------------------------------
   The kit tints the derived row with 4% of --uzh-ink. Four percent is
   not a tint, it is a rounding error: the row looked exactly like the
   one above it, which invites a cursor into a field that is computed.
   And on a dark card a 4% BLACK tint is darker than nothing at all,
   so at night it did not exist. Strong enough to read as filled, light
   enough not to look disabled.
   ------------------------------------------------------------------ */
.uzh-amount__row--out { background: rgba(0, 0, 0, 0.09); }
:root[data-theme="dark"] .uzh-amount__row--out { background: rgba(255, 255, 255, 0.10); }
/* And the cursor, which was the louder signal all along: a readonly
   input still shows an I-beam, so the field SAID "type here" while the
   tint whispered otherwise. The tint says computed; the cursor must not
   contradict it. */
.uzh-amount__row--out,
.uzh-amount__row--out .uzh-amount__input { cursor: default; }

/* ONE RADIUS FOR A FIELD.
   ------------------------------------------------------------------
   Two input surfaces disagreed: .uzh-amount__row hardcoded 12px while
   .uzh-calm-inputgroup used --uzh-radius-sm (6px). Nobody chose that;
   one was written with a token and the other with a number, months
   apart. Claudio noticed the moment both were on one page, which is the
   gallery earning its keep. A token now, used by both.
   ------------------------------------------------------------------ */
:root { --uzh-radius-field: 12px; }
.uzh-amount__row { border-radius: var(--uzh-radius-field); }
.uzh-calm-inputgroup { border-radius: var(--uzh-radius-field); }

/* THREE TOKENS, EIGHT REPORTS.
   ------------------------------------------------------------------
   A contrast pass over the gallery in both themes turned eight separate
   complaints into three faults, all at token level. That is the gallery
   paying for itself: every component on one page, measurable in one go.

   1. --uzh-ink IS NOT REDEFINED FOR DARK MODE. The kit sets #1A1A1A and
      the dark block never touched it, so everything coloured with the
      ink token -- or inheriting it through currentColor -- stayed
      near-black on a near-black surface. Measured at 1.06:1: the status
      headline, the step labels, the disclosure summary, the step dots'
      dashed rings. Not eight bugs, one missing line.

   2. --uzh-mut WAS 3.15:1 ON WHITE, below the 4.5 a body-sized label
      needs. That is most muted text on the site: field labels, balances,
      table heads, notes. #59747F keeps the blue-grey and reads at 4.97.

   3. --uzh-line WAS #DFE5EA, ~1.15:1 on white, which is the colour the
      kit's own comment calls essentially invisible. Everything that
      draws a hairline with it -- the picker's trigger, the outline card,
      the inputgroup -- had no visible edge at rest. Reported three
      separate times before it was traced here.

   --tc-neutral-ink joins --tc-neutral-border at the root for the same
   reason as before: a token meant to be overridden per surface must not
   degrade into something unreadable when nothing overrides it.
   ------------------------------------------------------------------ */
:root {
  /* --uzh-mut and --uzh-line are corrected where they are DECLARED, at
     the top of this file; a token whose real value is set 1800 lines
     below its declaration is a token nobody can read off the source.
     Only the third is genuinely an addition: --tc-neutral-ink joins
     --tc-neutral-border at the root because a token meant to be
     overridden per surface must not degrade into something unreadable
     when nothing overrides it. */
  --tc-neutral-ink: var(--uzh-ink);
}

/* WHAT THE TOKEN PASS COULD NOT REACH: four hardcoded light-mode
   values, each measured on the gallery in dark mode.
   ------------------------------------------------------------------
   A token fixes everything that USES the token. These four wrote their
   colour as a literal, so they kept it when the surface flipped.
   ------------------------------------------------------------------ */

/* The health page's figures sat on a hardcoded rgb(250,250,250) panel,
   so in dark mode they were light text on a white slab -- reported as
   "stat-grid dark mode always white background". One step lighter than
   the card, per convention 60, not one step darker. */
:root[data-theme="dark"] .uzh-stat-grid__item { background: #1e1a16; }

/* A FIELD MUST LOOK LIKE A FIELD BEFORE IT IS TOUCHED.
   ------------------------------------------------------------------
   The kit borders its input pill with
   `var(--tc-neutral-border, var(--uzh-line))`, and it defines
   --tc-neutral-border ONLY inside .uzh-toned-card__body. Every wrapper
   page has one of those; no AMM page does. So on the AMM the fallback
   applied -- #DFE5EA, which the kit's own comment measures at ~1.15:1
   on a white card and calls essentially invisible -- and the fields
   appeared only on :focus-within, when the tone accent replaced the
   line. A box you cannot see until you are already typing in it is not
   a box.

   The kit had this exact fault and fixed it for toned cards, which is
   where it was noticed. The fallback is the bug: a token meant to be
   overridden per surface should not degrade to something unreadable
   when it is not. So it gets an always-on value at the root, and toned
   cards go on overriding it as they already do.

   Not --tc-neutral-card alongside it: that one controls FILLS, and
   defining it globally would give a background to components that
   currently have none. Only the line that was missing.
   ------------------------------------------------------------------ */
:root { --tc-neutral-border: rgba(0, 0, 0, 0.18); }

:root[data-theme="dark"] {
  --tc-neutral-border: rgba(255, 255, 255, 0.22);
  /* The one that was missing. Everything using the ink token, or
     inheriting it through currentColor, was near-black on black. */
  --uzh-ink: #f3ede4;
  --tc-neutral-ink: #f3ede4;
  --uzh-bg: #0c0c0c;
  --uzh-grey: #c7c7c7;
  --uzh-mut: #9aa7ad;
  --uzh-line: rgba(255, 255, 255, 0.22);
}
:root[data-theme="dark"] body { background: var(--uzh-bg); color: var(--uzh-grey); }
:root[data-theme="dark"] h1,
:root[data-theme="dark"] h2,
:root[data-theme="dark"] h3 { color: #f3ede4; }

/* ...but NOT inside a toned band. The band paints its own background from
   --tc-bg and derives its ink from that tone's luminance, so on a light
   tone (#a3ddec, #f7931a) the correct text is black in BOTH themes. The
   blanket rule above has the same specificity as the kit's
   `.uzh-toned-card__band h1 { color: inherit }` and, loading later, was
   winning -- so switching to dark mode flipped the ticker line to #f3ede4
   while the wallet row in the same band, which is not a heading, stayed
   dark. That is the whole bug: a heading rule reaching into a surface
   that is not the page background. Theme decides the page's ink; a toned
   band's ink comes from its tone. */
:root[data-theme="dark"] .uzh-toned-card__band h1,
:root[data-theme="dark"] .uzh-toned-card__band h2,
:root[data-theme="dark"] .uzh-toned-card__band h3 { color: inherit; }
:root[data-theme="dark"] a { color: #7ea1ff; }
:root[data-theme="dark"] .uzh-card {
  background: #17130f;
  color: #f3ede4;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}
:root[data-theme="dark"] .uzh-card--outline { border-color: var(--uzh-blue-60); }

/* The kit's .uzh-table is a light-only component: it paints `background:
   #fff` on the table, again on every `thead th`, and `#fbfcff` on the
   hover and detail rows. Inside a dark .uzh-card that is a white slab,
   and because the card sets a light `color` the text on it was light-on
   -white -- the Reserves tables were legible in day mode and blank in
   night mode. The kit is only used for tables on this one page, so the
   dark skin belongs here beside the site's other dark surfaces rather
   than as a fork of the vendored kit.

   The table sits one step LIGHTER than the card it is in, not darker: it
   is a panel on a surface, and on a near-black card a darker panel reads
   as a hole. */
:root[data-theme="dark"] .uzh-table,
:root[data-theme="dark"] .uzh-table thead th {
  background: #1e1a16;
  color: inherit;
}
:root[data-theme="dark"] .uzh-table thead th { color: var(--uzh-mut); }
:root[data-theme="dark"] .uzh-table thead th:hover { color: #9db8ff; }
:root[data-theme="dark"] .uzh-table tr.uzh-row-main:hover td,
:root[data-theme="dark"] .uzh-table tr.uzh-row-detail > td { background: #26211c; }

/* Paint the html element itself, not just body — so a page shorter than
   the viewport, or the gutter beside a centred .uzh-wrap on a wide window,
   never shows through as the browser's default white regardless of
   theme. Reads the same --uzh-bg the dark-mode block above redefines, so
   one rule covers both themes. */
html { background: var(--uzh-bg); min-height: 100%; }

/* ------------------------------------------------------------------ */
/* [hidden] must actually hide.                                        */
/*                                                                     */
/* The browser's own `[hidden] { display: none }` is the weakest rule  */
/* there is, so ANY component rule that sets a display -- .uzh-card    */
/* (flex), .uzh-pill-btn, .uzh-progress__actions (flex) -- silently    */
/* beats it, and an element the JS "hid" stays on screen at full       */
/* height. That is how #balances-card sat there as a 219px empty card  */
/* on every wrapper page while hidden === true, and how the old        */
/* withdrawal id rows showed an em-dash for values that did not exist  */
/* yet.                                                                */
/*                                                                     */
/* One !important rule ends the whole class of bug. Nothing in this    */
/* frontend sets style.display, so there is no legitimate way to show  */
/* an element that still carries the attribute: JS removes `hidden`.   */
/* ------------------------------------------------------------------ */
[hidden] { display: none !important; }

/* ------------------------------------------------------------------ */
/* One header box on every page.                                        */
/*                                                                      */
/* The two header designs stay as they are -- the wrapper pages keep    */
/* their coloured kiosk band, the others keep .uzh-frame-header -- but  */
/* the BOX they occupy is now identical everywhere, so moving between   */
/* pages does not move the fold. Width comes from every page using      */
/* .uzh-wrap--wide; height comes from the one value below, which is     */
/* the tallest header's natural height (a wrapper band: coins, ticker   */
/* line, wallet rows). Shorter headers centre their content in it       */
/* rather than shrinking, so the internal content can differ freely     */
/* while the frame does not.                                            */
/* ------------------------------------------------------------------ */
:root { --uzh-head-h: 227px; }

.uzh-frame-header,
.uzh-toned-card__band {
  min-height: var(--uzh-head-h);
  box-sizing: border-box;
}

/* .uzh-frame-header is already a centred flex row. The band is a block
   with centred text, so it needs to become a column that centres what it
   holds -- otherwise a short band would pin its content to the top of
   the taller box. */
.uzh-toned-card__band {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The kiosk form was drawn for an 860px column; at 1140 its fields would
   stretch into unusable letterboxes. The band and the page frame are what
   widen -- the controls keep a readable measure, centred. */
.uzh-toned-card__body > * {
  /* width:100% matters. The body is a flex column, and `margin-inline:
     auto` alone cancels the default stretch, leaving each child sized to
     its own content -- which is why the Wrap/Unwrap (Deposit/Withdraw)
     toggle came out 151px wide while the fields under it were 704. With
     an explicit width every block in the card measures the same. */
  width: 100%;
  max-width: 44rem;
  margin-left: auto;
  margin-right: auto;
}

.uzh-theme-toggle {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 100;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--uzh-line);
  background: #fff;
  color: var(--uzh-blue);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--uzh-shadow);
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.uzh-theme-toggle:hover { filter: brightness(0.95); }
.uzh-theme-toggle:focus-visible { outline: 2px solid var(--uzh-blue); outline-offset: 2px; }
.uzh-theme-toggle svg { width: 20px; height: 20px; display: none; }
.uzh-theme-toggle svg.is-shown { display: block; }
:root[data-theme="dark"] .uzh-theme-toggle {
  background: #17130f;
  color: #f3ede4;
  border-color: rgba(255, 255, 255, 0.22);
}

/* Header variant for the landing page: a large BCC coin mark, but on a
   plain surface that matches the page instead of a fixed colour block
   (2026-09-15 -- this used to be permanently solid black regardless of
   the site's own light/dark toggle). Background and text now track the
   theme the same way every other heading on the page already does; only
   the coin mark is left alone (see the comment below it), since it's
   already a self-contained black-backed, white-lineart badge that reads
   the same on either surface without any help. */
.uzh-frame-header--dark {
  background: transparent;
  border-color: var(--uzh-line);
  color: var(--uzh-ink);
}
.uzh-frame-header--dark h1 { color: inherit; }
.uzh-frame-header--dark p { color: var(--uzh-mut); }
:root[data-theme="dark"] .uzh-frame-header--dark { color: #f3ede4; }
.uzh-frame-header__coin--lg { width: 96px; height: 96px; }
@media (max-width: 560px) {
  .uzh-frame-header__coin--lg { width: 72px; height: 72px; }
}

.uzh-form { display: none; flex-direction: column; gap: 1.1rem; }
.uzh-form.is-active { display: flex; }
/* The spacing between a field and the button that acts on it comes from
   that column gap -- so it only reaches the children of a .uzh-form.
   The withdraw side of the bridge has one level more (a start state and
   a tracking state inside its form), and its button therefore sat flush
   against the amount field while the deposit side, one level shallower,
   was spaced. A stack inside a form is still a stack. */
.uzh-form-stack { display: flex; flex-direction: column; gap: 1.1rem; }

/* The note's typography lives in the kit now, with the preview line it
   sits beside: two rules for one kind of text is how they came to
   disagree about weight and spacing. */


/* Connect button, relocated (2026-09-15) from the button row down in the
   body up next to the connection status itself, in the band -- it's about
   the connection state shown right there, not another form action mixed
   in with Submit/Add-token. A small pill matching the theme-toggle's own
   translucent-on-accent treatment, since it lives on the coloured band
   surface, not the neutral body below it. Sibling of #status, not nested
   inside it: setStatus() replaces #status's innerHTML wholesale on every
   connect, which would otherwise wipe this button out and detach its
   listener. */
.uzh-toned-card__connect-btn {
  font: inherit;
  font-weight: 700;
  font-size: 0.78rem;
  margin-left: 0.5rem;
  padding: 0.3rem 0.8rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.16);
  color: var(--tc-ink);
  cursor: pointer;
  transition: background 0.2s ease, opacity 0.15s ease;
}
.uzh-toned-card__connect-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.28); }
.uzh-toned-card__connect-btn:focus-visible { outline: 2px solid var(--tc-ink); outline-offset: 2px; }
.uzh-toned-card__connect-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: transparent;
}

/* Add-token reuses --ghost but the kit never gave that variant a disabled
   look (only --primary has one) -- needed now that it's disabled until a
   wallet is connected, same as Submit. */
.uzh-pill-btn--quiet:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  color: var(--tc-neutral-dim);
}
.uzh-pill-btn--quiet:disabled:hover { border-color: var(--tc-neutral-border); }

/* Footer: was a solid-blue block (the kit's default .uzh-frame-footer).
   Now it's just the page's own background with a single separating line
   -- .uzh-powered-by already draws that line itself (border-top, reading
   --tc-neutral-border/--tc-neutral-dim with a plain-page fallback of
   --uzh-line/--uzh-mut), so the wrapper only needs to feed it colour
   tokens that make sense off a blue surface and get out of the way
   layout-wise. Net effect: no colour block, one thin theme-aware line,
   less vertical space than the old padded block. */
.uzh-frame-footer {
  --tc-neutral-border: var(--uzh-line);
  --tc-neutral-dim: var(--uzh-mut);
  background: transparent;
  border-radius: 0;
  padding: 0;
  margin-top: 1.5rem;
}

/* The refresh status line -- one rule for balance.html and health.html so
   the two panels cannot drift apart in size, colour or alignment. Fixed
   min-width and nowrap keep the round button from shifting sideways as
   "updating…" becomes a timestamp. The wording itself lives in
   js/theme.js (UzhRefresh). */
.uzh-refresh-note {
  font-size: 0.78rem;
  color: var(--uzh-mut);
  flex: 0 0 auto;
  min-width: 14rem;
  text-align: right;
  white-space: nowrap;
}
/* The balances card's refresh control is the same .uzh-icon-btn as every
   other one on the site; only its size differs, because it sits beside an
   h3 rather than in a toolbar. The old .balance-refresh-btn was a fifth
   variant with its own size, its own white background (which stayed white
   in dark mode) and its own blue glyph. */
.uzh-icon-btn.is-spinning svg { animation: uzh-icon-spin 0.6s linear; }
@keyframes uzh-icon-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* "Wrappers / Networks" nav menu (2026-09-17, replacing the earlier
   in-grid tile and standalone-page link, both of which read as too
   heavy / out of place) -- deliberately styled identically to
   .uzh-theme-toggle (same 40px circle, 1px --uzh-line border, plain
   white/dark fill, no bold colour) rather than the filled-tone circle
   tried for the removed tile, since a solid accent-coloured disc was
   the specific thing that looked wrong there. Sits top-left, mirroring
   the theme toggle's own top-right position. */
.uzh-nav-menu { position: fixed; top: 1rem; left: 1rem; z-index: 100; }
.uzh-nav-menu__toggle,
.uzh-nav-menu__back {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--uzh-line);
  background: #fff;
  color: var(--uzh-blue);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--uzh-shadow);
  text-decoration: none;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.uzh-nav-menu__toggle svg,
.uzh-nav-menu__back svg { width: 20px; height: 20px; }
.uzh-nav-menu__toggle:hover,
.uzh-nav-menu__back:hover { filter: brightness(0.95); }
.uzh-nav-menu__toggle:focus-visible,
.uzh-nav-menu__back:focus-visible { outline: 2px solid var(--uzh-blue); outline-offset: 2px; }
:root[data-theme="dark"] .uzh-nav-menu__toggle,
:root[data-theme="dark"] .uzh-nav-menu__back {
  background: #17130f;
  color: #f3ede4;
  border-color: rgba(255, 255, 255, 0.22);
}

/* Wrap pages and health.html additionally need a "back to wrappers"
   arrow next to the burger -- .uzh-page-nav groups the two round
   buttons in a row at the same top-left spot the standalone
   .uzh-nav-menu occupies on index.html/networks.html, so the burger
   keeps its exact look everywhere; only its positioning switches from
   fixed (standalone) to relative (inside this flex row). */
.uzh-page-nav { position: fixed; top: 1rem; left: 1rem; z-index: 100; display: flex; align-items: center; gap: 0.5rem; }
.uzh-page-nav .uzh-nav-menu { position: relative; top: auto; left: auto; z-index: auto; }
.uzh-nav-menu__panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  background: #fff;
  border: 1px solid var(--uzh-line);
  border-radius: 10px;
  box-shadow: var(--uzh-shadow);
  padding: 0.35rem;
  min-width: 150px;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.uzh-nav-menu__panel[hidden] { display: none; }
.uzh-nav-menu__panel a {
  display: block;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  white-space: nowrap;
}
.uzh-nav-menu__panel a:hover { background: var(--uzh-light-grey, #f0f0f0); }
.uzh-nav-menu__panel a.is-current { color: var(--uzh-blue); }
:root[data-theme="dark"] .uzh-nav-menu__panel {
  background: #17130f;
  border-color: rgba(255, 255, 255, 0.22);
}
:root[data-theme="dark"] .uzh-nav-menu__panel a:hover { background: rgba(255, 255, 255, 0.08); }
.uzh-nav-menu__sublist {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding-left: 0.9rem;
}
.uzh-nav-menu__sublink { font-weight: 500; font-size: 0.82rem; opacity: 0.85; }
/* One axis for every arrow in the list. Same three cells the tiles and
   the page title use (.uzh-pair-line): the 1fr either side is what keeps
   the ARROW centred rather than the pair, which is the point -- the
   tickers are different widths and centring the group would leave the
   arrows scattered down the column. */
.uzh-nav-menu__panel a.uzh-nav-menu__sublink--pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  column-gap: 0.4rem;
  align-items: baseline;
}
.uzh-nav-menu__sublink--pair > :first-child { justify-self: end; }
.uzh-nav-menu__sublink--pair > :last-child { justify-self: start; }
.uzh-nav-menu__pair-arrow { justify-self: center; opacity: 0.7; }
/* ...and the columns have to be shared by the whole LIST, not computed
   per row. `1fr auto 1fr` centres the arrow in each link on its own, but
   a 1fr track still grows past its share when the content needs it, so
   the row with the widest right-hand ticker (wUZHETHw) pushed its arrow
   6px left of the others -- aligned enough to look like a mistake rather
   than a choice. Subgrid makes every row use one set of column widths:
   the widest left ticker, the arrow, the widest right ticker. Guarded,
   because without subgrid the declaration would be dropped and the three
   cells would stack. */
@supports (grid-template-columns: subgrid) {
  .uzh-nav-menu__sublist:has(.uzh-nav-menu__sublink--pair) {
    display: grid;
    grid-template-columns: auto auto auto;
    justify-content: center;
  }
  .uzh-nav-menu__panel a.uzh-nav-menu__sublink--pair {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }
}
.uzh-nav-menu__divider { height: 1px; background: var(--uzh-line); margin: 0.3rem 0.2rem; }
:root[data-theme="dark"] .uzh-nav-menu__divider { background: rgba(255, 255, 255, 0.22); }

/* ------------------------------------------------------------------ */
/* The pair arrow (see js/theme.js's UzhArrow for why it has two heads) */
/* ------------------------------------------------------------------ */
.uzh-pair-arrow {
  width: 30px;
  height: 24px;
  flex: none;
  color: inherit;
}
.uzh-pair-arrow__head { transition: opacity 240ms ease; }
.uzh-pair-arrow.is-forward .uzh-pair-arrow__head--left,
.uzh-pair-arrow.is-back .uzh-pair-arrow__head--right { opacity: 0.08; }
/* Inline in a line of text: matches the surrounding type rather than
   sitting at icon size. */
.uzh-pair-arrow--inline {
  width: 1.55em;
  height: 0.85em;
  margin: 0 0.3em;
  vertical-align: -0.08em;
  stroke-width: 2.6;
}

/* ------------------------------------------------------------------ */
/* Wallet row (see js/theme.js's UzhWallet for the rules it encodes)    */
/* Fixed grid: LED | name | button, with the full address on its own    */
/* line beneath. Nothing moves when a string changes length.            */
/* ------------------------------------------------------------------ */
.uzh-wallet-bar {
  /* Two wallet rows (Cardano, Bitcoin) sat stacked, which made those two
     bands 41px taller than the other wrapper bands. In the wide column
     they fit side by side, so every wrapper band is the same height and
     nothing is compressed; a single row still spans the full width. */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(26rem, 1fr));
  gap: 0.55rem 1.25rem;
  width: 100%;
  margin-top: 0.9rem;
  text-align: left;
}
.uzh-wallet-row {
  display: grid;
  /* One line per wallet: LED, name, the address beside the name, the copy
     cue, then the button. The name column is a fixed width so every
     address starts at the same x, on every row and every page. */
  grid-template-columns: 12px 6.5rem minmax(0, 1fr) 1.65rem 7.5rem;
  grid-template-areas: "led name addr copy button";
  align-items: center;
  column-gap: 0.6rem;
}
.uzh-wallet-row__name {
  grid-area: name;
  font-size: 0.85rem;
  font-weight: 600;
}
.uzh-wallet-row__addr {
  grid-area: addr;
  font-family: var(--uzh-mono, ui-monospace, "SF Mono", Menlo, monospace);
  font-size: 0.71rem;
  line-height: 1.5;
  /* One line, always. A Cardano base address is 103 characters -- no
     column is that wide, and wrapping it to three lines would make the
     row's height depend on which chain it belongs to. The full string is
     still all there: scroll the box, select it, read the tooltip, or use
     the Copy button beside it. */
  white-space: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  opacity: 0.9;
  align-self: center;
}
.uzh-wallet-row__addr::-webkit-scrollbar { height: 0; }
/* Fade the last few characters rather than cutting them dead: the start
   of the address stays readable as a recognition cue, and the fade says
   there is more without spending a column on an ellipsis. */
.uzh-wallet-row__addr {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.2rem), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.2rem), transparent 100%);
}
.uzh-wallet-row__addr.is-placeholder,
.uzh-wallet-row__addr:focus-within {
  -webkit-mask-image: none;
  mask-image: none;
}
.uzh-wallet-row__addr.is-placeholder {
  opacity: 0.5;
  font-style: italic;
  /* break-all is for hashes. Applied to ordinary words it split "not
     connected" across two lines inside a cell wide enough to hold it. */
  word-break: normal;
  white-space: nowrap;
}
/* ONE button style for every wallet control on the site -- the pill from
   the top of the wrapper cards. It draws itself from currentColor, so the
   same rule works white-on-blue inside a toned band and blue-on-white on
   an ordinary card, without a second variant to drift out of sync. */
.uzh-wallet-row__btn,
.uzh-wallet-row__copy {
  font: inherit;
  font-weight: 600;
  color: inherit;
  background: transparent;
  border: 1.5px solid currentColor;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  transition: background 160ms ease, opacity 160ms ease;
}
.uzh-wallet-row__btn {
  grid-area: button;
  width: 100%;
  font-size: 0.8rem;
  padding: 0.35rem 0.6rem;
}
/* The copy control is a cue, not a button: a small two-pages icon beside
   the address, borderless, taking the width of an icon rather than a
   column. It still occupies a fixed slot, so nothing moves when it
   becomes active or shows its confirmation. */
.uzh-wallet-row__copy {
  grid-area: copy;
  justify-self: start;
  align-self: center;
  width: 1.65rem;
  height: 1.65rem;
  padding: 0.22rem;
  border: 0;
  border-radius: 6px;
  opacity: 0.5;
}
.uzh-wallet-row__copy svg { width: 100%; height: 100%; display: block; }
.uzh-wallet-row__btn:hover:not(:disabled) {
  background: color-mix(in srgb, currentColor 14%, transparent);
}
.uzh-wallet-row__copy:hover:not(:disabled) {
  opacity: 1;
  background: color-mix(in srgb, currentColor 12%, transparent);
}
.uzh-wallet-row__btn:disabled,
.uzh-wallet-row__copy:disabled { opacity: 0.4; cursor: default; }
.uzh-wallet-row__copy.is-done { opacity: 1; background: transparent; }
.uzh-wallet-row__copy:disabled { opacity: 0.18; }
.uzh-led {
  grid-area: led;
  align-self: center;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1.5px dashed currentColor;
  background: transparent;
  opacity: 0.5;
  transition: background 220ms ease, opacity 220ms ease, box-shadow 220ms ease;
}
.uzh-wallet-row.is-connected .uzh-led {
  border-style: solid;
  background: currentColor;
  opacity: 1;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 25%, transparent);
}
.uzh-wallet-row.is-denied .uzh-led { background: var(--uzh-berry, #bf0d3e); border-color: var(--uzh-berry, #bf0d3e); }

/* Round icon button -- one component for every icon-only action. Same
   construction as the wallet pill (border from currentColor, so it works
   on any surface), just circular and sized to its glyph. */
.uzh-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.15rem;
  height: 2.15rem;
  padding: 0.42rem;
  flex: none;
  color: inherit;
  background: transparent;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  cursor: pointer;
  transition: background 160ms ease, opacity 160ms ease, transform 400ms ease;
}
.uzh-icon-btn svg { width: 100%; height: 100%; display: block; }
.uzh-icon-btn:hover:not(:disabled) { background: color-mix(in srgb, currentColor 14%, transparent); }
.uzh-icon-btn:disabled { opacity: 0.35; cursor: default; }
/* Smaller variant, for a control sitting beside an h3 rather than in a
   toolbar. Must come after the base rule: same specificity, so source
   order decides. */
.uzh-icon-btn--sm { width: 1.75rem; height: 1.75rem; padding: 0.3rem; border-width: 1px; }

/* ------------------------------------------------------------------ */
/* Pair line -- coins and the title beneath them share ONE grid, so     */
/* their arrows sit on the same vertical axis whatever the names are.   */
/* Centring each row as a whole does not do this: "UZHETHw" and         */
/* "wUZHETHw" are different widths, so the title's arrow drifts off the */
/* coin arrow above it. Three columns (1fr | arrow | 1fr) put the arrow */
/* at the exact centre of both rows, with the names hugging it.         */
/* ------------------------------------------------------------------ */
.uzh-coin-pair,
.uzh-pair-line {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: 0.9rem;
}
.uzh-coin-pair > :first-child,
.uzh-pair-line__left { justify-self: end; }
.uzh-coin-pair > :last-child,
.uzh-pair-line__right { justify-self: start; }
.uzh-coin-pair > .uzh-pair-arrow,
.uzh-pair-line > .uzh-pair-arrow { justify-self: center; }
.uzh-pair-line { text-align: initial; }

/* A pair line carrying ONE name. The three tracks are sized for
   left / arrow / right, so a single span lands in track 1 and sits
   left of centre -- which is what the UZHETHs <-> wUZHETHs tile looked
   like before it was given two names to show. Naming both sides is the
   real fix; this is so the fallback cannot look broken again. */
.uzh-pair-line > :only-child { grid-column: 1 / -1; text-align: center; }

/* A menu entry whose page needs a whitelisted account. The padlock sits
   at the entry's right edge, so a column of them reads as a column --
   and it is dimmer than the label, because it qualifies the entry
   rather than competing with it. */
.uzh-nav-menu__link.is-locked,
.uzh-nav-menu__panel > a.is-locked { display: flex; align-items: center; gap: 0.4rem; }
.uzh-nav-menu__lock { width: 13px; height: 13px; flex: none;
  margin-left: auto; opacity: 0.55; }

/* The explanation under an action button. It reuses .uzh-form purely for
   the show/hide that follows the mode, so it has to undo that rule's
   1.1rem gap: a preview line and its caveat are one paragraph's worth of
   text, not two sections. */
.uzh-explain { display: flex; flex-direction: column; gap: 0.35rem;
  margin-top: 0.8rem; }
/* ...except where it doubles as a .uzh-form, whose own rule decides
   whether the block is shown at all. */
.uzh-form.uzh-explain { display: none; }
.uzh-form.uzh-explain.is-active { display: flex; }


/* ═══════════════════════════════════════════════════════════════════
   THE AMM'S PAGES, FOLDED IN (2026-09-22)
   ═══════════════════════════════════════════════════════════════════

   These rules lived in frontend/amm/css/amm.css, loaded only by the
   four AMM pages. One site, one stylesheet: a second sheet is how two
   halves of the same interface drift apart, and they had — the amount
   pill had no visible border there because the token that draws it was
   only ever defined on the other side, and the picker, which is a
   perfectly general control, could not be used anywhere else or even
   shown in the gallery.

   Appended rather than interleaved, and unchanged: amm.css was already
   the LAST sheet those pages loaded, so putting it at the end of this
   one preserves the cascade exactly. There were no selectors defined in
   both files, so nothing changed precedence.

   Nothing here uses a bare element selector — every rule is
   class-scoped — which is what makes this safe to apply site-wide.

   The families: picker (a general control, now shown in the gallery),
   lq- (liquidity), exec- (the arbitrage executor), mx- (the market
   matrix), market-, venue-, legend-, swap-, quote-, chart-, graph-,
   viz-, tooltip-.
   ═══════════════════════════════════════════════════════════════════ */

/* ---------------------------------------------------------------------- *
 * AMM dashboard styles, shared by market.html and arbitrage.html.
 *
 * Shared rather than duplicated in two <style> blocks: a venue's colour,
 * a chip's shape and a table's rhythm have to be the same on both pages,
 * and the only reliable way to keep two copies identical is not to have
 * two copies. This sits beside the wrapper site's app.css rather than
 * inside it -- the wrappers do not use any of this, and a shared file
 * nobody shares is just a bigger file.
 *
 * Chart roles are custom properties so light and dark swap in one place
 * and the charts are written against roles, never raw hex.
 *
 * The four series steps are the validated categorical slots 1-4, checked
 * against THIS page's surfaces (#ffffff light, #17130f dark) rather than
 * against a default -- contrast only means anything against the surface a
 * chart actually renders on. Light mode returns a sub-3:1 warning for two
 * of them, so nothing here identifies a thing by colour alone: venues
 * differ by SHAPE on the overview (filled circle / hollow square) and by
 * DASH on the graph (solid / dashed), every line is directly labelled,
 * and every number in a chart appears in a table as well.
 * ---------------------------------------------------------------------- */

/* ------------------------------------------------------- venue ink -- */

/* A venue's colour is its IDENTITY, not "series 1", so it lives in its
   own token -- and at :root, not inside .viz. Twice now a card has been
   built without the .viz class and silently rendered its venue edge in
   the grey line colour (convention 22); scoping identity to a chart
   wrapper is what made that possible. The generic --series-* steps stay
   where they are, for anything that really is just the n-th line.

   UZHSwap uses its brand #FF007A unchanged: validated against both
   surfaces, all checks pass.

   Carbon's brand #53D0AC does NOT survive contact with a white page --
   1.86:1 against the light surface, well under the 3:1 floor for a mark
   that carries meaning, and outside the lightness band on both surfaces
   (scripts/validate_palette.js, dataviz). A 2px line in it is close to
   invisible. So the DATA ink is the same hue stepped down until it
   passes, and the brand colour is kept for the logo mark, where it sits
   on its own and is not encoding anything. Both steps below pass every
   check against their own surface. */
:root {
  --venue-uniswap: #FF007A;
  --venue-carbon:  #17876A;
  /* the untouched brand colours, for logos and nothing else */
  --brand-uniswap: #FF007A;
  --brand-carbon:  #53D0AC;
}
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    --venue-carbon: #1F8E6E;
  }
}
:root[data-theme="dark"] {
  --venue-carbon: #1F8E6E;
}

/* ------------------------------------------------- two sides, one row -- */

/* Pay/receive on the swap tab, and provide/and on the liquidity tab.
   Stacked by default -- on a phone there is no choice -- and side by
   side once there is room, which is how both venues' own interfaces
   read and how the pair of amounts is actually thought about.

   The breakpoint is the viewport rather than a container query because
   these cards sit in one fixed wrap width; if that ever stops being
   true this should become a container query and the number should go. */
.uzh-amount-pair { display: grid; }

@media (min-width: 880px) {
  .uzh-amount-pair {
    grid-template-columns: 1fr auto 1fr;
    align-items: end;               /* the two input rows line up, whatever
                                       the heads above them are doing */
    column-gap: 0.9rem;
  }
  .uzh-amount-pair > .uzh-amount { margin-top: 0; }
  /* The seam is now vertical, so the control that crosses it turns with
     it: the same glyph, a quarter turn round. */
  
  
  /* Already horizontal at rest, so here it simply stops turning. The
     bottom margin lines it up with the input rows, which is what
     `align-items: end` is measuring against. */
  
}

/* An <input> and a read-only <span> do not render the same height at the
   same font size -- 66px against 55px here -- so the two boxes sat 11px
   out of line once they were side by side: invisible stacked, obvious in
   a row. A min-height on the row does not fix it, because both rows were
   already taller than any sensible floor. The height has to be pinned on
   the thing that differs, so the value carries its own box either way. */

/* The swap tab's field was drawing a second box inside the row: the UI
   kit styles inputs by attribute selector, which out-specifies the bare
   .uzh-amount__input class, so `border: 0` there never applied. The liquidity
   field escaped it only by accident, via the two-class `.uzh-amount__row
   .lq-input`. Both are written the same way now, so the row is the only
   box either of them has. */

/* ------------------------------------------ the typed half of a pair --

   The kit's own trap, one property further along. uzh-bcc-ui.css styles
   form fields by ATTRIBUTE selector -- input[type="number"], (0,1,1) --
   which out-specifies the bare .uzh-amount__input class, (0,1,0). The
   kit already works around this for border/padding/background, with a
   .uzh-amount__row input[type=…] rule and a comment saying anything
   living in an amount row has to be written this way. Font size, ink,
   flex and height were left on the class, so they never applied to an
   <input>.

   The AMM never noticed: trade.html's derived side is a SPAN, which the
   attribute selector does not match, so only the typed side was wrong
   and there was nothing beside it to be wrong against. On the native
   bridge both sides are real fields, side by side, and the pair showed
   the whole thing at once -- 15px against 1.35rem, a 52px row against a
   taller one, and on a dark body a value in pure black.

   Stated once here, for every amount row on the site. */
.uzh-amount__row input.uzh-amount__input {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  height: 1.9rem;
  color: var(--uzh-ink);
}
:root[data-theme="dark"] .uzh-amount__row input.uzh-amount__input {
  color: #f3ede4;
}

/* The same rule for every other filled marker, found by asking the
   question the badge raised: which elements paint a background that
   does not differ from the surface behind them, and draw no edge?
   A status chip at 1.10 against white, and the health page's figure
   panels at 1.04. Both are markers — a thing you are meant to see AS a
   thing — so both get the boundary the badge was missing. */
.uzh-chip { border: 1px solid var(--uzh-line); }
.uzh-stat-grid__item { border: 1px solid var(--uzh-line); }

/* ---------------------------------------------------------------- seg
   The AMM's mode switch -- Swap / Provide liquidity. Reported as "hard
   to see the difference in light mode", and it was: the active pill
   painted var(--uzh-card, #fff) on a track tinted 4% ink, which on a
   white card is #fff on #f6f6f6, a ratio of 1.08. Only a 3px shadow
   said which half was chosen.

   The same defect class as the tile badge two rules up -- a filled
   marker with no edge -- so it takes the same answer, an edge, rather
   than a darker track. A darker track cannot be the answer here: the
   two themes want opposite moves. Deepening the tint to 9% raises the
   light pair to 1.20 but drops the dark pair from 1.18 to 1.05, because
   in dark the active pill is LIGHTER than the track and the tint walks
   the track up towards it. One number cannot serve both, so the track
   depth becomes a token each theme sets for itself, and the boundary --
   1.23 against the track in light, 2.33 in dark -- is what actually
   marks the choice.

   The transparent border sits on every button, not just the active one,
   so switching tabs does not resize the control. */
:root { --seg-track: rgba(0, 0, 0, 0.075); }
:root[data-theme="dark"] { --seg-track: rgba(255, 255, 255, 0.055); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) { --seg-track: rgba(255, 255, 255, 0.055); }
}

/* ------------------------------------------------- muted ink on a tint
   --uzh-mut (#59747F) was chosen to clear 4.5:1 on a WHITE surface,
   where it measures 4.97. Every surface that tints itself spends most of
   that margin: a 7.5% tint costs about 0.8 and puts the same text under
   AA without anything looking obviously wrong. Two places hit it
   independently -- the segmented control's idle label, and then the
   status block's detail line, which measured 4.20 to 4.45 across ALL
   FIVE tones in light mode (dark is 6.8+ and needs nothing).

   So it gets a name rather than a second magic number. #526B75 is the
   same hue and saturation, eight percent darker, and clears 4.5 on every
   tinted panel on the site.

   Dark mode deliberately keeps --uzh-mut: there the panels are tints of
   a LIGHT ink over a dark page, which moves the background away from the
   text rather than towards it. */
:root { --uzh-mut-tinted: #526B75; }
:root[data-theme="dark"] { --uzh-mut-tinted: var(--uzh-mut); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) { --uzh-mut-tinted: var(--uzh-mut); }
}

/* The status block's detail, on all five tones at once. Scoped to the
   token, not to a tone, because the deficit comes from the tint the
   component always applies -- not from any one colour. */
.uzh-status__detail { color: var(--tc-neutral-dim, var(--uzh-mut-tinted)); }

/* --------------------------------------------------------------- gate
   The one gate for every permissioned page: health, management and the
   three AMM pages. It replaces five near-identical cards that were a
   white box the width of half the page, carrying a heading, a one-line
   instruction and then a paragraph of security reasoning -- "no warning
   effect, no grace, does not call for attention, says irrelevant
   things", and all four of those were fair.

   THE WHOLE GATE IS THIS ONE LINE. A gate is a headline saying the
   outcome and a detail saying what produced it, next to a mark, which is
   exactly what .uzh-status already is (convention 48). So it declares a
   tone the way is-warn and is-error do and stops there: no element of
   its own, no padding, no border, no text rules, nothing to drift from
   the status blocks everywhere else. The LED carries the tone like every
   other LED on the site.

   Three heavier versions were built and thrown away first -- a solid
   CI-orange band, a card with a 3px orange edge, then the same thing
   with a padlock in the LED's place. Each looked fine and each was a
   second implementation of a component that already existed.

   CI ORANGE. --uzh-orange is the UZH palette's attention colour and the
   right register: the gate is not an error and nothing has failed, the
   page is simply shut until a member opens it, which berry would
   misstate as a fault. Gold was tried first and is too loud; a
   full-width solid orange fill was tried after that and is glaring -- at
   that area the colour stops being a signal and just hurts to look at.
   As a tone it lands where the component puts every other tone: a 9%
   tint, a matching edge, and the LED at full strength.

   That also sidesteps a contrast trap the fill had. On solid #FC4C02,
   white text measures 3.4:1 and fails AA, dark text manages only 5.12,
   and dimming the secondary line the usual way drops it to 3.55. Here
   the text sits on a 9% tint in the page's own ink, at full contrast in
   both themes, and the orange carries no text at all.

   THE LIVE LINE IS THE POINT. #gate-message / #gate-text is the
   __detail, where access.js and amm-common.js write what actually
   happened ("the connected address ... is not on the allow-list",
   "MetaMask is not installed"). The component reserves two lines for it,
   which is right here rather than merely tolerable: that message grows
   to a full address, and the reserved space means the page does not jump
   when it does.

   THE ESSAY IS GONE. What the gate does and does not protect is a fact
   about the deployment, not something a person locked out of a page
   needs to read; it lives in each page's source comment and in
   docs/UI_CONVENTIONS.md. One page's version was also simply wrong --
   it credited the AMM data API's signature check for an endpoint
   (/health) that markets.yaml deliberately leaves open. */
.uzh-status.is-locked { --banner-tone: var(--uzh-orange); }

/* ------------------------------- muted ink inside a light toned body
   The same fault as --uzh-mut, one surface further in. The kit sets
   --tc-neutral-dim to #59747F on a light toned body, which clears 4.5:1
   against that body's own flat #FAFAFA -- and every component that
   tints itself on top of it spends the margin. The native bridge's rate
   panel is a .uzh-status, so its detail line measured 4.18 to 4.27
   across all four tones: below AA on every one, while the same text on
   an ordinary page had already been fixed.

   It is fixed by the token that exists for this, rather than by a rule
   about status details: anything muted on a tinted surface inside a
   light toned body has the same problem, and naming only the case I
   happened to look at would leave the rest to be found one at a time.
   #526B75 gives 4.76 to 4.85 on the four tones, and 5.5 on the body
   itself where #59747F gave 4.85.

   Light only: .is-light exists only in the light theme (js/theme.js
   syncs the modifier to the site toggle), and the dark body's own
   --tc-neutral-dim is measured separately. */
.uzh-toned-card__body.is-light { --tc-neutral-dim: var(--uzh-mut-tinted); }

/* ------------------------------- a browser page, not a kiosk column --

   .uzh-toned-card__body > * caps every block at 44rem. That is right
   for the five wrapper pages: each is a single-column form of one
   field and a button, drawn for an 860px column, and at 1140 its
   fields would stretch into unusable letterboxes -- which is what the
   rule's own comment says.

   The native bridge is not that page. It is the trade card: two
   amounts side by side, a market head, a settings row with its
   explanation beside it. Its card measures 1140px like the trade
   page's, and the form sat in 704 of them with 218px of dead space
   down each side -- which is precisely what "trade.html covers well
   the width of the page, the bridge page not" describes.

   The kit's own guidance is the argument: kiosk mode is for the coffee
   machine, and its habits are not to be refitted onto a page that runs
   in an ordinary browser tab. A fixed narrow column inside a wide card
   is one of those habits. The toned card stays -- that IS the
   sanctioned way to have a per-network accent in web-app mode -- and
   only the column goes.

   Text keeps a measure. The disclosure and the note under the button
   are prose, and prose at 1140px is not an improvement at any width;
   they stay at 44rem while the instruments widen. */
.uzh-toned-card__body.is-wide > * { max-width: none; }
.uzh-toned-card__body.is-wide > details,
.uzh-toned-card__body.is-wide > .uzh-powered-by,
.uzh-toned-card__body.is-wide .uzh-explain {
  /* width:100% is load-bearing here for the same reason the rule this
     one overrides says it is: these sit in flex columns, and
     `margin-inline: auto` alone cancels the default stretch, leaving
     each one sized to its own content. .uzh-explain holds a preview
     line that is a non-breaking space until an amount is typed, so
     without this it measured THREE PIXELS wide. The comment on
     .uzh-toned-card__body > * has said so since the Wrap/Unwrap toggle
     came out 151px wide next to 704px fields; I read it, overrode the
     rule, and reproduced the fault one level further in. */
  width: 100%;
  max-width: 44rem;
  margin-left: auto;
  margin-right: auto;
}

/* ------------------------------------- the tracker before it tracks --

   .uzh-progress--idle is the step list on its own: what WILL happen,
   shown before you commit to it. Every step is in the kit's plain "not
   reached yet" state, which is exactly right, so this deliberately
   restyles nothing.

   In particular it does not dim. The obvious idea -- opacity on the
   whole block, to say "this is ahead of you" -- would multiply through
   .uzh-step's colour, and that colour is --tc-neutral-dim, a token
   picked to clear 4.5:1 with nothing to spare. Dimming a token that is
   already at its floor is the same fault this stylesheet has had three
   times now, in a new place.

   The one real difference: with no status box beneath it, the list's
   own bottom margin is the gap to whatever follows. */
.uzh-progress--idle .uzh-steps { margin-bottom: 0; }

/* ------------------------------- the refresh control, back in the row --

   The kit puts .uzh-holdings__actions at `position: absolute` in the
   strip's top-right corner and reserves `padding-right: 2.6rem` for it,
   with a comment saying that gutter means "a wide value can never run
   underneath the button".

   It cannot run under the BUTTON. The actions node also carries
   .uzh-refresh-note -- "updated 00:37:46 · manual" -- which is four or
   five times 2.6rem wide, sticks out to the LEFT of the reserved
   gutter, and is out of flow, so nothing keeps a cell away from it. Two
   cells never reached that far. The third one does: "Available in the
   pool" on this bridge, and "Total minted" on the five wrapper pages is
   one long supply away from the same collision.

   So it goes back in the row, pushed right by margin-left:auto. While
   there is room on the line it sits exactly where it sat and costs no
   height, which is what the absolute version was for; when there is not
   it wraps onto its own line instead of landing on top of a number. The
   gutter reserved for it is no longer needed and comes back to the
   content. */
.uzh-holdings { padding-right: 0.75rem; }
/* And nothing to reserve when there is nothing to say.

   The 14rem min-width above keeps the round button from shuffling
   sideways as "updating…" becomes a timestamp -- right on the health
   and balance toolbars, where that line is always there. In the
   holdings strip it is the exception: the five wrapper pages mount the
   control with NO note, and the bridge writes one only when a chain
   could not be read. Reserving 224px for an empty element made the
   actions a 256px block, which cannot fit beside three cells, so the
   button wrapped to a second line every single time and was never in
   the corner it was asked to be in.

   Empty, it takes no space and the button sits top-right. With a
   message, it grows, and wraps to the line below only if it must. */
.uzh-holdings__actions .uzh-refresh-note { min-width: 0; }
.uzh-holdings__actions .uzh-refresh-note:empty { display: none; }

.uzh-holdings__actions {
  position: static;
  margin-left: auto;
  align-self: flex-start;
  flex: 0 0 auto;
}

/* ------------------------------------------- a limit, said and gone --

   --alert is the fourth weight in the pill family, beside --tiny,
   --primary and --block: the chip that says you have crossed a stated
   limit. Berry, outlined rather than filled, because it sits in a
   field's head beside a caption and a filled red lozenge up there reads
   as an error banner that lost its way.

   It is a BUTTON and it acts -- it fills the field with the limit it
   names. The site's own rule for this corner is that a chip beside the
   label fills the field; a red chip that only scolds would be the one
   control on the page that decides nothing.

   And it leaves. The corner belongs to the balance on every field of
   every page, so the cap borrows it for five seconds (the timers are in
   native-bridge.js) and fades back out. The transition is here because
   the fade is a property of the chip, not of the moment. */
.uzh-pill-btn--alert {
  border-color: var(--uzh-berry);
  color: var(--uzh-berry);
  opacity: 1;
  transition: opacity 600ms ease, background 0.2s ease, border-color 0.2s ease;
}
.uzh-pill-btn--alert:not([disabled]):hover {
  background: color-mix(in srgb, var(--uzh-berry) 12%, transparent);
  border-color: var(--uzh-berry);
}
.uzh-pill-btn--alert.is-fading { opacity: 0; }
:root[data-theme="dark"] .uzh-pill-btn--alert { border-color: #ff8fa8; color: #ff8fa8; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .uzh-pill-btn--alert {
    border-color: #ff8fa8; color: #ff8fa8; }
}
/* No transition for anyone who asked for none; the chip still comes and
   goes, it just does it at once. */
@media (prefers-reduced-motion: reduce) {
  .uzh-pill-btn--alert { transition: background 0.2s ease, border-color 0.2s ease; }
}

/* The balance slot, saying the field is out of bounds.
   .uzh-amount__bal is the head's right-hand slot and already says what
   the field is bounded BY ("balance 3.4 · max 20"); when the number in
   the field passes that bound, the same slot says so rather than a
   sentence appearing somewhere else. One place to look, whichever
   state it is in.

   Berry, and only for text: the row keeps its own border, because a
   field that recolours its edge AND its caption for one condition is
   shouting. */
.uzh-amount__bal--warn { color: var(--uzh-berry); font-weight: 700; }
:root[data-theme="dark"] .uzh-amount__bal--warn { color: #ff8fa8; }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .uzh-amount__bal--warn { color: #ff8fa8; }
}
