/* ==========================================================================
   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;
}

: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;
  --uzh-mut: #7596A7;
  --uzh-line: #DFE5EA;
  --uzh-light-grey: #FAFAFA;
  --uzh-bg: var(--uzh-light-grey);

  /* -- semantic (data/status) -- */
  --uzh-pos: #1F7A3D;
  --uzh-neg: var(--uzh-berry);
  --uzh-mono: "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;
}

h1 { font-size: 1.9rem; }
h2 { font-size: 1.3rem; }
h3 { font-size: 1.1rem; }

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; }

/* ==========================================================================
   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;
}
.uzh-calm-field__preview {
  font-size: 0.85rem;
  color: var(--tc-neutral-dim);
  font-weight: 600;
  min-height: 1.2em;
  margin: 0;
}

/* 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;
}

.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 cq*/v* would be
     identical, but embedded in anything smaller than full-viewport (a
     preview frame, a demo page, an iframe) v* 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. */
  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 diagonal highlight crossing a coin every few
   seconds, clipped to the coin's own ink via a -mask.svg counterpart.
   THREE GOTCHAS, each cost a real debugging pass building CryptoCoffee:
   1. Animate via mask-image + transform, NEVER background-position —
      background-position silently fails to paint under a mask on the
      compositor thread.
   2. The sweep colour must be OPAQUE, not translucent + mix-blend-mode —
      translucent-over-mask reliably under/over-shoots contrast per theme.
   3. mask-image referencing an external SVG renders BLANK under file://
      in Chromium. Serve over http:// (`python3 -m http.server`), always —
      this bit is unrelated to how the mask itself is authored and will
      cost you the same debugging pass again if forgotten. */
.uzh-coin-frame::after {
  content: "";
  position: absolute;
  inset: -20%;
  background: rgba(255, 255, 255, 0.9);
  mix-blend-mode: normal;
  -webkit-mask-image: var(--uzh-coin-mask-url);
  mask-image: var(--uzh-coin-mask-url);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  transform: translateX(-140%) rotate(20deg);
  animation: uzh-shine 4.5s ease-in-out infinite;
}
@keyframes uzh-shine {
  0%, 60% { transform: translateX(-140%) rotate(20deg); }
  85% { transform: translateX(140%) rotate(20deg); }
  100% { transform: translateX(140%) rotate(20deg); }
}
@media (prefers-reduced-motion: reduce) {
  .uzh-coin-frame::after { animation: none; display: none; }
}
