/* ---------------------------------------------------------------------------
   Fonts, served from here rather than from Google.
   ---------------------------------------------------------------------------
   Every public page used to pull its stylesheet from fonts.googleapis.com and
   the files from fonts.gstatic.com, which sends each visitor's IP address and
   referer to Google before the page has drawn - on a service whose whole
   pitch is that it knows nothing about its users, and whose privacy policy
   did not mention it. The two files below are the Latin subsets of the same
   variable fonts Google serves (one file covers every weight the site uses),
   fetched 9 Sept 2026 and committed under public/assets/fonts/.
   Update them by downloading fresh copies; there is nothing to build. */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/SpaceGrotesk.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('../fonts/JetBrainsMono.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Dark, neon-accented "OG bridge" look — dark navy/black surfaces, a single
   bright accent color, a card-based swap widget with a circular swap-glyph
   divider between the two sides, monospace for addresses/numbers. */

:root {
  color-scheme: dark;
  --bg: #0a0e0c;
  --surface: #101512;
  --surface-2: #161c18;
  --border: #23302a;
  --border-strong: #35473d;
  --accent: #00e39a;
  --accent-dim: #00b87d;
  --accent-text-on: #04120c;
  --text: #eaf3ee;
  --text-muted: #7d9188;
  --danger: #ff6b6b;
  --warn-bg: #241d08;
  --warn-border: #5c4a12;
  --warn-text: #f2cf6a;
  font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
}

* { box-sizing: border-box; }

/* The browser's own [hidden] { display: none } is a USER-AGENT rule, so it
   loses to ANY author rule that sets display on the same element — no
   specificity contest, the author sheet simply wins. `.pill` sets
   display:inline-block, so every <span class="pill" hidden> in the admin
   nav rendered anyway: a permanent red "0" badge sat beside Holds and Chat
   on all 15 admin pages, telling whoever was on shift that something needed
   attention when nothing did. Two badges x 17 pages = 34 wrong signals.
   This restores the meaning of the attribute for every element on the site
   rather than patching .pill, because the next component with a display
   rule would reintroduce the same bug silently. !important is what it takes
   to beat an author display declaration from a class.

   `.hidden` is folded in here for the same reason one level up. It is
   declared plainly further down as `.hidden { display: none }`, which loses
   to `#quote-result p { display: flex }` on specificity (an id beats a
   class), so on the public quote panel the two rows the script hides —
   "Network fee (est.)" when the quote carries no gas estimate, and the note
   line when there is no note — were laid out as empty flex rows regardless:
   a label with nothing after it, and a blank gap. Same defect, different
   selector. */
[hidden], .hidden { display: none !important; }

body {
  margin: 0;
  padding: 32px 16px;
  background:
    radial-gradient(circle at 50% -10%, rgba(0, 227, 154, 0.08), transparent 45%),
    var(--bg);
  color: var(--text);
  /* Backstop only: nothing in this stylesheet is supposed to stick out
     past the viewport any more (the live routes panel used to — see the
     media query near the bottom of this file, which now only goes
     two-column once the panel genuinely fits), and every deliberately wide
     thing scrolls inside its OWN container instead: .route-card-tool does.
     (The .asset-picker-* pill rows used to be in that list and no longer
     are - they wrap now, see bug #423.) This just guarantees that a future
     absolutely-positioned or fixed-width element can never turn the whole
     page into a sideways-scrolling one on a narrow screen. Deliberately
     not set on <html>: with html's overflow left visible this propagates
     to the viewport, so body itself stays `visible` and never becomes a
     containing block for the position:fixed chat widget below. */
  overflow-x: hidden;
}

/* The page's single centered column (brand + swap card + footer links).
   Centered via margin auto, NOT flex — that matters: the live routes panel
   below sits inside .swap-wrap and overflows it to the right, so it can
   never shift where THIS centers. Popping the panel in or out never
   re-centers or nudges the card. */
.layout {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
}

/* Wraps just the swap card (not the brand row or footer links) and
   #routes-panel. A plain block here; on wide screens (see the 1252px rule
   near .routes-inline) it becomes a flex row whose first item, the card,
   is exactly its own width, so the panel lands flush with the card's right
   edge, top-aligned with it, and the row grows to the taller of the two. */
.swap-wrap {
  position: relative;
  width: 100%;
}

.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 20px;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
}
/* The mark is inline SVG now (public/_brand.php), not a text character in a
   coloured box, so the rounded square and the arrows both come from the SVG
   and this rule only has to size it. The old rule set background,
   border-radius, font-size and font-weight to dress up a &#8644; glyph -
   which meant the logo was drawn by whatever font the visitor happened to
   have. Keep flex:none: in the .brand flex row a bare SVG will happily be
   squashed when the wordmark needs room. */
.brand .glyph {
  width: 22px; height: 22px;
  flex: none;
  display: block;
}
.brand .glyph svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* The referral strip, between the logo and the card (index.php). Rules top
   and bottom rather than a filled box: it is a statement of fact about the
   visit, not a warning and not a promotion, and .banner's amber would read
   as one of those. Hidden until app.js finds a code, so the normal page has
   no empty gap where it would be. */
.referral-banner {
  margin: 0 0 20px;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  text-align: center;
}
.referral-banner-line {
  font-size: 13px;
  color: var(--text);
}
.referral-banner-line strong {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.06em;
  color: var(--accent);
  /* The code is the one part of this line anybody reads back to someone
     else, so it selects whole rather than by word - same reasoning as
     .addr on the order summary. */
  user-select: all;
  -webkit-user-select: all;
}
.referral-banner-note {
  margin-top: 3px;
  font-size: 12px;
  color: var(--text-muted);
}

.card {
  width: 100%;
  max-width: 460px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 24px;
  box-shadow: 0 0 0 1px rgba(0, 227, 154, 0.03), 0 20px 60px -30px rgba(0, 227, 154, 0.25);
}

h1 {
  font-size: 19px;
  font-weight: 600;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
h2 { font-size: 15px; font-weight: 600; margin: 20px 0 8px; color: var(--text); }
.muted { color: var(--text-muted); font-size: 13px; line-height: 1.5; }
.muted a { color: var(--accent); }

/* --- swap-widget layout: two "sides" (from/to) separated by a circular
   swap glyph, echoing the classic ShapeShift-style bridge widget --- */
.swap-side {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  margin-top: 10px;
}
.swap-side .side-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.swap-side .side-row {
  display: flex;
  gap: 8px;
}
.swap-divider {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: -8px 0;
  position: relative;
  z-index: 1;
}
.swap-divider .glyph {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;
  font-weight: 700;
}

label {
  display: block;
  margin: 12px 0 4px;
  font-size: 13px;
  color: var(--text-muted);
}
.swap-side label:first-child { margin-top: 0; }

input, select {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
}
input:focus, select:focus {
  outline: none;
  border-color: var(--accent-dim);
  box-shadow: 0 0 0 3px rgba(0, 227, 154, 0.15);
}
select { cursor: pointer; }
input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Amount / ≈ USD pair (see index.php + app.js's syncAmountFields()) — two
   equal-width fields side by side; either can be typed into, and app.js
   keeps the other one filled in from the origin token's cached price.
   #amountUsd disables itself (input:disabled above) when no price is
   cached for the picked token, rather than showing a field that can never
   actually do anything. */
.amount-fields {
  display: flex;
  gap: 8px;
}
.amount-fields label {
  flex: 1;
  min-width: 0;
  /* Both labels need the SAME top margin, or the two boxes visibly
     misalign: `.swap-side label:first-child { margin-top: 0; }` further
     up only ever matches the FIRST of these two (Amount) — :first-child
     is about position among ITS OWN siblings, not position within
     .swap-side as a whole — so without this, ≈USD kept its normal 12px
     top margin and sat noticeably lower than Amount. */
  margin-top: 0;
}

button {
  width: 100%;
  margin-top: 18px;
  padding: 13px;
  border: none;
  border-radius: 12px;
  background: var(--accent);
  color: var(--accent-text-on);
  font-weight: 700;
  font-size: 14px;
  /* A browser gives a <button> its own system font unless told otherwise, so
     "Get quote", "Find", "Copy" and "Claim this code" showed in Arial on the
     swap, order and referral pages (30 Sept 2026 device sweep). */
  font-family: inherit;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.05s ease;
}
button:hover { background: var(--accent-dim); }
button:active { transform: scale(0.99); }

hr { border: none; border-top: 1px solid var(--border); margin: 20px 0; }
.hidden { display: none; }
.error {
  color: var(--danger);
  font-size: 13px;
  margin-top: 12px;
  padding: 10px 12px;
  background: rgba(255, 107, 107, 0.08);
  border: 1px solid rgba(255, 107, 107, 0.25);
  border-radius: 10px;
}
/* The same red as .error, but sized and spaced to sit UNDER the input it is
   about rather than as a box at the foot of the form. Bugs #1678 and #1679
   were the same refusal reported twice, and both times the message was
   correct and several rows away from the field it described - so the box
   looked fine and the site looked broken. app.js creates this element on
   demand; see setFieldError(). */
.field-error {
  color: var(--danger);
  font-size: 12px;
  line-height: 1.45;
  margin: 6px 0 0;
}
/* And make the box itself say something is wrong, so the two are obviously
   about each other. */
input[aria-invalid="true"] {
  border-color: rgba(255, 107, 107, 0.55);
}

.notice {
  color: var(--text);
  font-size: 13px;
  margin-top: 12px;
  padding: 10px 12px;
  background: rgba(0, 229, 160, 0.06);
  border: 1px solid rgba(0, 229, 160, 0.25);
  border-radius: 10px;
}
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; word-break: break-all; font-size: 13px; }

.status {
  font-size: 14px;
  padding: 10px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--accent);
  font-weight: 600;
}

#qrcode {
  display: flex;
  justify-content: center;
  margin: 16px 0;
  padding: 16px;
  background: #fff;
  border-radius: 14px;
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
}

/* What the QR code carries, and the switch for a wallet that cannot read the
   amount form (2 Oct 2026; see assets/js/payment_uri.js). */
.qr-note {
  text-align: center;
  margin: -6px 0 6px;
  font-size: 0.85rem;
}
.qr-mode {
  display: block;
  margin: 0 auto 10px;
  padding: 4px 6px;
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
}
.qr-mode:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- chain-first "select asset" picker (modeled on Chainflip's swap UI —
   see public/index.php + app.js). A trigger button per swap-side opens a
   shared modal: search box, horizontal-scrolling chain pills, and a
   filtered asset list below. --- */
.asset-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin: 0;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: border-color 0.15s ease;
}
.asset-trigger:hover:not(:disabled) { border-color: var(--border-strong); background: var(--bg); }
.asset-trigger:disabled { opacity: 0.6; cursor: default; }
.asset-trigger-icon { flex-shrink: 0; display: flex; }
.asset-trigger-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.asset-trigger-symbol { font-size: 15px; font-weight: 600; }
.asset-trigger-chain { font-size: 12px; color: var(--text-muted); }
.asset-trigger-chevron { color: var(--text-muted); font-size: 18px; line-height: 1; transform: rotate(90deg); flex-shrink: 0; }

.asset-icon {
  width: 30px; height: 30px;
  border-radius: 50%;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  background: var(--surface-2);
}
.asset-icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
.asset-icon.asset-icon-fallback {
  background: var(--border-strong);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
}

.asset-picker-modal {
  position: fixed;
  inset: 0;
  background: rgba(4, 8, 6, 0.7);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 100;
}
.asset-picker-modal.hidden { display: none; }
@media (min-width: 480px) {
  .asset-picker-modal { align-items: center; padding: 20px; }
}

/* PAGE SCROLL LOCK WHILE THE PICKER IS OPEN (bug #1100, 11 Sept 2026).
   Reported from the live site at 690px: with the asset picker up, the swap
   form behind it still scrolled. The overlay is `position: fixed; inset: 0`,
   so it covers the page - but covering is not blocking. A wheel or a swipe
   over it still scrolled the document underneath, which slid the form out
   from under the dialog and left the picker floating over whatever happened
   to be there when you let go.

   `overflow: hidden` on body alone does NOT fix this: on most pages the
   element that actually scrolls is <html>, not <body>, so the rule lands on
   the wrong box and nothing changes. Taking body out of flow with
   `position: fixed` is what genuinely stops it, and it is also the only
   form iOS Safari honours.

   The cost of position:fixed is that body forgets where it was scrolled to,
   so the JS stores the offset, writes it here as an inline `top: -Npx`, and
   scrolls back to it on close - otherwise opening the picker halfway down
   the page would fling you to the top, which is a worse bug than the one
   being fixed. It also pads for the scrollbar it removes, so the page does
   not jump sideways by 15px as the dialog opens. */
body.is-scroll-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
/* ONE SCROLLING AREA: THE ASSET LIST (bug 2397, the owner, 5 Oct 2026):
   "increase the size of this so it can show all, on phone also ... we dont
   want scroll for anything except the bottom list which can get big". With
   the five receive-only EVM chains (4-5 Oct) the chain row grew to nine
   pills and its 108px cap turned it into a little scrolling box, so Base and
   Arbitrum sat below the fold of a box inside a box. The network and chain
   rows now always show every pill; the panel is taller to make room; the
   asset list below takes what is left and is the only thing that scrolls.

   overflow-y: auto on the PANEL is a last resort, not a second scroller: it
   only engages when the screen is too short for the rows plus the asset
   list's 120px floor (a phone held sideways). In every ordinary case the
   list shrinks first and the panel never overflows. dvh where supported, so
   a phone's sliding address bar does not push the bottom of the sheet off
   screen; vh is the fallback. tests/picker_scroll_test.php. */
.asset-picker-panel {
  width: 100%;
  max-width: 440px;
  max-height: 92vh;
  max-height: 92dvh;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  padding: 20px;
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* WIDER THAN THE SWAP CARD, ON PURPOSE (bug #423, 10 Sept 2026). At 440px
   the five family pills and the four EVM chain pills each overflowed their
   row, so the picker showed TWO horizontal scrollbars stacked above a
   cut-off asset list. A scrollbar is a reasonable answer to "more items
   than fit"; it is a poor answer to "nine items that would fit if the box
   were 100px wider". The rows below now wrap as well, so this width is
   what keeps the common case to a single line each rather than the thing
   preventing a scrollbar. Mobile is untouched: width:100% still wins below
   this breakpoint. */
@media (min-width: 480px) {
  .asset-picker-panel { border-radius: 20px; max-height: min(820px, 92vh); max-height: min(820px, 92dvh); max-width: 560px; }
}
.asset-picker-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px;
}
.asset-picker-header h2 { margin: 0; font-size: 17px; }
.asset-picker-close {
  width: auto; margin: 0; padding: 0 4px;
  background: transparent; color: var(--text-muted);
  font-size: 20px; line-height: 1;
}
.asset-picker-close:hover { background: transparent; color: var(--text); }
.asset-picker-search { margin-bottom: 16px; flex-shrink: 0; }
.asset-picker-chains-label, .asset-picker-assets-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-bottom: 8px; flex-shrink: 0;
}
/* Level 1 (network family) — same row treatment as the Level 2 chain-pill
   row below it, but plain text (no per-family icon) and a count badge, so
   the two levels read as a hierarchy rather than duplicates of each other. */
/* WRAPS RATHER THAN SCROLLING SIDEWAYS (bug #423). These rows hold five
   families and, today, at most four chains in a family - a count that fits
   on one or two lines at any sane width. `overflow-x: auto` turned that into
   a horizontal scrollbar the user had to drag to discover TRON and Sui
   existed, which is the worst way to present a short list: the items are
   not merely small, they are invisible until you scroll.

   NO HEIGHT CAP SINCE 5 OCT (bug 2397). This row used to stop at 108px and
   scroll vertically past three rows; the owner wants every pill visible and
   only the asset list scrolling, so the row grows with its pills and the
   panel above makes the room (see .asset-picker-panel). Both rows stay
   flex-shrink: 0 so they never get squeezed into clipping; the asset list
   is the one that gives way. */
.asset-picker-families {
  display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 10px; margin-bottom: 6px;
  flex-shrink: 0;
}
.asset-picker-families.hidden { display: none; }
.family-pill {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
  padding: 7px 12px;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 13px; font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  width: auto;
}
.family-pill-count { color: var(--text-muted); font-weight: 400; font-size: 12px; }
.family-pill.active { border-color: var(--accent-dim); background: rgba(0, 227, 154, 0.12); color: var(--accent); }
.family-pill.active .family-pill-count { color: var(--accent); opacity: 0.75; }
.family-pill:hover:not(.active) { border-color: var(--border-strong); }

/* Same treatment as the family row above, and for the same reason - see the
   comment there. Kept as a separate rule rather than merged into one
   selector because the two levels have drifted apart before (the family row
   gained a count badge) and a shared rule invites a change meant for one to
   land silently on both. */
.asset-picker-chains {
  display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 10px; margin-bottom: 6px;
  flex-shrink: 0;
}
.chain-pill {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
  padding: 7px 12px 7px 8px;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 13px; font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  width: auto;
}
.chain-pill .asset-icon { width: 20px; height: 20px; font-size: 10px; }
.chain-pill.active { border-color: var(--accent-dim); background: rgba(0, 227, 154, 0.12); color: var(--accent); }
.chain-pill:hover:not(.active) { border-color: var(--border-strong); }

/* overscroll-behavior stops scroll CHAINING (bug #1100): without it, a wheel
   or swipe that reaches the end of this list hands the remaining scroll to
   the page behind, so the list bottoming out drags the document instead.
   The body lock above already prevents the document moving, making this the
   belt to that braces - but it is also what stops the rubber-band bounce on
   iOS, which the lock does not cover.

   overflow-x is deliberately left alone here, unlike the two pill rows
   above. This list is meant to scroll vertically, so a gutter is expected
   rather than a bug, and the -4px/+4px margin-padding pair exists to give
   the rows' focus rings somewhere to draw - clipping the x axis would cut
   them off. */
.asset-picker-assets { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overscroll-behavior: contain; margin: -4px -4px 0; padding: 4px; }
.asset-row {
  width: 100%;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 8px;
  margin: 0;
  border: none; background: transparent;
  border-radius: 10px;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}
.asset-row:hover { background: var(--surface-2); }
.asset-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* Ticker symbols in the mono face, whose zero is slashed: in Space Grotesk
   "USDT0" read as "USDTO" and a visitor cannot tell USDT0 from a typo. */
.asset-row-symbol { font-size: 14px; font-weight: 600; font-family: 'JetBrains Mono', ui-monospace, monospace; }
.asset-row-chain { font-size: 12px; color: var(--text-muted); }
.asset-row-selected { font-size: 12px; color: var(--accent); flex-shrink: 0; }
.asset-picker-empty { padding: 24px 8px; text-align: center; color: var(--text-muted); font-size: 13px; }

.banner {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 13px;
  margin-bottom: 16px;
}
.banner a { color: #ffe8a3; }

#quote-result {
  margin-top: 16px;
  padding-top: 4px;
}
#quote-result p { display: flex; justify-content: space-between; font-size: 13px; margin: 6px 0; }
#quote-result strong { color: var(--text); }

/* --- quote rows: label left, figure right (bug #1340) -------------------
   Two flex children per row and no more. The "?" is inside the label span,
   so space-between has exactly one gap to distribute and the figures line
   up on the right edge, one under another, where they can be compared.

   Before this, the tip was a third child after the <strong>, and
   space-between spread the row into three columns: the figure floated in
   the middle, at a different x on every row depending on how long the
   label happened to be, and the right edge was held by a 10px circle. */
.quote-row-label {
  display: inline-flex;
  align-items: center;
  /* Never push the figure off the row: the label wraps, the number does
     not. min-width:0 is what lets flex-shrink actually take effect on a
     flex item containing text. */
  min-width: 0;
}
/* The figure. text-align matters when a long label wraps and the value
   ends up sharing the last line. */
#quote-result strong,
.quote-row-value {
  flex: 0 0 auto;
  text-align: right;
}
/* The expiry row is muted as a whole, so its figure is the one value on the
   quote that is not white - it is a clock, not a number to compare against
   the others - but it sits on the same right edge. Tabular figures so the
   countdown does not jitter sideways as the digits change. */
.quote-row-value { font-variant-numeric: tabular-nums; }

/* The prose rows in this block are sentences, not label/value pairs, so
   space-between has nothing useful to do with them - it just shoved the
   trailing "?" of "Nothing leaves your wallet yet" to the far right edge,
   a whole card away from the words it explains. Same complaint as #1340,
   one row further down. */
#quote-result p.hint { justify-content: flex-start; }

/* --- live "compare routes" panel — pops out to the right of the quote
   card as soon as an amount + both assets are picked (see app.js's
   fetchRoutes()). Ranked list of real distinct supported bridges for
   the same transfer; the visitor's pick becomes `preferredBridge` on the
   actual quote/order calls.

   Positioning: on wide viewports it is the second item of the .swap-wrap
   flex row, to the right of the card and top-aligned with it — it never
   touches the card's own centering, and a long list pushes the footer down
   instead of covering it (bug reports 2386/2387). On narrow viewports
   there's no room for a second column, so it drops back into normal flow
   and stacks below the card instead, full width, matching the card. --- */
.routes-panel {
  width: 100%;
  max-width: 460px;
  margin-top: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 20px;
  box-shadow: 0 0 0 1px rgba(0, 227, 154, 0.03), 0 20px 60px -30px rgba(0, 227, 154, 0.25);
  animation: routes-panel-in 0.2s ease;
  box-sizing: border-box;
}
.routes-panel.hidden { display: none; }
@keyframes routes-panel-in {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}
.routes-panel-header { margin-bottom: 14px; }
.routes-panel-header h2 { margin: 0; }

.routes-list { display: flex; flex-direction: column; gap: 10px; }

.route-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  margin: 0;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.route-card:hover { border-color: var(--border-strong); background: var(--surface-2); }
.route-card.selected { border-color: var(--accent-dim); background: rgba(0, 227, 154, 0.07); }

.route-card-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.route-card-amount { font-size: 16px; font-weight: 700; }
/* Route cards no longer print a dollar figure - see the long note in
   renderRoutesPanel() in app.js. The rule is kept rather than deleted because
   the decision is a product one, not a technical one: if the panel is ever
   given a valuation that IS comparable across routes, the card markup comes
   back and this is the style it should wear. Deleting it would make that a
   guess. Unused today, on purpose. */
.route-card-usd { font-size: 12px; color: var(--text-muted); flex-shrink: 0; }
/* A route that would land under min_output_usd. Marked on the card so it is
   visible before anyone picks it, rather than refused after they do. */
.route-card-warn { margin-top: 6px; font-size: 11px; color: var(--danger); text-align: left; }

/* .hint existed only as `.settings-form .hint`, so every <p class="hint">
   outside that one form — the admin order and asset lists already had some —
   rendered as ordinary body text and read as part of the page rather than as
   a note about it. The scoped rule still wins where it applies; this gives
   the class a meaning everywhere else.
   .hint-warn is that same line going red while the typed amount is under the
   minimum. */
.hint { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.hint-warn { color: var(--danger); }

/* The order ID on order.php. Given a box of its own because it is the only
   credential the order has — no account, no email, nothing else to look it up
   by — and it used to be one grey line at the foot of the page, set smaller
   than the footer links. */
.order-id-block {
  margin-top: 22px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
}
.order-id-title { margin: 0 0 4px; font-weight: 600; font-size: 14px; }
.order-id-row {
  display: flex; align-items: center; gap: 10px;
  margin-top: 10px; flex-wrap: wrap;
}
.order-id-row .mono { font-size: 13px; word-break: break-all; flex: 1 1 220px; }

/* A copy button that sits beside the thing it copies, rather than the
   full-width one under the deposit address. */
.copy-inline {
  width: auto; margin: 0; padding: 6px 14px; font-size: 12px; flex-shrink: 0;
}
.copy-inline.copied, button.copied { background: var(--accent-dim, #10291c); color: var(--accent, #7fe0a8); }

.route-card-mid { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.route-card-tool { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-muted); min-width: 0; }
.route-card-tool .asset-icon { width: 18px; height: 18px; font-size: 9px; }
.route-card-tool span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.route-card-tags { display: flex; gap: 6px; flex-shrink: 0; }

.route-tag {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 999px;
}
.route-tag-best { background: var(--accent); color: var(--accent-text-on); }
.route-tag-fastest { background: transparent; border: 1px solid var(--border-strong); color: var(--text-muted); }

.route-card-bottom { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; }
.route-card-selected { color: var(--accent); font-weight: 600; }
.route-card-eta { color: var(--text-muted); margin-left: auto; }

/* Wide enough to pop the panel out to the right as a true second column
   instead of stacking it below the form.

   The old 900px threshold did the arithmetic for a LEFT-ALIGNED card
   (460 + 20 + 360 = 840, rounded up for padding). The card is not
   left-aligned: .layout centres it with `margin: 0 auto`, so only HALF the
   leftover width is available on the right-hand side. The panel's right
   edge lands at (100vw + 460)/2 + 20 + 360 — the centre line + 610px — and
   it has to clear body's 16px right padding:

       100vw/2 >= 230 (half the card) + 20 (gap) + 360 (panel)
                  + 16 (body padding)      =>   100vw >= 1252px

   Between 900px and that, the panel hung off the right of the viewport
   (98px over at 1024px), the page grew a horizontal scrollbar, and every
   route card's right-hand column — the USD figure, the BEST/FASTEST tags,
   the ETA — was cut off. Stacking below the card is the correct layout at
   those widths, so the second column now only takes over once it actually
   fits. */
/* The in-form headline for the stacked layout; see index.php. */
.routes-inline {
  margin: 8px 0 4px;
  font-size: 13px;
  color: var(--muted);
}
.routes-inline strong { color: var(--text); }
.routes-inline.hidden { display: none; }
/* Bug reports 2386/2387 (25 Sept): with the panel position:absolute, a long
   list of quotes ran down OVER the footer (the Legal column vanished behind
   it), because an absolute box adds nothing to the page's height. So on wide
   screens .swap-wrap is a flex row instead: the card keeps its full width
   (flex 0 0 100%, so it never shrinks and never moves), the panel sits beside
   it and overflows to the right exactly where it used to, and the row is as
   tall as the TALLER of the two - so the footer is pushed down when the
   quotes are longer than the card, and stays where it was when they are not.
   Checked in a browser at 1536, 1300, 1252, 1000 and 390 px wide: same card
   and panel positions as before, no sideways scroll, footer clear. */
@media (min-width: 1252px) {
  .routes-inline { display: none; }
  .swap-wrap {
    display: flex;
    align-items: flex-start;
  }
  .swap-wrap > .card { flex: 0 0 100%; }
  .routes-panel {
    flex: 0 0 360px;
    margin-top: 0;
    margin-left: 20px;
    width: 360px;
    max-width: 360px;
  }
}

/* --- admin panel additions (kept in the same file so admin pages just
   load this stylesheet + assets/admin.css on top) --- */
nav.admin-nav {
  display: flex; gap: 16px; margin-bottom: 20px; font-size: 14px;
}
nav.admin-nav a { color: var(--text-muted); text-decoration: none; }
nav.admin-nav a.active { color: var(--accent); font-weight: 600; }
nav.admin-nav .spacer { flex: 1; }

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); }
th { color: var(--text-muted); font-weight: 500; }

.pill {
  display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px;
}
.pill-awaiting_deposit, .pill-deposit_seen, .pill-deposit_confirmed, .pill-bridging, .pill-awaiting_fill { background: var(--warn-bg); color: var(--warn-text); }
.pill-completed { background: rgba(0, 227, 154, 0.12); color: var(--accent); }
.pill-refund_pending { background: #2b1c10; color: #f2b98a; }
.pill-refunded { background: #10202b; color: #8ac6f2; }
.pill-unclaimed { background: #2b1c10; color: #f2b98a; }
.pill-expired, .pill-failed { background: rgba(255, 107, 107, 0.12); color: var(--danger); }
.pill-closed { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); }

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 20px; }
.stat-box { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px; }
.stat-box .value { font-size: 22px; font-weight: 700; color: var(--accent); }
.stat-box .label { font-size: 12px; color: var(--text-muted); }

.settings-form label { margin-top: 16px; }
.settings-form .hint { font-size: 12px; color: var(--text-muted); margin-top: 2px; }

/* --- support chat widget (assets/js/chat-widget.js) --- */
#chat-bubble {
  position: fixed;
  bottom: 20px; right: 20px;
  width: 52px; height: 52px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text-on);
  border: none;
  font-size: 22px;
  cursor: pointer;
  box-shadow: 0 8px 24px -6px rgba(0, 227, 154, 0.5);
  z-index: 40;
  margin: 0;
}
#chat-bubble:hover { background: var(--accent-dim); }

#chat-panel {
  position: fixed;
  bottom: 84px; right: 20px;
  width: 320px;
  max-width: calc(100vw - 40px);
  height: 420px;
  max-height: calc(100vh - 120px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 40;
}
#chat-panel.hidden { display: none; }

#chat-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-weight: 600;
  font-size: 14px;
}
#chat-header button {
  width: auto; margin: 0; padding: 0 4px;
  background: transparent; color: var(--text-muted);
  font-size: 18px; line-height: 1;
}
#chat-header button:hover { background: transparent; color: var(--text); }

#chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.chat-msg {
  max-width: 80%;
  padding: 8px 11px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.4;
  word-wrap: break-word;
}
.chat-msg-visitor {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-text-on);
  border-bottom-right-radius: 4px;
}
.chat-msg-bot, .chat-msg-admin {
  align-self: flex-start;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-bottom-left-radius: 4px;
}

#chat-form {
  display: flex;
  gap: 6px;
  padding: 10px;
  border-top: 1px solid var(--border);
}
#chat-form input {
  flex: 1;
  margin: 0;
}
#chat-form button {
  width: 40px;
  margin: 0;
  padding: 0;
  flex-shrink: 0;
}

/* ---------------------------------------------------------------------------
   Loading affordances — see public/assets/js/loading.js

   Lives in this file rather than its own because the admin pages already load
   this stylesheet and nothing else shared. One file to keep in step instead of
   two, and the bar has to look the same on both halves of the product.
   --------------------------------------------------------------------------- */

#load-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  z-index: 9999;
  background: linear-gradient(90deg, var(--accent-dim), var(--accent));
  /* The glow is what makes 3px readable against a dark page without making
     the bar thick enough to feel like a layout shift. */
  box-shadow: 0 0 8px rgba(0, 227, 154, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: width 0.2s ease, opacity 0.2s ease;
}
#load-bar.is-active { opacity: 1; }
#load-bar.is-done   { transition: width 0.15s ease, opacity 0.35s ease 0.15s; opacity: 0; }

.spinner {
  display: inline-block;
  width: 13px;
  height: 13px;
  margin-right: 8px;
  vertical-align: -2px;
  border: 2px solid currentColor;
  /* One transparent edge is what makes the rotation visible; a full ring
     would look static however fast it span. */
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

button.is-busy {
  cursor: progress;
  /* Deliberately NOT dimmed to the usual disabled grey. A button that is
     working and a button that cannot be used look different on purpose: the
     first is a reason to wait, the second a reason to look elsewhere. */
  opacity: 0.92;
}
button.is-busy:hover { background: var(--accent); }

/* Someone who has asked for less movement still needs to know the page is
   busy, so the bar and the spinner both remain — they simply stop moving.
   The information was never the animation. */
@media (prefers-reduced-motion: reduce) {
  #load-bar { transition: opacity 0.2s ease; }
  .spinner {
    animation: none;
    border-top-color: currentColor;
    opacity: 0.55;
  }
}

/* ---------------------------------------------------------------------------
   Chat: is anyone there, and removing a message
   --------------------------------------------------------------------------- */

.chat-presence {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  margin-right: 10px;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0.01em;
}

.chat-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  /* Grey by default, and it only turns green on positive confirmation that
     someone is there. If the check fails the visitor sees no claim at all
     rather than a wrong one. */
  background: var(--text-muted);
  flex: none;
}
.chat-presence.is-online       { color: var(--accent); }
.chat-presence.is-online .chat-dot {
  background: var(--accent);
  box-shadow: 0 0 6px rgba(0, 227, 154, 0.8);
}

/* A message whose text has been erased. Deliberately still occupies a slot in
   the transcript: the fact that something was said at that moment, and removed,
   is part of the record even when the words are not. */
.chat-msg-deleted {
  opacity: 0.5;
  font-style: italic;
  border: 1px dashed var(--border-strong);
  background: transparent !important;
  color: var(--text-muted) !important;
}

/* The delete control. Hidden until the message is hovered or focused, because
   an always-visible × on every bubble makes an irreversible action look like
   part of the furniture. */
.chat-msg { position: relative; }
.chat-msg-del {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 18px;
  height: 18px;
  padding: 0;
  margin: 0;
  line-height: 1;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--danger);
  font-size: 12px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.chat-msg:hover .chat-msg-del,
.chat-msg-del:focus-visible { opacity: 1; }
.chat-msg-del:hover { background: var(--danger); color: var(--surface); }

/* Keyboard users get no hover, so the control must never be reachable-but-
   invisible. */
@media (hover: none) {
  .chat-msg-del { opacity: 1; }
}

/* When a message was sent. Rendered from an epoch in the reader's own
   timezone — see formatWhen() in chat-widget.js and chat_admin.js.

   Its own line, small and dim: a timestamp on every bubble is reference
   information, wanted only when someone goes looking for it, and sized like
   the message it belongs to would compete with the message. */
.chat-when {
  display: block;
  margin-top: 4px;
  font-size: 10px;
  line-height: 1;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
  /* Inherits the bubble's colour so it stays legible on both the accent-
     coloured and the neutral bubble without needing a rule for each. */
  color: inherit;
}

/* "Customers see: Online" on the admin chat pages. Green only when it is
   actually true, so the state is readable at a glance rather than by reading
   the word — the whole point is that it was previously impossible to check
   this without leaving the page. */
.presence-live { color: var(--accent) !important; }

/* --- Bug reporter -------------------------------------------------------
   The link is a quiet footer item on purpose: it should be findable when
   something has gone wrong and invisible the rest of the time. This is not
   a marketing call-to-action, it is a fire exit. */
.bug-report-footer {
  margin-top: 18px;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
}
.bug-report-footer a { color: var(--text-muted); text-decoration: underline; }
.bug-report-footer a:hover { color: var(--text); }

/* Fixed rather than inline, so it opens over whatever the reporter is
   looking at instead of pushing it around — the state they are describing
   stays on screen behind the panel. */
.bug-panel {
  position: fixed;
  right: 16px;
  bottom: 16px;
  width: min(420px, calc(100vw - 32px));
  max-height: min(80vh, 640px);
  overflow-y: auto;
  z-index: 60;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: 18px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
}
.bug-panel h3 { margin: 0 0 6px; font-size: 16px; }
.bug-panel .bug-hint { margin: 0 0 12px; font-size: 12px; color: var(--text-muted); line-height: 1.5; }
.bug-panel textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  resize: vertical;
}
.bug-file-row { display: flex; align-items: center; gap: 8px; margin: 10px 0 4px; flex-wrap: wrap; }
/* The real file input is hidden and driven by its own label, because the
   browser default renders as an unstyled grey button that looks like it
   belongs to a different site. */
.bug-file-row input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.bug-file-label {
  display: inline-block;
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  font-size: 12px;
  cursor: pointer;
  margin: 0;
}
.bug-file-label:hover { border-color: var(--accent); color: var(--accent); }
/* The file input is focusable even while visually hidden, so its focus ring
   has to land somewhere a person can see — on the label that stands in for
   it. Without this, tabbing through the panel appears to skip a control. */
.bug-file-row input[type="file"]:focus-visible + .bug-file-name,
.bug-file-row input[type="file"]:focus-visible ~ .bug-file-label,
.bug-file-label:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bug-file-name { font-size: 12px; word-break: break-all; }
/* The panel's header row: title on the left, × on the right (bug #424).
   The h3 keeps whatever margin the panel already gave it, so adding this
   row does not shift the copy below - only the button is new. Modelled on
   .asset-picker-header, which solves the same problem the same way. */
.bug-panel-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px;
}
.bug-panel-head h3 { margin-top: 0; }
.bug-dismiss {
  width: auto; margin: 0; padding: 0 4px;
  background: transparent; border: 0;
  color: var(--text-muted);
  font-size: 22px; line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}
.bug-dismiss:hover { background: transparent; color: var(--text); }
.bug-actions { display: flex; gap: 8px; margin-top: 14px; }
.bug-actions button { width: auto; margin: 0; padding: 9px 16px; font-size: 13px; }
.bug-secondary { background: transparent; border: 1px solid var(--border-strong); color: var(--text-muted); }
.bug-message { margin: 12px 0 0; font-size: 12px; line-height: 1.5; color: var(--accent); }
.bug-message.bug-error { color: var(--danger); }

@media (max-width: 480px) {
  .bug-panel { right: 8px; left: 8px; bottom: 8px; width: auto; }
}

/* --- The "?" a visitor can tap next to anything confusing -------------
   See includes/site_help.php for the mechanism and, in particular, for
   why it is a checkbox rather than a <details>: <details> is block-level
   to the HTML PARSER whatever the CSS says, so every tip written inside
   a paragraph was being lifted out of it and dropped on the next line.
   A checkbox, a label and some spans are phrasing content and stay put.

   Sizing note that took several passes to get right: the marker is
   measured against the label's CAP HEIGHT, not its font-size. The text
   is 13px but its capitals are 9px of actual ink, and the eye compares
   the circle to the letters it can see. 10px is 1.1x the caps. */

.tip {
  display: inline-block;
  width: 10px;
  height: 10px;
  vertical-align: middle;
  line-height: 0;
  /* Clear space either side, as margin, so it can never touch the text
     it follows or the field beside it. */
  margin: 0 3px 0 7px;
  position: relative;
}

/* The focusable control. Off-screen rather than display:none, which
   would remove it from the tab order and make the tip keyboard-dead. */
.tip-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

/* The marker you actually see. */
.tip-mark {
  /* RESET FIRST. This element is a <label>, and the site has a global
     `label { display:block; margin:12px 0 4px; font-size:13px }` for real
     form labels. Every one of those properties lands here too, and the
     12px top margin pushed the marker a whole line below the text it
     belongs to - which looked exactly like a wrapping bug and was not
     one. Anything the global rule sets has to be overridden here, not
     just the properties this component happens to care about. */
  margin: 0;
  padding: 0;

  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 10px;
  height: 10px;
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-muted);
  font-size: 8px;
  line-height: 1;
  /* Regular weight: a bold glyph this small reads as an exclamation
     rather than an offer of help. */
  font-weight: 500;
  opacity: 0.8;
  cursor: pointer;
  user-select: none;
  /* Labels here are small and letter-spaced; without these the glyph
     inherits that and sits off-centre inside its own circle. */
  text-transform: none;
  letter-spacing: 0;

  /* NUDGED UP 1px - the fix for "too low".
     vertical-align:middle centres on the baseline plus half the
     X-HEIGHT, but letters occupy baseline to CAP height, so their
     optical centre is about a pixel higher. Correct by the rule, and
     visibly low on the screen. */
  transform: translateY(-1px);
  position: relative;
  transition: opacity 0.12s ease, color 0.12s ease,
              border-color 0.12s ease, background-color 0.12s ease;
}

/* THE CLICK TARGET, and nothing else.
   Absolutely positioned, so it adds no width, no height and no line box
   - it cannot push a neighbour or be pushed by one. Nothing is drawn
   here; it exists so a fingertip has 24px to aim at while the marker
   itself stays 10px.

   IT ONLY GROWS UP AND RIGHT, into empty label space. Measured on the
   live page, the input begins within a few pixels below the circle, so
   a downward extension would put an invisible target over the top edge
   of the field a visitor is trying to click into - the box stops taking
   clicks along its top edge with nothing on screen to explain why.
   Growing left would do the same to the label text. */
.tip-mark::after {
  content: '';
  position: absolute;
  top: -14px;
  right: -14px;
  bottom: 0;
  left: 0;
}

/* Quiet until wanted, then unmistakable about WHICH one is open: once
   your eye is in the panel, the filled marker is the only thing tying it
   back to the field it belongs to. */
.tip-mark:hover {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent-dim);
}
.tip-toggle:focus-visible + .tip-mark {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.tip-toggle:checked + .tip-mark {
  opacity: 1;
  color: var(--accent-text-on);
  background: var(--accent);
  border-color: var(--accent);
}

.tip-body {
  display: none;
  position: absolute;
  z-index: 60;
  top: calc(100% + 8px);
  left: 0;
  width: max-content;
  min-width: 240px;
  max-width: min(340px, calc(100vw - 40px));
  padding: 12px 14px;
  background: var(--card, #101613);
  border: 1px solid var(--border-strong);
  /* Ties the open panel back to the control it belongs to, which matters
     most on a row where two fields sit side by side. */
  border-left: 3px solid var(--accent-dim);
  border-radius: 10px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-muted);
  font-weight: 400;
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  white-space: normal;
  /* The tip lives inside text with line-height:0; the panel must undo it. */
  cursor: auto;
}
.tip-toggle:checked ~ .tip-body {
  display: block;
  /* The open one wins every stacking contest near it. Without this, two
     panels drawn over the same area are ordered by document position, so
     the one just opened can appear UNDER the one above it. */
  z-index: 61;
}

/* Paragraphs, as spans. A real <p> here would close the paragraph the
   tip is sitting in - the same parser rule that made <details> unusable. */
.tip-p { display: block; margin: 0 0 8px; }
.tip-p:last-child { margin-bottom: 0; }
.tip-label {
  color: var(--text, #e6e6e6);
  font-weight: 600;
  font-size: 13px;
}
.tip-body code {
  background: var(--bg);
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 12px;
}
.tip-body strong { color: var(--text, #e6e6e6); }

/* --- anchored to the field, not to the "?" ---------------------------
   When the tip sits inside a <label> - which is every tip on a form
   field - the panel is positioned against the LABEL instead. It then
   opens directly under that field, aligned to its left edge, so its
   position alone says which field it explains. Tips outside a label (a
   heading, a sentence) keep the original behaviour and hang off the
   marker, which is right for those. */
label:has(.tip) { position: relative; }
label:has(.tip) .tip { position: static; }
label:has(.tip) .tip-body { left: 0; right: auto; top: calc(100% + 6px); }

/* A field on the right-hand side of a row would push its panel off the
   edge of the card, so those open leftwards instead.

   The label selector repeats :has() rather than being written as the
   shorter `.tip-right .tip-body`. It has to: the rule above already
   counts a class for its :has() argument, so the short version loses the
   cascade and the panel keeps opening off the edge - the exact bug this
   rule exists to fix, under a rule that looks like it should work. */
label.tip-right:has(.tip) .tip-body,
.tip-right .tip-body { left: auto; right: 0; }

@media (max-width: 560px) {
  /* No room to float anything sensibly on a narrow screen: the panel
     becomes a full-width block under the field. Still absolutely
     positioned, so it still does not shove the input around. */
  .tip-body,
  label:has(.tip) .tip-body,
  label.tip-right:has(.tip) .tip-body,
  .tip-right .tip-body {
    left: 0;
    right: auto;
    width: calc(100vw - 40px);
    max-width: none;
    min-width: 0;
  }
}


/* Order page: the network warning and the "what you get" summary.
   The warning uses the same red as form errors on purpose - it is the one
   instruction on the page that cannot be undone if it is missed. */
.deposit-network-warning {
  margin: 10px 0 12px;
  padding: 10px 12px;
  border: 1px solid rgba(255, 99, 99, 0.45);
  border-radius: 8px;
  background: rgba(255, 99, 99, 0.08);
  color: var(--danger);
  font-size: 13px;
  line-height: 1.45;
}
.order-summary {
  margin: 16px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
}
/* THE STEPS (4 Oct 2026) - order.js renderProgress(). One row per step: a
   mark, the label, its time, its transaction link. The mark is decoration
   (aria-hidden); the label carries the state in words. A long hash wraps
   rather than widening the card on a phone. */
.order-progress {
  list-style: none;
  margin: 14px 0 4px;
  padding: 0;
  display: grid;
  gap: 8px;
  font-size: 13px;
}
.order-step { display: flex; gap: 10px; align-items: baseline; min-width: 0; }
.order-step-mark {
  flex: none;
  width: 1.2em;
  text-align: center;
  font-weight: 700;
}
.order-step-done .order-step-mark { color: var(--accent); }
.order-step-active .order-step-mark { color: var(--text); }
/* The muted colour, not opacity: opacity takes the text below the contrast
   the rest of the page passes axe at. */
.order-step-waiting { color: var(--text-muted); }
.order-step-body { display: flex; flex-wrap: wrap; gap: 4px 10px; min-width: 0; }
.order-step-tx { overflow-wrap: anywhere; font-size: 12px; }
.order-progress-note {
  margin: 6px 0 4px;
  padding: 10px 12px;
  border-left: 3px solid var(--border);
  font-size: 13px;
  line-height: 1.5;
}
.order-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0;
  font-size: 13px;
}
/* A CLICK TAKES THE WHOLE ADDRESS AND NOTHING ELSE. Bug #1343.
   user-select: all makes the browser treat the element as one indivisible
   selection, so the two ways a hand-drag went wrong both stop existing: you
   cannot end up with half an address, and you cannot start on the label and
   drag in, which copied "Refunds go to\n0xe8aBF..." with a newline the
   browser inserts between two inline elements. Nothing in the markup put
   that newline there, which is why it was invisible until it was pasted.
   On a page whose whole job is moving money to an address, "be careful how
   you drag" is not an acceptable answer. */
.addr { user-select: all; -webkit-user-select: all; }

/* Small, quiet, and the same shape as the copy buttons the deposit address
   and order ID already had. */
.copy-mini {
  flex: none;
  /* RESET THE GLOBAL BUTTON RULE FIRST, exactly as .copy-inline above does.
     `button { width: 100%; margin-top: 18px }` lands on this element too, and
     `flex: none` does not save it: flex-basis stays auto, which reads the
     width, which was 100% - so this 40px button took the whole summary row
     and squeezed the address beside it down to ONE CHARACTER PER LINE, 700px
     tall. Found on the order page after the copy buttons shipped (#1343);
     every real order with a recipient and a refund address had it. */
  width: auto;
  margin: 0;
  padding: 2px 8px;
  font-size: 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}
.copy-mini:hover { border-color: var(--accent); color: var(--accent); }

.order-summary-row .mono {
  word-break: break-all;
  text-align: right;
}

/* "from $X" beside a chain in the send-side picker. Muted, small, after the
   name, so it reads as a hint and not as a price. */
.chain-pill .chain-pill-min {
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  margin-left: 4px;
  white-space: nowrap;
}
.chain-pill.active .chain-pill-min { color: inherit; opacity: 0.8; }

/* --- the referral claim's review step (public/referral.php) --------------
   Replaces a window.confirm(). Bordered and inset so it reads as a stop,
   not as one more paragraph on the page - the form it replaces is hidden
   while this is up, so there is exactly one thing on screen to act on. */
.review-panel {
  padding: 16px;
  border: 1px solid var(--accent);
  border-radius: 14px;
  background: var(--surface-2);
}
.review-title {
  margin: 0 0 12px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
.review-label {
  margin: 10px 0 2px;
  font-size: 12px;
  color: var(--text-muted);
}
.review-value {
  margin: 0;
  font-size: 13px;
  color: var(--text);
  /* The address is the whole reason this step exists: let it wrap to as
     many lines as it needs rather than eliding, which is exactly what the
     OS dialog did wrong. */
  word-break: break-all;
}
.review-warn {
  margin: 14px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}
.review-warn strong { color: var(--danger); }

/* Side by side, cancel first. Buttons are full-width by default here, so
   both the width and the default top margin have to be undone. */
.review-actions {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}
.review-actions button {
  width: auto;
  flex: 1 1 0;
  margin-top: 0;
}
/* "Go back and edit" is an outline button: the filled accent belongs to the
   irreversible one, so the eye lands on the action that costs something. */
.review-actions .review-cancel {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}
.review-actions .review-cancel:hover {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
.review-actions button:disabled { opacity: 0.6; cursor: default; }

/* --- the "order placed, save this ID" overlay (order.php, bug #1343) -----
   Same overlay shape as .asset-picker-modal: bottom sheet on a phone, a
   centred dialog once there is room. Kept as its own class rather than
   reusing the picker's - the picker's panel is a scrolling list with its own
   sizing, and sharing a class would mean every future change to one silently
   reshaping the other. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(4, 8, 6, 0.7);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 120;
  padding: 0;
}
.modal-overlay.hidden { display: none; }
@media (min-width: 480px) {
  .modal-overlay { align-items: center; padding: 20px; }
}
.modal-panel {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  padding: 22px;
  box-shadow: 0 -20px 60px -30px rgba(0, 0, 0, 0.9);
}
@media (min-width: 480px) {
  .modal-panel { border-radius: 20px; }
}
.modal-title {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
}
.modal-lead {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
}
/* The ID itself, given the same box the order-id block uses further down the
   page, so the thing being pointed at looks the same in both places. */
.modal-id-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
  /* Wrap rather than squeeze. A UUID is 36 characters and the Copy button is
     ~70px; on a narrow phone they do not both fit, and the alternative to
     wrapping is breaking the ID across lines with a single orphaned
     character on the second - which is exactly the shape that gets
     mis-transcribed. Dropping the button below keeps the ID on one line. */
  flex-wrap: wrap;
}
.modal-id-row .mono {
  flex: 1 1 240px;
  /* 12px, not the .mono default of 13px: at 13px a 36-character UUID is just
     wide enough to wrap inside a 420px panel. */
  font-size: 12px;
  /* anywhere, not break-all: it breaks at the UUID's dashes first and only
     mid-token if there is genuinely no room. */
  overflow-wrap: anywhere;
  word-break: normal;
  user-select: all;
  -webkit-user-select: all;
}
.modal-note {
  margin: 12px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* --- the referral block on the order page (bug #1349) -------------------
   Boxed and set apart from the order's own details: it is the one thing on
   this page that is optional and that nothing bad happens if you ignore. It
   must not read as another step in sending the deposit. */
.order-referral {
  margin-top: 18px;
  padding: 14px;
  border: 1px dashed var(--border);
  border-radius: 12px;
}
/* The global `button { width:100%; margin-top:18px }` is meant for the big
   primary actions; here it would make "Apply code" the loudest thing on a
   page whose actual job is a deposit address. */
#order-referral-apply {
  width: auto;
  margin-top: 10px;
  padding: 8px 16px;
  font-size: 13px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}
#order-referral-apply:hover {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
#order-referral-apply:disabled { opacity: 0.6; cursor: default; }
#order-referral-applied strong { color: var(--accent); }

/* A different coin arrived (order.php #other-coin, 2 Oct 2026, order 35095).
   The page's warning colours: it is news the customer must not miss, not an error. */
.other-coin { margin: 0 0 14px; padding: 12px 14px; border: 1px solid var(--warn-border); background: var(--warn-bg); border-radius: 10px; }
.other-coin p { margin: 6px 0; }
.other-coin .other-coin-head { color: var(--warn-text); font-weight: 600; margin-top: 0; }
.other-coin .other-coin-token-row { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; }
.other-coin .other-coin-token-row .addr { overflow-wrap: anywhere; min-width: 0; }
