﻿/* soltradingbot dashboard
   No framework. One typeface family (monospace), one accent per wallet, and a
   dot vocabulary that means the same thing everywhere: green fine, amber worth
   a look, red stop. */

:root {
  /* Buckshot brand v2 — deep-ink surfaces, azure→violet accent.
     Same variable names as v1 so every component recolors automatically. */
  --bg: #06080f;
  --panel: #0c1119;
  --panel-2: #111724;
  --line: #1c2434;
  --line-soft: #161d2a;
  --text: #e2e8f4;
  --text-dim: #8b96ac;
  --text-faint: #5a6478;

  --green: #34d399;
  --amber: #fbbf24;
  --red: #f87171;
  --blue: #4d9fff;
  --violet: #8b7bff;
  --grad: linear-gradient(135deg, var(--blue), var(--violet));

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Consolas,
    "Liberation Mono", Menlo, monospace;
  --sans: "Segoe UI Variable Display", "Segoe UI", Inter, system-ui,
    -apple-system, sans-serif;

  --radius: 12px;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

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

.wrap {
  max-width: 1240px;
  margin: 0 auto;
  padding: 14px 18px 60px;
}

/* --- Status strip ----------------------------------------------------- */

.strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 16px;
  position: sticky;
  top: 0;
  z-index: 40;
}

.strip-item {
  display: flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  cursor: default;
}

.strip-label {
  color: var(--text-dim);
}

.strip-value {
  color: var(--text);
}

.strip-spacer {
  flex: 1;
}

.strip-config {
  color: var(--text-faint);
  font-size: 12px;
  white-space: nowrap;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
  flex: none;
  box-shadow: 0 0 0 0 currentColor;
}
.dot.green {
  background: var(--green);
}
.dot.amber {
  background: var(--amber);
}
.dot.red {
  background: var(--red);
  animation: pulse 1.8s ease-in-out infinite;
}
.dot.grey {
  background: var(--text-faint);
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* --- Banners ---------------------------------------------------------- */

.banner {
  margin-top: 12px;
  padding: 11px 16px;
  border-radius: var(--radius);
  border: 1px solid;
  display: flex;
  align-items: center;
  gap: 10px;
}
.banner strong {
  letter-spacing: 0.04em;
}
/* Sits at the far right of whichever banner it belongs to, and inherits its
   colour so it never reads as a separate control. */
.banner-close {
  margin-left: auto;
  flex: none;
  padding: 0 7px;
  line-height: 1.4;
  font-size: 15px;
  background: none;
  border-color: transparent;
  color: inherit;
  opacity: 0.55;
}
.banner-close:hover:not(:disabled) {
  opacity: 1;
  background: rgba(255, 255, 255, 0.06);
  border-color: transparent;
}
.banner.dry {
  background: rgba(78, 161, 255, 0.08);
  border-color: rgba(78, 161, 255, 0.35);
  color: #9dc9ff;
}
.banner.live {
  background: rgba(248, 81, 73, 0.08);
  border-color: rgba(248, 81, 73, 0.32);
  color: #ff9b95;
}
.banner.warn {
  background: rgba(210, 153, 34, 0.08);
  border-color: rgba(210, 153, 34, 0.35);
  color: #e8c46a;
}

/* --- Layout ----------------------------------------------------------- */

.cols {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 12px;
  margin-top: 12px;
}


@media (max-width: 920px) {
  .cols {
    grid-template-columns: minmax(0, 1fr);
  }
}







.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-top: 12px;
  overflow: hidden;
}
.cols .panel {
  margin-top: 0;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2);
}

.panel-title {
  font-size: 12px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.panel-body {
  padding: 16px;
}

.panel details > summary {
  cursor: pointer;
  list-style: none;
}
.panel details > summary::-webkit-details-marker {
  display: none;
}

/* --- Forms ------------------------------------------------------------ */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

label,
.field-label {
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
}

input,
select,
button,
textarea {
  font-family: var(--mono);
  font-size: 13px;
}

/* THE TWO WHITE BOXES ON THE LOGS TAB.
   TOMBSTONE. #log-from and #log-to are input[type="datetime-local"], a type that
   appears nowhere in this selector list and therefore took the browser default:
   measured 2026-08-21 at 21px tall, appearance:auto, a 2px UA border and a
   background of rgb(255,255,255) — two glaring white boxes on a dark panel,
   beside text inputs rendering at 39px. Same omission as the search boxes, and
   the same lesson: a type not named here is a control nobody styled. */
input[type='text'],
input[type='number'],
input[type='password'],
input[type='datetime-local'],
select,
textarea {
  background: #0a0e13;
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 10px;
  padding: 9px 12px;
  width: 100%;
  min-width: 0;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--blue);
}

input::placeholder {
  color: var(--text-faint);
}

.row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.row > * {
  flex: 1 1 130px;
}

.controls {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-top: 10px;
}
@media (max-width: 560px) {
  .controls {
    grid-template-columns: minmax(0, 1fr);
  }
}

button {
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 4px;
  padding: 6px 12px;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
  padding: 8px 16px;
}
button:hover:not(:disabled) {
  border-color: #3a4757;
  background: #1c242e;
}
button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

button.primary {
  background: rgba(63, 185, 80, 0.13);
  border-color: rgba(63, 185, 80, 0.45);
  color: #7ee08a;
}
button.primary:hover:not(:disabled) {
  background: rgba(63, 185, 80, 0.2);
}

button.danger {
  background: rgba(248, 81, 73, 0.12);
  border-color: rgba(248, 81, 73, 0.4);
  color: #ff9b95;
}
button.danger:hover:not(:disabled) {
  background: rgba(248, 81, 73, 0.19);
}

button.ghost {
  background: transparent;
}

button.tiny {
  padding: 4px 12px;
  font-size: 11px;
}

.btn-row {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.hint {
  color: var(--text-faint);
  font-size: 11.5px;
  margin-top: 6px;
  line-height: 1.45;
}

/*
 * Says who a panel's settings belong to. Set apart from the ordinary hints
 * because it answers a different question — not "what does this do" but
 * "am I allowed to change it, and who else does it affect".
 */
.scope-note {
  margin: 0 0 12px;
  padding: 7px 10px;
  border-left: 2px solid rgba(78, 161, 255, 0.5);
  background: rgba(78, 161, 255, 0.06);
  border-radius: 0 4px 4px 0;
  color: var(--text-dim, #9aa7b4);
}
.scope-note:empty {
  display: none;
}

/*
 * A position whose wallet key has been removed. Dimmed rather than hidden:
 * the tokens are still on chain and still yours, so hiding the row would be a
 * worse lie than showing it — but it must not sit among the tradeable ones
 * looking like something you can act on.
 */
/* --- Wallet privacy --------------------------------------------------- */

.audit-head {
  margin: 18px 0 8px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.audit-head:first-child {
  margin-top: 4px;
}

.wallet-risk {
  border: 1px solid var(--line, #1e2632);
  border-left-width: 3px;
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
}
.wallet-risk.good {
  border-left-color: rgba(63, 185, 80, 0.6);
}
.wallet-risk.warn {
  border-left-color: rgba(232, 196, 106, 0.7);
}
.wallet-risk.bad {
  border-left-color: rgba(248, 81, 73, 0.75);
}

.wallet-risk-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.risk-score {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.finding-list {
  margin: 6px 0 0;
  padding-left: 0;
  list-style: none;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-dim);
}
.finding-list li {
  padding: 2px 0;
}

/* The verdict word, so severity is readable without decoding a colour. */
.strength {
  font-size: 9.5px;
  letter-spacing: 0.07em;
  padding: 1px 5px;
  border-radius: 999px;
  border: 1px solid;
  margin-right: 6px;
  vertical-align: 1px;
}
.strength.red {
  color: #f85149;
  border-color: rgba(248, 81, 73, 0.5);
}
.strength.amber {
  color: #e8c46a;
  border-color: rgba(232, 196, 106, 0.5);
}
.strength.grey {
  color: var(--text-faint);
  border-color: var(--line);
}

.audit-advice {
  margin-top: 8px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text);
  border-left: 2px solid var(--line);
  padding-left: 9px;
}

.link-row {
  border: 1px solid var(--line, #1e2632);
  border-radius: 10px;
  padding: 9px 12px;
  margin-bottom: 8px;
}
.link-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.link-pair {
  font-weight: 500;
  color: var(--text);
  font-size: 12.5px;
}

/* --- Pagination ------------------------------------------------------- */

.pager {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 10px 14px;
  border-top: 1px solid var(--line, #1e2632);
  justify-content: flex-end;
  flex-wrap: wrap;
}
.pager.hidden {
  display: none;
}
.pager-btn {
  min-width: 28px;
  padding: 5px 12px;
  font-size: 11.5px;
  line-height: 1.2;
}
.pager-btn.current {
  border-color: #3f6fb9;
  color: var(--text);
  opacity: 1;
  cursor: default;
}
.pager-gap {
  color: var(--text-faint);
  padding: 0 2px;
  user-select: none;
}

/* A resolved ticker. Slightly brighter than an address, because it is the
   thing you actually read when scanning the table. */
.ticker {
  color: var(--text);
  font-weight: 500;
}
a.ticker {
  text-decoration: none;
}
a.ticker:hover {
  text-decoration: underline;
}
.token-ticker {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin-right: 10px;
}
.token-ticker:empty {
  display: none;
}

.tag.orphaned {
  border-color: rgba(232, 196, 106, 0.5);
  color: #e8c46a;
}
.orphaned-row td {
  opacity: 0.65;
}

.danger-note {
  border-left: 2px solid rgba(248, 81, 73, 0.6);
  background: rgba(248, 81, 73, 0.07);
  padding: 8px 10px;
  border-radius: 0 4px 4px 0;
  margin: 10px 0;
  line-height: 1.5;
}

/*
 * What is saved right now, for fields whose value is a credential and so is
 * never sent back in full. Monospace because the masked tail is meant to be
 * compared character by character against the key you have in hand.
 */
.current-value {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 11.5px;
  color: var(--text-dim, #9aa7b4);
  margin: 0 0 8px;
  word-break: break-all;
}
.current-value:empty {
  display: none;
}

/* A disabled input should read as "not yours to change", not as broken. */
input:disabled,
select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* --- Wallets ---------------------------------------------------------- */

.wallet {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
}
.wallet:last-child {
  border-bottom: none;
}

.wallet-swatch {
  width: 3px;
  align-self: stretch;
  border-radius: 2px;
  flex: none;
}

.wallet-main {
  flex: 1;
  min-width: 0;
}

.wallet-label {
  display: flex;
  align-items: center;
  gap: 8px;
}

.wallet-key {
  color: var(--text-faint);
  font-size: 11.5px;
}

.wallet-bal {
  text-align: right;
  white-space: nowrap;
}

.wallet-bal .low {
  color: var(--amber);
}

/* --- Tables ----------------------------------------------------------- */

.table-scroll {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}

th {
  text-align: left;
  font-weight: normal;
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 7px 10px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
  white-space: nowrap;
}

tr:last-child td {
  border-bottom: none;
}

td.num,
th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pos {
  color: var(--green);
}
.neg {
  color: var(--red);
}
.muted {
  color: var(--text-faint);
}

.token-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tag {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 3px;
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border: 1px solid;
}
.tag.armed {
  color: var(--green);
  border-color: rgba(63, 185, 80, 0.4);
  background: rgba(63, 185, 80, 0.1);
}
.tag.off {
  color: var(--text-faint);
  border-color: var(--line);
}
.tag.overlap {
  color: var(--amber);
  border-color: rgba(210, 153, 34, 0.45);
  background: rgba(210, 153, 34, 0.1);
}
.tag.dry {
  color: var(--blue);
  border-color: rgba(78, 161, 255, 0.4);
}

.empty {
  padding: 22px 14px;
  text-align: center;
  color: var(--text-faint);
}

/* --- Activity --------------------------------------------------------- */

/* Paged, not scrolled: a fixed 25 rows means the panel is the same height
   whether the bot has been up for a minute or a week. */
.feed {
  font-size: 12px;
}

.feed-row {
  display: flex;
  gap: 10px;
  padding: 7px 14px;
  border-bottom: 1px solid var(--line-soft);
  align-items: baseline;
}
.feed-row:last-child {
  border-bottom: none;
}

.feed-time {
  color: var(--text-faint);
  flex: none;
  font-size: 11px;
}

.feed-kind {
  color: var(--text-faint);
  flex: none;
  width: 66px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.feed-msg {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

.feed-row.success .feed-msg {
  color: #7ee08a;
}
.feed-row.warn .feed-msg {
  color: #e8c46a;
}
.feed-row.error .feed-msg {
  color: #ff9b95;
}

/* --- Modal ------------------------------------------------------------ */

.modal-back {
  position: fixed;
  inset: 0;
  background: rgba(3, 6, 10, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}

.modal {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  max-width: 520px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2);
  font-size: 12px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.modal-body {
  padding: 16px;
}

.modal-body p {
  margin: 0 0 12px;
}

.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 14px;
  margin: 12px 0;
  font-size: 12.5px;
}
.kv dt {
  color: var(--text-faint);
}
.kv dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.hidden {
  display: none !important;
}

.toast-stack {
  position: fixed;
  right: 16px;
  bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 200;
  max-width: 380px;
}

.toast {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 4px;
  padding: 9px 13px;
  font-size: 12.5px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
  overflow-wrap: anywhere;
}
.toast.success {
  border-left-color: var(--green);
}
.toast.error {
  border-left-color: var(--red);
}
.toast.warn {
  border-left-color: var(--amber);
}
.toast.info {
  border-left-color: var(--blue);
}

.foot {
  margin-top: 26px;
  color: var(--text-faint);
  font-size: 11.5px;
  text-align: center;
}

/* --- Token workspace -------------------------------------------------- */

.workspace {
  border-color: #2c3847;
}

.mint-bar {
  display: flex;
  gap: 8px;
  align-items: stretch;
}

.mint-bar input {
  flex: 1;
  font-size: 13.5px;
  padding: 8px 10px;
}

.mint-bar button {
  flex: none;
  padding: 8px 16px;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 9px;
}

.chip {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 5px 12px;
  font-size: 11.5px;
  color: var(--text-dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
}
.chip:hover {
  border-color: var(--blue);
  color: var(--text);
}
.chip .chip-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}

.token-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2);
}

.token-id {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.token-mint {
  font-size: 13.5px;
  overflow-wrap: anywhere;
}

.token-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
  margin: 0;
}
.token-stats > div {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.token-stats dt {
  font-size: 10.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.token-stats dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.inline-warn {
  padding: 8px 14px;
  background: rgba(210, 153, 34, 0.09);
  border-bottom: 1px solid rgba(210, 153, 34, 0.28);
  color: #e8c46a;
  font-size: 12px;
}

/* --- Bulk actions ----------------------------------------------------- */

.bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line-soft);
}

.bulk-label {
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-right: 2px;
}

.bulk-bar input {
  width: 88px;
  flex: none;
  padding: 4px 8px;
}

.bulk-sep {
  width: 1px;
  align-self: stretch;
  background: var(--line);
  margin: 0 4px;
}

.bulk-btn {
  padding: 6px 14px;
  font-size: 11.5px;
}

/* Armed state: first tap arms, second tap fires. No dialog, but a single
   mis-tap cannot empty every wallet. */
.bulk-btn.armed {
  background: rgba(248, 81, 73, 0.22);
  border-color: var(--red);
  color: #ffb3ae;
  animation: pulse 1s ease-in-out infinite;
}

/* --- Wallet trading table --------------------------------------------- */

.wallet-table td {
  padding: 7px 10px;
}

.wallet-table tr.busy {
  opacity: 0.5;
  pointer-events: none;
}

.wallet-table tr.flash-ok td {
  animation: flashok 1.1s ease-out;
}
.wallet-table tr.flash-err td {
  animation: flasherr 1.6s ease-out;
}

@keyframes flashok {
  from {
    background: rgba(63, 185, 80, 0.22);
  }
  to {
    background: transparent;
  }
}
@keyframes flasherr {
  from {
    background: rgba(248, 81, 73, 0.22);
  }
  to {
    background: transparent;
  }
}

.wallet-name {
  display: flex;
  align-items: center;
  gap: 8px;
}

.wallet-name .bar {
  width: 3px;
  height: 18px;
  border-radius: 2px;
  flex: none;
}

.buy-cell {
  display: flex;
  gap: 5px;
  align-items: center;
}

.buy-cell input {
  width: 74px;
  padding: 4px 7px;
  text-align: right;
}

.buy-cell button {
  padding: 5px 14px;
  font-size: 11.5px;
  background: rgba(63, 185, 80, 0.13);
  border-color: rgba(63, 185, 80, 0.45);
  color: #7ee08a;
}
.buy-cell button:hover:not(:disabled) {
  background: rgba(63, 185, 80, 0.22);
}

.sell-group {
  display: flex;
  gap: 6px;
}

.sell-group button {
  padding: 5px 12px;
  font-size: 11.5px;
  min-width: 40px;
  background: rgba(248, 81, 73, 0.1);
  border-color: rgba(248, 81, 73, 0.3);
  color: #ff9b95;
}
.sell-group button:hover:not(:disabled) {
  background: rgba(248, 81, 73, 0.2);
  border-color: rgba(248, 81, 73, 0.55);
}
.sell-group button.all {
  background: rgba(248, 81, 73, 0.2);
  border-color: rgba(248, 81, 73, 0.5);
  font-weight: 600;
}

.mismatch {
  color: var(--amber);
  cursor: help;
  margin-left: 4px;
}

/* The held figure when it disagrees with the ledger. Marked, not alarmed: the
   number is correct — it is the chain — and only the cost basis behind it is
   incomplete. */
.mismatch-cell {
  color: var(--amber);
  cursor: help;
}

.row-note {
  font-size: 11px;
  color: var(--text-faint);
}

.pos-row {
  cursor: pointer;
}
.pos-row:hover td {
  background: rgba(78, 161, 255, 0.06);
}

/* --- Top bar and chain picker ------------------------------------------
 *
 * The picker sits in the header, pinned right, and is deliberately nowhere
 * near the page tabs. The tabs change what you are looking at; this changes
 * which network your money is on. Keeping them apart means a misclick between
 * neighbouring controls can never move you between chains.
 *
 * Closed, it still shows the chain's name and accent colour, so "which chain
 * am I on" is answered without opening anything.
 */

.topbar {
  display: flex;
  align-items: stretch;
  gap: 8px;
  position: sticky;
  top: 0;
  z-index: 40;
}

.topbar .strip {
  flex: 1;
  min-width: 0;
  position: static;
}

.chain-menu {
  position: relative;
  flex: none;
}

.chain-drop {
  position: relative;
  height: 100%;
}

.chain-trigger {
  --chain-accent: var(--blue);
  display: flex;
  align-items: center;
  gap: 8px;
  height: 100%;
  padding: 0 12px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.12s, background 0.12s;
}

.chain-trigger:hover {
  border-color: var(--chain-accent);
}

.chain-drop.open .chain-trigger {
  border-color: var(--chain-accent);
  background: var(--panel-2);
}

.chain-swatch {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--chain-accent);
  flex: none;
  box-shadow: 0 0 6px var(--chain-accent);
}

.chain-caret {
  color: var(--text-faint);
  font-size: 10px;
  transition: transform 0.12s;
}

.chain-drop.open .chain-caret {
  transform: rotate(180deg);
}

.chain-list {
  display: none;
  position: absolute;
  top: calc(100% + 5px);
  right: 0;
  min-width: 190px;
  padding: 4px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  z-index: 60;
}

.chain-drop.open .chain-list {
  display: block;
}

.chain-option {
  --chain-accent: var(--blue);
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: 4px;
  font-size: 12.5px;
  color: var(--text-dim);
  text-decoration: none;
  white-space: nowrap;
}

.chain-option:hover {
  background: var(--panel);
  color: var(--text);
  text-decoration: none;
}

.chain-option.active {
  color: var(--text);
  background: var(--panel);
}

.chain-option-label {
  flex: 1;
  font-weight: 600;
}

/* The unit is shown next to each option because it is the thing that actually
   changes about every number on screen once you switch. */
.chain-option-unit {
  color: var(--text-faint);
  font-family: var(--mono);
  font-size: 11px;
}

@media (max-width: 700px) {
  .topbar {
    flex-wrap: wrap;
  }
  .chain-menu {
    width: 100%;
  }
  .chain-trigger {
    width: 100%;
    justify-content: space-between;
    padding: 8px 12px;
  }
  .chain-list {
    left: 0;
    right: 0;
  }
}

/* --- Tabs -------------------------------------------------------------- */

.tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 12px;
  border-bottom: 1px solid var(--line);
}

.tab {
  padding: 7px 16px;
  font-size: 12px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 5px 5px 0 0;
  text-decoration: none;
  margin-bottom: -1px;
}
.tab:hover {
  color: var(--text);
  text-decoration: none;
}
.tab.active {
  color: var(--text);
  background: var(--panel);
  border-color: var(--line);
  border-bottom: 1px solid var(--panel);
}

.tab-spacer {
  flex: 1;
}

.tab-note {
  font-size: 11.5px;
  color: var(--text-faint);
  padding-right: 4px;
}

/* --- Quick buy / sell -------------------------------------------------- */

.quick-group {
  display: flex;
  gap: 6px;
  align-items: center;
}

.quick-buy {
  padding: 6px 13px;
  font-size: 11.5px;
  background: rgba(63, 185, 80, 0.12);
  border-color: rgba(63, 185, 80, 0.4);
  color: #7ee08a;
  white-space: nowrap;
}
.quick-buy:hover:not(:disabled) {
  background: rgba(63, 185, 80, 0.24);
  border-color: rgba(63, 185, 80, 0.7);
}

.manual-buy {
  display: flex;
  gap: 6px;
  margin-left: 5px;
  padding-left: 8px;
  border-left: 1px solid var(--line);
}
.manual-buy input {
  width: 68px;
  padding: 4px 6px;
  text-align: right;
}
.manual-buy button {
  padding: 6px 13px;
  font-size: 11.5px;
  background: rgba(63, 185, 80, 0.12);
  border-color: rgba(63, 185, 80, 0.4);
  color: #7ee08a;
}

.bulk-btn.buy {
  background: rgba(63, 185, 80, 0.13);
  border-color: rgba(63, 185, 80, 0.4);
  color: #7ee08a;
}

.bulk-progress {
  padding: 9px 14px;
  border-bottom: 1px solid var(--line-soft);
  background: rgba(78, 161, 255, 0.07);
  color: #9dc9ff;
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.bulk-progress .bar {
  flex: 1;
  height: 4px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
}
.bulk-progress .bar > span {
  display: block;
  height: 100%;
  background: var(--blue);
  transition: width 0.3s;
}

/* --- Settings ---------------------------------------------------------- */

.presets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 8px;
}

.preset {
  text-align: left;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--panel-2);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.preset:hover {
  border-color: #3a4757;
}
.preset.active {
  border-color: var(--blue);
  background: rgba(78, 161, 255, 0.1);
}
.preset .preset-name {
  font-size: 12.5px;
  color: var(--text);
}
.preset .preset-summary {
  font-size: 11px;
  color: var(--text-faint);
  line-height: 1.35;
}
.preset .preset-nums {
  font-size: 10.5px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* --- Privacy audit ----------------------------------------------------- */

.audit {
  margin-top: 16px;
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
}

.audit-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
}
.audit-row:last-child {
  border-bottom: none;
}

.audit-main {
  flex: 1;
  min-width: 0;
}
.audit-label {
  font-size: 12.5px;
}
.audit-detail {
  font-size: 11.5px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.audit-why {
  font-size: 11px;
  color: var(--text-faint);
  line-height: 1.4;
  margin-top: 2px;
}

.audit-scope {
  margin-top: 14px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
}
.audit-scope h4 {
  margin: 0 0 6px;
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: normal;
}
.audit-scope li {
  font-size: 11.5px;
  color: var(--text-dim);
  line-height: 1.5;
  margin-bottom: 5px;
}
.audit-scope ul {
  margin: 0;
  padding-left: 16px;
}

/* --- Confirmation ------------------------------------------------------ */

.confirm-list {
  margin: 12px 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  max-height: 260px;
  overflow-y: auto;
}
.confirm-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 11px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.confirm-row:last-child {
  border-bottom: none;
}
.confirm-row.skipped {
  color: var(--text-faint);
}
.confirm-row .amount {
  font-variant-numeric: tabular-nums;
}

.confirm-total {
  display: flex;
  justify-content: space-between;
  padding: 8px 11px;
  background: var(--panel-2);
  font-size: 13px;
  border-top: 1px solid var(--line);
}

/* --- Login ------------------------------------------------------------- */

.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.login-panel {
  width: 100%;
  max-width: 340px;
  margin-top: 0;
}

.who {
  font-size: 11.5px;
  color: var(--text-dim);
  padding-right: 8px;
}

.metric-good {
  color: var(--green);
}
.metric-warn {
  color: var(--amber);
}
.metric-bad {
  color: var(--red);
}

/* --- Funding ------------------------------------------------------------
 *
 * The linkability verdict is the reason the preview exists, so it is styled to
 * be read first — a plan that funds ten wallets from one address in one block
 * should look wrong before you read a single number.
 */
.fund-link {
  display: inline-block;
  margin: 8px 0 4px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
}
.fund-link-low {
  color: #7ee081;
  background: rgba(63, 185, 80, 0.1);
  border: 1px solid rgba(63, 185, 80, 0.35);
}
.fund-link-moderate {
  color: #e8c46a;
  background: rgba(210, 153, 34, 0.1);
  border: 1px solid rgba(210, 153, 34, 0.35);
}
.fund-link-high {
  color: #ff9b95;
  background: rgba(248, 81, 73, 0.1);
  border: 1px solid rgba(248, 81, 73, 0.35);
}

.fund-note {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-dim);
}

#fund-source {
  min-height: 60px;
}


/* --- Bridge funding ------------------------------------------------------
   The root address is the one thing on this page you paste into an exchange
   withdrawal form, so it is shown in full and in a monospace face — a
   truncated address is one a person cannot check against what they pasted. */
.hop-address {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  word-break: break-all;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
  user-select: all;
}

/* Restoring an existing phrase. Folded away behind a summary because it is
   the rarer path — you generate once and restore only when moving machines —
   and an always-open textarea next to "Generate" invites pasting into the
   wrong one. */
.hop-restore {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.hop-restore > summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  user-select: none;
}

.hop-restore > summary:hover {
  color: var(--text);
}

.hop-restore textarea {
  width: 100%;
  margin-top: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.6;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  resize: vertical;
}

/* The 24 words, laid out to be copied onto paper rather than skimmed. */
.hop-phrase {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  line-height: 1.9;
  word-spacing: 6px;
  padding: 12px 14px;
  margin-top: 8px;
  border: 1px solid #e8c46a;
  border-radius: 6px;
  background: rgba(232, 196, 106, 0.06);
  user-select: all;
}

.hop-root {
  padding: 12px;
  border: 1px solid var(--line-soft);
  border-radius: 8px;
}

/* An anchor styled as a button, so "view on explorer" sits level with the
   real buttons beside it instead of hanging as a bare link. */
.button-like {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.fund-note-bad {
  color: #ff9aa8;
}

/* Recovery sits apart from the run controls: you reach for it when something
   has already gone wrong, and it should not read as part of the normal flow. */
.hop-recover {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

/* Same separation as recovery, but the rule is drawn in the danger colour: this
   block throws a key away and should not read like another settings row. */
/* Choosing which wallets a sweep empties. Sits above the destination, because
   the order the decisions are made in is: which wallets, then where to. */
.sweep-picker {
  margin-bottom: 10px;
}

.sweep-picker .field-label {
  margin-right: 4px;
}

/* The two halves of the send-out dialog. Ruled apart because they move money
   on different chains and confusing them is expensive. */
.exit-section {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.exit-section:first-of-type {
  border-top: none;
  padding-top: 0;
}

.hop-replace {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid rgba(248, 81, 73, 0.35);
}

/* Money that landed on Ethereum mainnet instead of the trading chain. Drawn
   under the real balance and only when non-zero, so it reads as a warning
   rather than as a second column everyone learns to ignore. */
.wallet-mainnet {
  display: block;
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}

/* Only coloured when there is actually something there. A permanently amber
   line is one people stop seeing. */
.wallet-mainnet-warn {
  color: #e8c46a;
}

/* --- Docs -----------------------------------------------------------------
   The one screen here that is read rather than scanned, so it gets a padded
   body and a capped line length. The first version put the content straight
   into .panel, which has no padding of its own — every other panel wraps its
   content in .panel-body — so the text ran flush into the border. */

.docs-body {
  padding: 18px 22px 26px;
  /* Caps the MEASURE, not the panel. Capping the panel left a narrow box with
     text jammed against both edges, which is the opposite of what a reading
     width is for. ~72 characters is the comfortable range. */
  max-width: 68ch;
  line-height: 1.7;
}

.docs-body h2 {
  margin: 34px 0 10px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  font-size: 16px;
  letter-spacing: 0.01em;
  color: var(--text);
}

.docs-body h2:first-of-type {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

.docs-body h3 {
  margin: 22px 0 6px;
  font-size: 13.5px;
  color: var(--text);
}

.docs-body p {
  margin: 0 0 12px;
}

.docs-body ul,
.docs-body ol {
  margin: 0 0 14px;
  padding-left: 20px;
}

.docs-body li {
  margin-bottom: 7px;
}

.docs-body code {
  font-size: 12px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  /* A long address must break rather than push the page sideways. */
  word-break: break-word;
}

/* Jump list. auto-fit collapses it to one column on a narrow screen without
   needing its own media query. */
.docs-toc {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 4px 14px;
  margin: 0 0 26px;
  padding: 14px 16px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
}

.docs-toc a {
  font-size: 12.5px;
  color: var(--text-dim);
  /* Comfortably tappable on a phone without looking padded on a desktop. */
  padding: 3px 0;
}

.docs-toc a:hover {
  color: var(--text);
}

/* A heading INSIDE the contents grid, so the copy-trading half reads as its own
   manual rather than as nine more numbers on the end of the first one. Spans
   every column, because a label sitting in one cell of an auto-fit grid reads
   as a broken link rather than as a divider. */
.docs-toc-break {
  grid-column: 1 / -1;
  margin: 10px 0 2px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* No rule above the first one — it would draw a line under nothing. */
.docs-toc-break:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Term lists. A definition list with an unstyled <dt> renders as one
   undifferentiated grey block, which is the opposite of what a glossary is for:
   the whole value is being able to find the word you do not know. */
.docs-terms {
  margin: 0 0 18px;
}
.docs-terms dt {
  margin-top: 12px;
  font-weight: 600;
  color: var(--text);
}
.docs-terms dt:first-child {
  margin-top: 0;
}
.docs-terms dd {
  margin: 3px 0 0;
  padding-left: 14px;
  border-left: 2px solid var(--line-soft);
  color: var(--text-dim);
}

/* The numbered walk-through of one copy. Roomier than an ordinary list because
   each step is a paragraph, and they are meant to be followed against the live
   Logs page rather than skimmed. */
.docs-steps > li {
  margin-bottom: 10px;
}

/* Three weights of aside. The danger one is deliberately loud: someone
   skimming should still catch the parts that lose money. */
.docs-callout {
  margin: 0 0 16px;
  padding: 12px 15px;
  border-left: 3px solid var(--line);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: rgba(255, 255, 255, 0.03);
}

.docs-callout.warn {
  border-left-color: var(--amber);
  background: rgba(210, 153, 34, 0.08);
}

.docs-callout.danger {
  border-left-color: var(--red);
  background: rgba(248, 81, 73, 0.09);
}

/* Wrapped in its own scroller below, so the page itself never scrolls
   sideways on a phone. */
.docs-table {
  width: 100%;
  margin: 0 0 16px;
  border-collapse: collapse;
  font-size: 12.5px;
}

.docs-table th,
.docs-table td {
  padding: 8px 10px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
}

.docs-table th {
  font-weight: 600;
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.docs-table td:first-child {
  color: var(--text-dim);
}

.docs-rules li {
  margin-bottom: 11px;
}

.docs-end {
  margin-top: 28px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  color: var(--text-dim);
}

@media (max-width: 620px) {
  .docs-body {
    padding: 14px 14px 20px;
  }
  .docs-table {
    /* Scrolls inside itself. Making the TABLE scroll rather than the page is
       what keeps the body from moving sideways under a thumb. */
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* --- Cross-browser and mobile ---------------------------------------------
   The app was built and only ever looked at on a wide desktop screen. These
   are the things that break elsewhere. */

/* iOS Safari zooms the whole page when a focused input's font is under 16px,
   and then leaves it zoomed. Every input here is 13px, so tapping any field on
   an iPhone threw the layout off and the user had to pinch back. Only applies
   to touch devices so the desktop type scale is untouched. */
@media (hover: none) and (pointer: coarse) {
  input[type='text'],
  input[type='number'],
  input[type='password'],
  input[type='datetime-local'],
  select,
  textarea {
    font-size: 16px;
  }

  /* Apple's minimum comfortable target. The tiny buttons were 2px/7px. */
  button {
    min-height: 34px;
  }
  button.tiny {
    min-height: 28px;
  }
}

/* Long addresses, hashes and token names are the one kind of content here that
   can be wider than a phone. Nothing may push the page sideways.

   NOT overflow-x: hidden on html/body. That makes them a scroll container,
   which silently kills position: sticky on every descendant — including the
   status strip at the top. It is the standard "fixed my horizontal scroll and
   broke my sticky header" trap, and I walked into it a minute ago.

   Overflow is handled where it originates instead: long strings wrap, and wide
   tables scroll inside themselves. */
code,
.mono,
.hop-address,
.wallet-address {
  overflow-wrap: anywhere;
}

@media (max-width: 700px) {
  .wrap {
    padding: 10px 12px 48px;
  }

  /* The tab bar overflowed once Docs was added as a fourth item. It scrolls
     horizontally now rather than wrapping into two rows and pushing the
     content down. */
  .tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar {
    display: none;
  }
  .tab {
    flex: none;
  }

  /* Sticky plus a tall wrapped strip eats a phone screen. */
  .strip {
    position: static;
    gap: 4px 12px;
    padding: 8px 10px;
  }

  .panel-body {
    padding: 10px 12px;
  }

  /* Buttons in a row go full width rather than squeezing to unreadable. */
  .btn-row > button {
    flex: 1 1 100%;
  }
}

/* Any table that is not already inside .table-scroll. Applied at the element
   level so a table added later cannot silently overflow. */
@media (max-width: 700px) {
  .table-scroll,
  .panel table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
}

/* Firefox and Safari ignore scrollbar-color/-width silently; harmless there
   and stops the default heavy scrollbar on Chrome/Edge from cropping content. */
* {
  scrollbar-width: thin;
}

/* Safari wants the prefixed value first, then the standard one.
   Declaring ONLY the prefixed value here would have overridden the
   position: sticky set earlier in this file, because later rules win — the fix
   would have become the bug it was meant to prevent. */
.strip {
  position: -webkit-sticky;
  position: sticky;
}

/* Respect a reader who has asked for less motion. The red status dot pulses
   forever, which is exactly the kind of thing that setting exists for. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* --- Launch panel (PONS, Robinhood only) ----------------------------------
   A <details> so it collapses out of the way. Most visits to this page are to
   buy something, and a launch form sitting open above the token box is a form
   in the way of the thing you came for. */
.launch-panel > summary {
  cursor: pointer;
  list-style: none;
}
.launch-panel > summary::-webkit-details-marker {
  display: none;
}
.launch-panel > summary::after {
  content: '▸';
  margin-left: auto;
  color: var(--text-faint);
}
.launch-panel[open] > summary::after {
  content: '▾';
}

/* The part that spends money, set apart from the metadata above it. */
.launch-deployer {
  margin-top: 14px;
  padding: 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
}

.launch-result {
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.launch-warn {
  margin-top: 6px;
  padding-left: 8px;
  border-left: 2px solid var(--amber);
  color: #e8c46a;
}

.launch-panel textarea {
  resize: vertical;
}

/* --- Launch panel, laid out as three steps --------------------------------
   The first version was one long column of fifteen fields, which reads as a
   wall and gives no sense of what is required versus optional. Numbered steps
   separate the three decisions being made: what the token is, who pays for it,
   and who buys it afterwards. */

.launch-body {
  padding: 14px 16px 18px;
}

.launch-facts {
  margin: 0 0 14px;
  padding: 9px 12px;
  border-left: 2px solid rgba(78, 161, 255, 0.5);
  background: rgba(78, 161, 255, 0.06);
  border-radius: 0 4px 4px 0;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.5;
}

.launch-step {
  display: flex;
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid var(--line-soft);
}
.launch-step:first-of-type {
  border-top: none;
  padding-top: 0;
}

.launch-step-n {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.launch-step-body {
  flex: 1;
  min-width: 0;
}

.launch-step-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.launch-step-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
}

/* Logo: preview beside the field, so a wrong or dead URL is visible before it
   becomes a permanent string on a token contract. */
.launch-logo-row {
  display: flex;
  gap: 12px;
  margin-top: 10px;
  align-items: flex-start;
}

.launch-logo-preview {
  flex: none;
  width: 64px;
  height: 64px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: #0a0e13;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  text-align: center;
  font-size: 10px;
  line-height: 1.3;
  color: var(--text-faint);
}
.launch-logo-preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.launch-logo-preview .bad {
  color: var(--red);
}

.launch-logo-fields {
  flex: 1;
  min-width: 0;
}

.launch-socials {
  margin-top: 12px;
}
.launch-socials > summary {
  cursor: pointer;
  font-size: 11.5px;
  color: var(--text-faint);
  list-style: none;
  user-select: none;
}
.launch-socials > summary::-webkit-details-marker {
  display: none;
}
.launch-socials > summary::before {
  content: '+ ';
}
.launch-socials[open] > summary::before {
  content: '− ';
}
.launch-socials > summary:hover {
  color: var(--text-dim);
}

/* The buy checklist. A wrapping grid rather than a list, because fifteen
   wallets stacked vertically pushes the Launch button off screen — and the
   button you need after ticking boxes should be visible while you tick them. */
.lp-wallets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 6px 10px;
  margin-top: 10px;
  max-height: 220px;
  overflow-y: auto;
}

.lp-wallet {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 7px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12.5px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
}
.lp-wallet:hover {
  background: rgba(255, 255, 255, 0.04);
}
.lp-wallet:has(input:checked) {
  background: rgba(63, 185, 80, 0.09);
}

.lp-wallet input[type='checkbox'] {
  width: auto;
  min-width: 0;
  margin: 0;
  flex: none;
  accent-color: var(--green);
}

.lp-wallet-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lp-wallet-bal {
  color: var(--text-faint);
  font-size: 11.5px;
  flex: none;
}

/* A wallet with nothing in it cannot buy. Dimmed rather than hidden, so it is
   obvious why ticking it would not help. */
.lp-wallet.empty .lp-wallet-name,
.lp-wallet.empty .lp-wallet-bal {
  color: var(--text-faint);
}

/* Not enough for the buy plus the gas reserve. Distinct from empty: the wallet
   has money in it, which is exactly why the shortfall is easy to miss. */
.lp-wallet.short {
  border-color: rgba(210, 153, 34, 0.5);
}

.lp-wallet-warn {
  color: var(--amber);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  flex: none;
  white-space: nowrap;
}

/* The buy summary turns amber when a picked wallet cannot cover its own buy. */
#lp-buy-summary.warn {
  color: var(--amber);
}

.launch-result {
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.launch-warn {
  margin-top: 6px;
  padding-left: 8px;
  border-left: 2px solid var(--amber);
  color: #e8c46a;
}

.launch-panel textarea {
  resize: vertical;
}

@media (max-width: 620px) {
  .launch-step {
    gap: 8px;
  }
  .launch-body {
    padding: 12px;
  }
}

/* --- Wallet link map -----------------------------------------------------

   A force-laid graph instead of a list, because "are any of my wallets
   connected to each other" is a shape question and a list is the wrong shape
   for it. Fifteen rows of prose about pairs is something you have to read and
   hold in your head; two clusters with a line between them is something you
   see.

   Deliberately restrained: same palette as everything else, no gradients, no
   drop shadows. The animation settles and stops rather than drifting forever —
   perpetual motion on a page about money reads as decorative and makes the
   labels hard to read.                                                      */

.bubblemap-wrap {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  position: relative;
}

.bubblemap-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;
}

.bubblemap-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--text-dim);
  flex-wrap: wrap;
}

.bmk {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.bmk + .bmk {
  margin-left: 8px;
}
.bmk-good {
  background: var(--green);
}
.bmk-warn {
  background: var(--amber);
}
.bmk-bad {
  background: var(--red);
}
.bmk-sep {
  color: var(--text-faint);
  margin: 0 2px;
}
.bmk-note {
  color: var(--text-faint);
}

.bubblemap {
  display: block;
  width: 100%;
  height: 380px;
  touch-action: none;
}

/* Lines sit under the bubbles so a node is never obscured by its own edges. */
.bm-link {
  stroke: var(--line);
  fill: none;
}
.bm-link.strong {
  stroke: var(--red);
  stroke-width: 2;
  opacity: 0.75;
}
.bm-link.medium {
  stroke: var(--amber);
  stroke-width: 1.5;
  opacity: 0.6;
}
.bm-link.weak {
  stroke: var(--text-faint);
  stroke-width: 1;
  opacity: 0.45;
  stroke-dasharray: 3 3;
}
.bm-link.lit {
  opacity: 1;
  stroke-width: 2.5;
}
.bm-link.dimmed {
  opacity: 0.08;
}

.bm-node {
  cursor: pointer;
}
.bm-bubble {
  stroke-width: 1.5;
  transition: opacity 0.12s;
}
.bm-node.dimmed .bm-bubble,
.bm-node.dimmed .bm-name {
  opacity: 0.18;
}
.bm-node:focus {
  outline: none;
}
.bm-node:focus .bm-bubble {
  stroke: var(--text);
  stroke-width: 2.5;
}

.bm-name {
  font-family: var(--mono);
  font-size: 10.5px;
  fill: var(--text-dim);
  text-anchor: middle;
  pointer-events: none;
  user-select: none;
}
.bm-node.lit .bm-name {
  fill: var(--text);
}

/* Hover card. Positioned in JS against the wrapper, and kept inside it so it
   cannot spill off a narrow screen. */
.bubble-tip {
  position: absolute;
  z-index: 20;
  max-width: 320px;
  padding: 9px 11px;
  background: var(--panel-2, var(--panel));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text);
  pointer-events: none;
}
.bubble-tip h5 {
  margin: 0 0 5px;
  font-size: 12.5px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.bubble-tip .tip-addr {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
  margin-bottom: 6px;
  word-break: break-all;
}
.bubble-tip .tip-line {
  color: var(--text-dim);
  margin: 2px 0;
}
.bubble-tip .tip-fix {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--line-soft);
  color: var(--text);
}
.bubble-tip .tip-fix strong {
  color: var(--green);
}
.bubble-tip .tip-none {
  color: var(--text-faint);
}

.bubblemap-empty {
  padding: 26px 12px;
  text-align: center;
  color: var(--text-faint);
  font-size: 12px;
}

@media (max-width: 640px) {
  .bubblemap {
    height: 300px;
  }
  .bmk-note {
    display: none;
  }
}

/* --- Keyboard focus ------------------------------------------------------

   There was no :focus rule for a button anywhere in this stylesheet. Inputs had
   one; the controls that SPEND MONEY did not. So anybody navigating by keyboard
   — by preference, by injury, or because a trackpad died mid-launch — was
   tabbing blind across Buy, Sell all and Remove wallet with no indication of
   where they were.

   :focus-visible rather than :focus, so a mouse click does not leave a ring
   behind on a dense terminal layout. Red on the destructive controls, because
   the ring is the last thing seen before a keypress commits.                */

button:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.quick-group button:focus-visible {
  outline-offset: 1px;
}

.sell-group button:focus-visible,
.bulk-btn:focus-visible,
button.danger:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 1px;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 1px;
}

/* The map's own ring, kept, but no longer suppressed for keyboard users. */
.bm-node:focus-visible .bm-bubble {
  stroke: var(--text);
  stroke-width: 2.5;
}

/* An error is not a hint.
   The login page carried its only failure message in .hint — the faintest
   colour in the system, dimmer than body text. Somebody who mistyped a password
   was told so in the least legible way the design allows. */
.login-error {
  color: var(--red);
  font-size: 13px;
  margin-top: 10px;
  line-height: 1.45;
}

/* ======================================================================
   BRAND V2 OVERRIDE LAYER — Buckshot azure/violet system.
   Appended last on purpose: presentation only, no selector renames,
   no layout-structure changes. Everything above still applies unless
   overridden here.
   ====================================================================== */

/* ambient tint — static, zero-cost */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(55% 38% at 12% 0%, rgba(29, 78, 216, 0.11), transparent 62%),
    radial-gradient(50% 40% at 92% 100%, rgba(109, 40, 217, 0.09), transparent 62%);
}

::selection { background: var(--blue); color: #041022; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #202a3e; border-radius: 8px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: #2b3852; }

/* chrome text goes sans; data stays mono */
.tabs, .tab, .panel-title, button, label, .field-label,
.strip-label, .strip-config, .chain-trigger, .chain-option,
.tag, .toast, .modal-head, .banner, .nav-brand, .who, .tab-note,
.bulk-label, .preset .preset-name {
  font-family: var(--sans);
}

/* --- status strip: glass ---------------------------------------------- */
.strip {
  background: rgba(12, 17, 25, 0.82);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-color: rgba(43, 55, 82, 0.7);
  border-radius: 14px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.35);
}
.dot.green { box-shadow: 0 0 8px rgba(52, 211, 153, 0.7); }
.dot.red { box-shadow: 0 0 8px rgba(248, 113, 113, 0.7); }
.dot.amber { box-shadow: 0 0 8px rgba(251, 191, 36, 0.6); }

/* --- nav: brand + pill tabs ------------------------------------------- */
.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.01em;
  color: var(--text);
  margin-right: 14px;
  user-select: none;
}
.nav-brand svg { display: block; }

/* Tabs become a segmented control. The v1 folder-tab metaphor (bottom
   border + notched active tab) is fully neutralized here — those base
   properties are explicitly reset so nothing fights the pill shape. */
.tabs {
  gap: 4px;
  align-items: center;
  margin-top: 16px;
  margin-bottom: 20px;
  padding: 5px;
  border-bottom: none !important;
  background: rgba(12, 17, 25, 0.7);
  border: 1px solid var(--line);
  border-radius: 999px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.tab {
  border: 1px solid transparent !important;
  border-radius: 999px !important;
  padding: 8px 18px !important;
  margin-bottom: 0 !important;
  background: transparent !important;
  font-weight: 600;
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0.005em;
  color: var(--text-dim);
  transition: background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
}
.tab:hover {
  background: rgba(255, 255, 255, 0.05) !important;
  color: var(--text);
  text-decoration: none;
}
.tab.active {
  background: var(--grad) !important;
  color: #071022 !important;
  border-color: transparent !important;
  box-shadow: 0 2px 12px rgba(77, 159, 255, 0.35);
}
.tab.active:hover { color: #071022; }
/* the brand mark sits inside .tabs now — keep it flush, not pill-padded */
.tabs .nav-brand { padding: 0 10px 0 8px; margin-right: 2px; }
.tab-spacer { flex: 1; }
.who, .tab-note { color: var(--text-dim); font-size: 12px; }
#btn-logout { margin-left: 8px; }

/* --- panels ------------------------------------------------------------ */
.panel {
  border-radius: 16px;
  border-color: rgba(28, 36, 52, 0.9);
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.28);
}
.panel-head { border-radius: 16px 16px 0 0; }
.panel-title { font-weight: 700; letter-spacing: 0.01em; }

/* --- buttons ------------------------------------------------------------ */
button {
  border-radius: 999px;
  transition: transform 0.12s ease, border-color 0.16s ease,
    background 0.16s ease, box-shadow 0.16s ease, color 0.16s ease;
}
button:hover:not(:disabled) { border-color: rgba(77, 159, 255, 0.55); }
button:active:not(:disabled) { transform: translateY(1px); }
button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(77, 159, 255, 0.45);
}

/* Semantics preserved from v1: GREEN means "this spends money" (buys, launch,
   saves that commit). The brand gradient is reserved for actions that cannot
   spend — signing in. Do not put the gradient on a buy button. */
button.primary,
.quick-buy,
.bulk-btn.buy {
  background: rgba(52, 211, 153, 0.16);
  border: 1px solid rgba(52, 211, 153, 0.5);
  color: #7ce8bd;
  font-weight: 700;
}
button.primary:hover:not(:disabled),
.quick-buy:hover:not(:disabled),
.bulk-btn.buy:hover:not(:disabled) {
  background: rgba(52, 211, 153, 0.26);
  border-color: rgba(52, 211, 153, 0.75);
  box-shadow: 0 3px 18px rgba(52, 211, 153, 0.25);
  transform: translateY(-1px);
}

#btn-signin {
  background: var(--grad);
  border: none;
  color: #071022;
  font-weight: 700;
  box-shadow: 0 3px 16px rgba(77, 159, 255, 0.3);
}
#btn-signin:hover:not(:disabled) {
  box-shadow: 0 6px 24px rgba(77, 159, 255, 0.45);
  transform: translateY(-1px);
}

/* --- inputs -------------------------------------------------------------- */
input, select, textarea {
  border-radius: 10px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: rgba(77, 159, 255, 0.65);
  box-shadow: 0 0 0 3px rgba(77, 159, 255, 0.14);
}

/* --- tables --------------------------------------------------------------- */
th {
  font-family: var(--sans);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
}
tbody tr { transition: background 0.12s ease; }
tbody tr:hover td { background: rgba(77, 159, 255, 0.045); }
.table-scroll { -webkit-overflow-scrolling: touch; border-radius: 12px; }

/* --- chips / tags / toasts -------------------------------------------------- */
.chip { border-radius: 999px; transition: border-color 0.15s ease, background 0.15s ease; }
.chip:hover { border-color: rgba(77, 159, 255, 0.5); background: rgba(77, 159, 255, 0.08); }
.tag { border-radius: 999px; font-weight: 600; }
.toast { border-radius: 12px; backdrop-filter: blur(8px); box-shadow: 0 16px 44px rgba(0, 0, 0, 0.45); }
.modal { border-radius: 18px; box-shadow: 0 40px 110px rgba(0, 0, 0, 0.6); }
.modal-back { backdrop-filter: blur(4px); }
.preset { border-radius: 12px; }
.preset.active { box-shadow: inset 0 0 0 1px rgba(77, 159, 255, 0.4); }
.banner { border-radius: 12px; }
.pager-btn { border-radius: 999px; }
.chain-trigger { border-radius: 999px; font-weight: 600; }
.chain-list { border-radius: 14px; overflow: hidden; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5); }

/* --- login -------------------------------------------------------------------- */
.login-wrap {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  padding: 24px;
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 20px;
  color: var(--text);
}
.login-panel {
  width: min(400px, 92vw);
  border-radius: 20px;
  background: rgba(12, 17, 25, 0.85);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.55), 0 0 40px rgba(77, 159, 255, 0.08);
}
.login-panel .panel-head { justify-content: center; }
.login-panel button.primary { width: 100%; padding: 11px 0; margin-top: 4px; }
.login-error { color: var(--red); font-size: 12.5px; min-height: 18px; margin: 10px 0 0; }

/* --- mobile ----------------------------------------------------------------------- */
@media (max-width: 760px) {
  .wrap { padding: 10px 12px 48px; }
  .strip { gap: 4px 12px; padding: 8px 12px; font-size: 12px; border-radius: 12px; }
  .strip-config { display: none; }
  .tabs { overflow-x: auto; white-space: nowrap; scrollbar-width: none; padding-bottom: 2px; }
  .tabs::-webkit-scrollbar { display: none; }
  .tab { padding: 9px 14px; flex: none; }
  .nav-brand { margin-right: 8px; }
  .nav-brand span { display: none; }
  .cols { display: block; }
  .cols .panel { margin-bottom: 14px; }
  button { min-height: 36px; }
  input, select { min-height: 38px; font-size: 14px; }
  .token-stats { grid-template-columns: 1fr 1fr; }
  .modal { width: min(94vw, 560px); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ======================================================================
   UX PRIMITIVES — shared layout vocabulary that replaces inline styles.
   ====================================================================== */

/* actions aligned beside labelled inputs inside a .row */
.row-actions {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  flex: none !important;
}

/* section sub-headers inside long panel bodies */
.sub-head {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 22px 0 12px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.sub-head:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* destructive flows grouped and visually quarantined */
.danger-zone {
  border: 1px solid rgba(248, 113, 113, 0.28);
  background: rgba(248, 113, 113, 0.04);
  border-radius: 12px;
  padding: 16px;
  margin-top: 22px;
}
.danger-zone .sub-head,
.danger-zone > .sub-head:first-child {
  color: #ff9b95;
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}
.danger-zone .sub-head + .sub-head,
.danger-zone .dz-divider {
  border-top: 1px solid rgba(248, 113, 113, 0.18);
  margin-top: 18px;
  padding-top: 16px;
}

/* hint variants — live output is not documentation */
.hint-warn { color: #eccd77; }
.live-note {
  background: rgba(77, 159, 255, 0.06);
  border: 1px solid rgba(77, 159, 255, 0.18);
  border-radius: 10px;
  padding: 9px 12px;
}
.live-note:empty { display: none; border: none; padding: 0; }

/* spacing utilities replacing the inline margin-top ladder */
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.flex-none { flex: none !important; }
.section-divider {
  margin-top: 20px;
  border-top: 1px solid var(--line-soft);
  padding-top: 16px;
}

/* ======================================================================
   REDESIGN LAYER v3 — the real visual overhaul.
   Appended last so it wins. DOM-hook-safe: only properties, no renames.
   Goal: high-tier, spacious, legible — not the cramped 13px mono grid.
   ====================================================================== */

body {
  font-size: 14px;
  line-height: 1.6;
  color: var(--text);
}
.wrap { max-width: 1320px; padding: 18px 28px 90px; }

/* --- status strip: taller, quieter, legible --- */
.strip {
  padding: 13px 20px;
  gap: 8px 22px;
  font-size: 12.5px;
  border-radius: 16px;
  margin-bottom: 4px;
}
.strip-label { color: var(--text-faint); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; }
.strip-value { color: var(--text); font-weight: 600; }

/* --- panels: elevated cards with a hairline gradient top edge --- */
.panel {
  border-radius: 18px;
  margin-top: 18px;
  background: linear-gradient(180deg, rgba(17, 23, 36, 0.6), rgba(12, 17, 25, 0.6));
  border: 1px solid rgba(43, 55, 82, 0.55);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.3);
  position: relative;
}
.panel-head {
  padding: 16px 22px;
  background: transparent;
  border-bottom: 1px solid var(--line-soft);
}
.panel-title {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text);
}
.panel-body { padding: 22px; }

/* --- token header + stats become a real stat-card grid --- */
.token-header {
  flex-direction: column;
  align-items: stretch;
  gap: 18px;
  padding: 22px;
  background: transparent;
  border-top: none;
}
.token-id { gap: 12px; }
.token-ticker { font-size: 20px; font-weight: 800; letter-spacing: -0.02em; }
.token-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  width: 100%;
}
.token-stats > div {
  gap: 6px;
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}
.token-stats dt { font-size: 10px; letter-spacing: 0.1em; }
.token-stats dd { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }

/* --- data tables: air, clear rows, sticky header --- */
table { font-size: 13px; }
thead th {
  position: sticky;
  top: 0;
  background: rgba(12, 17, 25, 0.96);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  padding: 12px 14px;
  font-size: 10px;
  letter-spacing: 0.13em;
  z-index: 1;
}
td { padding: 13px 14px; border-bottom: 1px solid rgba(255, 255, 255, 0.045); }
tbody tr:hover td { background: rgba(77, 159, 255, 0.05); }
.num { font-variant-numeric: tabular-nums; }

/* --- wallet rows in the list: card-like, hover lift --- */
.wallet {
  padding: 14px 16px;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  margin-bottom: 8px;
  background: rgba(255, 255, 255, 0.02);
  transition: border-color 0.15s ease, transform 0.15s ease, background 0.15s ease;
}
.wallet:hover {
  border-color: rgba(77, 159, 255, 0.35);
  background: rgba(77, 159, 255, 0.04);
  transform: translateX(2px);
}
.wallet-swatch { width: 11px; height: 11px; border-radius: 4px; box-shadow: 0 0 8px currentColor; }
.wallet-label { font-size: 14px; font-weight: 600; }
.wallet-key { font-size: 12px; color: var(--text-faint); }
.wallet-bal { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- section sub-heads: brighter, with an accent tick --- */
.sub-head {
  font-size: 12px;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 10px;
}
.sub-head::before {
  content: "";
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: var(--grad);
}

/* --- empty states: centered, friendlier --- */
.empty {
  padding: 44px 22px;
  text-align: center;
  color: var(--text-dim);
  font-size: 14px;
}

/* --- token mint bar: bigger target --- */
.mint-bar input { padding: 11px 14px; font-size: 14px; }

/* --- bubblemap: framed, breathing --- */
.bubblemap-wrap {
  border-radius: 16px;
  border: 1px solid var(--line-soft);
  background: rgba(255, 255, 255, 0.015);
  overflow: hidden;
}

/* --- inputs / selects: comfortable height --- */
input[type='text'], input[type='number'], input[type='password'],
input[type='datetime-local'], select, textarea {
  padding: 11px 13px;
  font-size: 13.5px;
  background: rgba(6, 8, 15, 0.6);
}
/* The UA widget inside a datetime input keeps its own colours unless told
   otherwise, which is what leaves a light calendar glyph invisible on dark. */
input[type='datetime-local'] { color-scheme: dark; }

/* --- danger-zone: stronger presence --- */
.danger-zone {
  padding: 20px;
  border-radius: 16px;
  border-width: 1px;
}

/* --- foot --- */
.foot { margin-top: 36px; color: var(--text-faint); font-size: 12px; text-align: center; }

/* --- mobile: keep the redesign coherent small --- */
@media (max-width: 760px) {
  .wrap { padding: 12px 14px 60px; }
  .panel-body { padding: 16px; }
  .token-header { padding: 16px; }
  .token-stats { grid-template-columns: repeat(2, 1fr); }
  .strip { padding: 11px 14px; }
  .panel-title { font-size: 14px; }
}

/* ======================================================================
   POLISH LAYER v4 — expandables, selects, badges, modals.
   Fixes every collapsible/expand state and the last rough component edges.
   ====================================================================== */

/* --- launch step number badges: gradient chips --- */
.launch-step-n {
  width: 28px;
  height: 28px;
  border: none;
  background: var(--grad);
  color: #071022;
  font-weight: 800;
  font-size: 13px;
  box-shadow: 0 3px 12px rgba(77, 159, 255, 0.3);
}
.launch-step-title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
}

/* --- native selects: custom caret, consistent with inputs --- */
select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 18px) center, calc(100% - 13px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 34px !important;
  cursor: pointer;
}
select[multiple] { background-image: none; padding-right: 13px !important; }

/* --- <details> everywhere: a real card-summary with rotating chevron --- */
.panel details:not(.launch-panel):not([id]) > summary,
.launch-socials > summary,
.hop-restore > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-dim);
  font-weight: 600;
  font-size: 13px;
  list-style: none;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.panel details:not(.launch-panel):not([id]) > summary:hover,
.launch-socials > summary:hover,
.hop-restore > summary:hover {
  border-color: rgba(77, 159, 255, 0.35);
  background: rgba(77, 159, 255, 0.05);
  color: var(--text);
}
/* one consistent chevron, replacing the ad-hoc ::before markers */
.launch-socials > summary::before,
.hop-restore > summary::before,
.panel details:not(.launch-panel):not([id]) > summary::before {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.18s ease;
  margin-right: 2px;
}
.launch-socials[open] > summary::before,
.hop-restore[open] > summary::before,
.panel details:not(.launch-panel):not([id])[open] > summary::before {
  transform: rotate(45deg);
}
/* expanded content gets breathing room below the summary */
.launch-socials[open] > summary,
.hop-restore[open] > summary { margin-bottom: 14px; }
.hop-restore textarea { margin-top: 0; }

/* --- launch panel top-level summary: header row, rotating caret --- */
.launch-panel > summary { padding: 16px 22px; }
.launch-panel > summary::-webkit-details-marker { display: none; }

/* --- modal: generous, centered, polished --- */
.modal { width: min(94vw, 560px); }
.modal-head { padding: 18px 22px; font-size: 15px; font-weight: 700; }
.modal-body { padding: 22px; }
.modal-body p { line-height: 1.6; }
.kv { gap: 8px 16px; }
.kv dt { color: var(--text-faint); }
.kv dd { font-weight: 600; font-variant-numeric: tabular-nums; }

/* --- confirm dialog rows: clearer --- */
.confirm-row { padding: 11px 0; }
.confirm-total { font-weight: 700; font-size: 15px; }

/* --- presets: card feel, active glow --- */
.preset { padding: 14px 16px; transition: border-color 0.15s ease, background 0.15s ease; }
.preset:hover { border-color: rgba(77, 159, 255, 0.35); background: rgba(77, 159, 255, 0.04); }
.preset .preset-name { font-size: 14px; font-weight: 700; }

/* --- feed rows: readable telemetry --- */
.feed-row { padding: 11px 16px; gap: 12px; }
.feed-kind { font-weight: 700; letter-spacing: 0.06em; }

/* --- banners: align with card system --- */
.banner { padding: 13px 18px; border-radius: 14px; font-size: 13.5px; }

/* --- inline hint-warn (replaces remaining inline amber colors gracefully) --- */
p.hint[style*="e8c46a"] { color: #eccd77 !important; }

/* --- docs: cap measure, center, so it doesn't strand right-side space --- */
.docs-body { max-width: 760px; }

/* --- keep chevrons/carets still for reduced motion --- */
@media (prefers-reduced-motion: reduce) {
  .launch-socials > summary::before,
  .hop-restore > summary::before,
  .panel details > summary::before { transition: none; }
}

/* ======================================================================
   REAL-DATA FIXES v5 — from inspecting the LIVE app with real content.
   Each fix addresses a bug that only appears with actual data, not mocks.
   ====================================================================== */

/* (1) Token stat cards: long real values like the Robinhood route
   "Kyberswap +0.25% fee" were set nowrap and overflowed into the next
   card. Let them wrap; size down slightly so two lines fit cleanly. */
.token-stats { grid-template-columns: repeat(auto-fit, minmax(134px, 1fr)); }
.token-stats dd {
  white-space: normal;
  overflow-wrap: break-word;
  font-size: 15px;
  line-height: 1.32;
}

/* (2) Wallet Lab rows: the colour dot sat at the top of a tall row (label +
   address + two-line balance + action buttons). Centre it, and let the
   buttons wrap on narrow screens instead of overflowing. */
.wallet { align-items: center; flex-wrap: wrap; gap: 10px 12px; }
.wallet-swatch { align-self: center; }
.wallet-main { flex: 1 1 180px; min-width: 0; }

/* (3) Launch wallet checklist: was a loose inline run of checkbox+name+number
   with ragged gaps. Make each wallet a bordered chip, balance flushed right. */
.lp-wallets {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 8px;
  max-height: 264px;
}
.lp-wallet {
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.02);
  font-size: 13px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.lp-wallet:hover {
  border-color: rgba(77, 159, 255, 0.35);
  background: rgba(77, 159, 255, 0.05);
}
.lp-wallet-name { font-weight: 600; }
.lp-wallet-bal {
  margin-left: auto;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.lp-wallet.empty { opacity: 0.5; }
.lp-wallet.short .lp-wallet-bal { color: var(--amber); }

/* (4) Docs: content was capped at 760px and jammed left, stranding half the
   panel. Centre a wider comfortable measure so the space reads intentional. */
.docs-body { max-width: 900px; margin-left: auto; margin-right: auto; }
.docs-toc { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* (5) Settings density: the page sprawled because every control carried a
   multi-line paragraph at body size. Make guidance genuinely secondary and
   tighten the vertical rhythm so it scans. */
.hint { font-size: 12px; line-height: 1.5; margin-top: 6px; color: var(--text-dim); }
#page-settings .panel-body { padding: 18px 22px; }
#page-settings .row { margin-top: 14px; }
.preset { padding: 12px 14px; }
.preset .preset-summary { font-size: 11.5px; color: var(--text-dim); }
.preset .preset-nums { font-size: 11px; }

/* ======================================================================
   COMPREHENSIVE SWEEP v6 — from the full multi-agent UI audit.
   Every item is a presentation fix; no id/class JS depends on is renamed.
   Numbered to the audit's master build list.
   ====================================================================== */

/* (1) Compact table/bulk inputs were inflated by the v3 number-input rule. */
.manual-buy input,
.bulk-bar input { padding: 5px 8px; font-size: 12px; }

/* (2) Buttons that are direct .row children ballooned to flex:1; the explorer
   link had no button chrome. */
.sweep-picker > .row > *,
.hop-root > .row > *,
.hop-restore > .row > * { flex: 0 0 auto; }
.button-like {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line2); border-radius: 999px;
  padding: 9px 16px; background: rgba(11, 15, 23, 0.5);
  color: var(--text); font-family: var(--sans); font-size: 13px;
  font-weight: 600; line-height: 1;
}
.button-like:hover { border-color: var(--blue); color: var(--blue2); text-decoration: none; }

/* (3) Destructive section headings should read as destructive. */
.danger-zone > .field-label:first-child,
.hop-replace > .field-label:first-child {
  color: #ff9b95; font-size: 12px; font-weight: 700;
  letter-spacing: 0.06em; margin-bottom: 6px;
}

/* (4) Settings credential saves: consistent green commit weight. */
#btn-save-jupkey, #btn-save-ethrpc, #btn-save-lifikey, #btn-own-password {
  background: rgba(52, 211, 153, 0.16);
  border: 1px solid rgba(52, 211, 153, 0.5);
  color: #7ce8bd; font-weight: 700;
}
#btn-save-jupkey:hover:not(:disabled), #btn-save-ethrpc:hover:not(:disabled),
#btn-save-lifikey:hover:not(:disabled), #btn-own-password:hover:not(:disabled) {
  background: rgba(52, 211, 153, 0.26); border-color: rgba(52, 211, 153, 0.75);
}

/* (5) The always-visible DRY RUN / LIVE banner title must not break mid-word. */
.banner { align-items: flex-start; }
.banner strong { white-space: nowrap; flex: none; }

/* (6) Wallet table: match the redesign's cell padding + align to the 22px
   panel gutter so the workspace reads as one panel, not staircased bands. */
.wallet-table td { padding: 10px 14px; }
.token-header, .bulk-bar { padding-left: 22px; padding-right: 22px; }
.wallet-table th:first-child, .wallet-table td:first-child { padding-left: 22px; }
.wallet-table th:last-child, .wallet-table td:last-child { padding-right: 22px; }

/* (7) Transient notices as inset callouts; stat grid keeps trailing tracks;
   heads wrap; sticky headers clear the 52px topbar; orphaned pill spacing. */
.inline-warn, .bulk-progress { margin: 12px 22px 0; border-radius: 12px; }
.inline-warn { border: 1px solid rgba(210, 153, 34, 0.30); border-left: 3px solid rgba(210, 153, 34, 0.75); }
.bulk-progress { border: 1px solid var(--line-soft); border-left: 3px solid var(--blue); }
.token-stats { grid-template-columns: repeat(auto-fill, minmax(134px, 1fr)); }
.panel-head { flex-wrap: wrap; row-gap: 4px; }
#positions-summary, #token-status { min-width: 0; }
/* v3 made headers sticky at top:0, which slid them under the 52px topbar; a
   top:52px offset then overlapped the first data row. Static headers scroll
   with the table — no collision, no overlap. */
thead th { position: static; }
.tag.orphaned + button { margin-left: 8px; }

/* (8) Wallet-lab: card the add/generate boxes, soften hop radii, tame muted
   subtitles, size the list toggle, wrap actions, stack RH balance on mobile. */
#import-box, #walletgen-box {
  border: 1px solid var(--line-soft); border-radius: 14px;
  padding: 16px; background: rgba(255, 255, 255, 0.02);
}
.hop-address, .hop-phrase, .hop-restore textarea { border-radius: 10px; }
.hop-root { border-radius: 12px; }
.panel-title .muted { font-weight: 400; font-size: 12.5px; letter-spacing: 0; text-transform: none; }
button.small { padding: 5px 11px; font-size: 11.5px; }
.row-actions { flex-wrap: wrap; }

/* (10) Accounts cell spacing; settings controls grid; audit sub-cards. */
#accounts-body td:first-child .tag { margin-left: 6px; }
#accounts-body td:last-child { white-space: nowrap; }
#accounts-body td:last-child button + button { margin-left: 6px; }
#page-settings .controls { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.wallet-risk, .link-row, .audit-scope { border-radius: 14px; border-color: var(--line-soft); }

/* (11) Mobile: unpin the tall header; keep chain switcher from stretching;
   docs anchors clear the sticky header. */
.topbar { align-items: center; }
.chain-drop, .chain-trigger { height: auto; }
.chain-trigger { padding-top: 9px; padding-bottom: 9px; }
.docs-body h2, .docs-body h3 { scroll-margin-top: 120px; }

/* (12) Bulk-bar divider as a pip; △ gap; bulk sells read red like row sells. */
.bulk-sep { align-self: center; height: 20px; margin: 0 6px; }
.mismatch { margin-left: 0; }
.bulk-btn[data-bulk="sell"]:not(.danger) {
  background: rgba(248, 81, 73, 0.10); border-color: rgba(248, 81, 73, 0.30); color: #ff9b95;
}

/* (13) Revealed recovery phrase reads as one quarantined secret block. */
#hop-phrase-box {
  background: rgba(232, 196, 106, 0.05); border: 1px solid rgba(232, 196, 106, 0.2);
  border-radius: 12px; padding: 12px 14px; margin-top: 12px;
}

/* (14) Low-severity polish. */
.strength { font-size: 10.5px; padding: 1px 6px; }
.modal-head { position: sticky; top: 0; z-index: 1; }
.modal-body > .btn-row { justify-content: flex-end; }
.banner-close { opacity: 0.75; }

@media (max-width: 760px) {
  .toast-stack { left: 12px; right: 12px; max-width: none; }
  .tab-note { display: none; }
}
@media (max-width: 700px) {
  .topbar { position: static; }
}

/* ======================================================================
   ALERTS v7 — banners were three inline flex children (label / message /
   close) which read as disconnected blobs. Restructure as a proper alert:
   accent bar, uppercase label, message beneath, close pinned top-right.
   ====================================================================== */
.banner {
  display: block;
  position: relative;
  padding: 13px 46px 13px 18px;
  border-left-width: 3px;
  border-radius: 12px;
  line-height: 1.5;
}
.banner strong {
  display: block;
  margin-bottom: 2px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: normal;
}
.banner > span {
  display: block;
  font-size: 13.5px;
  opacity: 0.9;
}
.banner-close {
  position: absolute;
  top: 9px;
  right: 11px;
  margin: 0;
  padding: 2px 8px;
  line-height: 1.2;
}

/* ======================================================================
   SUB-TABS v8 — secondary navigation inside Settings (and later Wallet Lab).
   Lighter than the main nav pills so the hierarchy reads at a glance.
   ====================================================================== */
.subtabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 4px 0 20px;
  padding: 5px;
  background: rgba(12, 17, 25, 0.6);
  border: 1px solid var(--line);
  border-radius: 999px;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.subtab {
  padding: 8px 18px;
  border-radius: 999px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 13px;
  color: var(--text-dim);
  border: 1px solid transparent;
  transition: background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease;
}
.subtab:hover {
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  text-decoration: none;
}
.subtab.active {
  background: rgba(77, 159, 255, 0.15);
  color: var(--text);
  box-shadow: inset 0 0 0 1px rgba(77, 159, 255, 0.35);
}
/* THE HOOK EXISTED AND THE RULE DID NOT. `subtab-danger` was written on the
   Danger Zone pill in index.html and defined nowhere, so the only withdrawal
   control in the product sat behind a pill rendered in the same dim grey as
   Wallets, Funding and Privacy. Somebody meant it to stand out. */
.subtab.subtab-danger {
  color: #ff9b95;
  border-color: rgba(255, 155, 149, 0.28);
}
.subtab.subtab-danger:hover {
  background: rgba(255, 155, 149, 0.1);
  color: #ffb8b3;
}
.subtab.subtab-danger.active {
  background: rgba(255, 155, 149, 0.16);
  color: #ffd2ce;
  box-shadow: inset 0 0 0 1px rgba(255, 155, 149, 0.45);
}
[data-stab].stab-off { display: none !important; }

@media (max-width: 760px) {
  .subtabs { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .subtabs::-webkit-scrollbar { display: none; }
  .subtab { flex: none; }
}

/* ======================================================================
   TRADING TAB spacing fixes — the scope bar was cramped with mismatched
   corners, and presets butted against the applies-to select and the
   "custom settings" note. Give the section one consistent vertical rhythm.
   ====================================================================== */
.scope-note {
  padding: 11px 14px;
  border-left-width: 3px;
  border-radius: 10px;      /* full radius, was 0 4px 4px 0 */
  margin: 0 0 16px;
}
/* Applies-to (admin) needs to separate from the preset cards below it. */
#shared-row { margin-bottom: 16px; }
/* Preset cards: a touch more air between them, and clear space before the
   "custom settings" note and the slippage controls. */
#preset-list.presets { gap: 10px; }
#preset-detail { margin: 12px 0 4px; }
/* The slippage controls grid already has an inline 14px top margin; the
   surrounding rhythm above now matches it. */
.panel[data-stab="trading"] .field + .field,
.panel[data-stab="trading"] .row + .row { margin-top: 0; }

/* ======================================================================
   DOCS PAGE UPGRADE — was a wall of monospace. Now: readable sans prose
   (mono only for code/addresses), real heading hierarchy, refined
   callouts, and a sticky sidebar table of contents on wide screens.
   ====================================================================== */
.docs-body {
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.75;
  color: #c7d0de;
  max-width: 820px;
  margin: 0 auto;
}
.docs-body p { margin: 0 0 16px; }
.docs-body ul, .docs-body ol { margin: 0 0 16px; padding-left: 22px; }
.docs-body li { margin: 5px 0; }
.docs-body strong { color: var(--text); font-weight: 700; }
.docs-body em { color: var(--text); font-style: italic; }
.docs-body code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1px 6px;
  color: var(--blue2);
}
.docs-body h2 {
  font-family: var(--sans);
  font-size: 23px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 44px 0 18px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
  scroll-margin-top: 100px;
}
.docs-content > h2:first-child { border-top: none; padding-top: 0; margin-top: 4px; }
.docs-body h3 { font-family: var(--sans); font-size: 16.5px; font-weight: 700; color: var(--text); margin: 26px 0 10px; }
.docs-callout {
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.65;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 12px;
  padding: 15px 18px;
  margin: 18px 0;
  background: rgba(255, 255, 255, 0.02);
}
.docs-callout strong { color: var(--text); }
.docs-callout.danger { border-color: rgba(248, 113, 113, 0.35); border-left-color: var(--red); background: rgba(248, 113, 113, 0.05); }
.docs-callout.warn { border-color: rgba(251, 191, 36, 0.35); border-left-color: var(--amber); background: rgba(251, 191, 36, 0.05); }
.docs-callout:not(.danger):not(.warn) { border-left-color: var(--blue); background: rgba(77, 159, 255, 0.05); }
.docs-table { font-family: var(--sans); border-collapse: collapse; width: 100%; margin: 18px 0; font-size: 14px; }
.docs-table th { text-align: left; font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); padding: 10px 14px; border-bottom: 1px solid var(--line2); }
.docs-table td { padding: 11px 14px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); vertical-align: top; }
.docs-rules { font-family: var(--sans); }
.docs-rules li { margin: 10px 0; }
.docs-end { font-family: var(--sans); color: var(--faint); font-size: 14px; margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--line); }
.docs-toc { font-family: var(--sans); }
.docs-toc a {
  display: block;
  padding: 7px 12px;
  border-radius: 8px;
  color: var(--dim);
  font-size: 13.5px;
  transition: background 0.15s ease, color 0.15s ease;
}
.docs-toc a:hover { background: rgba(77, 159, 255, 0.08); color: var(--text); text-decoration: none; }
@media (min-width: 1100px) {
  .docs-body {
    display: grid;
    grid-template-columns: 210px minmax(0, 1fr);
    column-gap: 52px;
    align-items: start;
    max-width: none;
  }
  .docs-toc {
    grid-column: 1;
    position: sticky;
    top: 84px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
  }
  .docs-content { grid-column: 2; min-width: 0; max-width: 760px; }
}

/* Untracked holdings: tokens on chain with no position behind them. One line
   per holding, laid out so the wallet, the mint and the quantity read across
   without a table — there are usually none, occasionally one. */
#untracked-results { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
#untracked-results .row-line {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 12px;
}
#untracked-results .row-line .num { margin-left: auto; }

/* Copy trading roster.
   A grid rather than a flex row, so the name, address, handle and observed
   columns line up down the page. With five hundred rows, ragged columns are the
   difference between a table you can scan and a wall of text. */
.copy-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px 0 8px;
}
.copy-toolbar input[type="search"] {
  /* A search input takes the browser's own look unless every property is
     stated: rounded white pill, its own font, a cancel button that does not
     match anything else on the page. Reset rather than tweaked, or it renders
     differently in each browser. */
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 340px;
  max-width: 460px;
  /* 38, not 32. Measured 2026-08-21: every text input and select on these pages
     renders at 39px, so a 32px search box was the odd control out on a page whose
     other fields are not. The operator called them "small" and they were. */
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(6, 8, 15, 0.6);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  outline: none;
}
.copy-toolbar input[type="search"]::placeholder { color: var(--text-faint); }
.copy-toolbar input[type="search"]:focus { border-color: var(--blue); }
.copy-toolbar input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.copy-list { display: flex; flex-direction: column; gap: 1px; }
.copy-row {
  display: grid;
  /* name · address · X · score · 30d PnL · median hold · what we saw */
  /* name · address · X · score · status · 7d · 30d · win · hold · trips · sim · seen
     Fixed widths on every numeric column so nothing reflows as values change,
     and the whole row stays on ONE line at any magnitude — the counts are
     abbreviated (1.8k) rather than allowed to wrap. */
  /* THIRTEEN columns, not twelve. The renderer emits a lifecycle badge between
     the score and the status, and this template had no track for it — so every
     cell from the status rightwards sat one column left of its own heading, and
     the last one fell into an implicit track the header never knew about. */
  grid-template-columns:
    minmax(150px, 260px) 84px 16px 40px 74px 86px 56px 62px 36px 42px 44px 58px 66px 86px minmax(132px, 1fr);
  /* EIGHT, AND THE TABLE FITS. THE DUPLICATE COST 56 PIXELS.
     TOMBSTONE. This rule declared gap, padding and font-size TWICE each — 8px
     then 12px, 3px/8px then 7px/10px, 12px then 13px — so the second of each
     silently won. Fifteen tracks means fourteen gaps, and the four unintended
     pixels on each came to 56: measured 2026-08-21, the header needed 1210px
     inside a 1166px container at a 1280px viewport, so the main table of the
     Copying tab carried a horizontal scrollbar WITH NO ROWS IN IT.
     Deduplicated, keeping the 8px the first declaration always intended. No
     column was removed and no information was dropped to achieve it. */
  gap: 8px;
  padding: 7px 10px;
  font-size: 13px;
  line-height: 1.25;
  align-items: center;
  border: 1px solid transparent;
  border-radius: 6px;
}
.copy-row:nth-child(odd) { background: var(--line-soft); }
.copy-row:hover { border-color: var(--line); }
/* A followed wallet is the only row on this page that costs anything, so it is
   the only one that gets a colour. */
.copy-row-on { border-color: var(--blue); background: rgba(77, 159, 255, 0.07); }
.copy-follow { display: flex; align-items: center; gap: 6px; cursor: pointer; min-width: 0; }
/* The checkbox carries the browser's own margins, which set the row height for
   every other cell. Sized explicitly so forty rows fit on a screen. */
.copy-follow input[type="checkbox"] { width: 13px; height: 13px; margin: 0; flex: 0 0 auto; }
.copy-row { min-height: 0; }
.copy-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Nothing in a row may wrap. A two-line row breaks the alignment of every
   column beside it, which is what made the table read as overlapping data. */
.copy-row > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.copy-seen { text-align: right; font-variant-numeric: tabular-nums; font-size: 11px; }
.copy-analyse {
  font-size: 10px;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
.copy-analyse:hover { border-color: var(--blue); color: var(--blue); }
.copy-analyse:disabled { opacity: 0.5; cursor: default; }
.copy-addr { font-size: 12px; }
.copy-x { text-align: center; }
.copy-seen { font-size: 12px; text-align: right; }
/* The score is the one number on this page that is ours rather than reported,
   so it is the one thing given a colour. Grades, not a gradient: a continuous
   colour ramp implies a precision this number does not have. */
.copy-score { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.copy-score.grade-strong { color: var(--green); }
.copy-score.grade-good { color: #7ed9a8; }
.copy-score.grade-fair { color: var(--amber); }
.copy-score.grade-poor { color: var(--red); }
.copy-pnl { text-align: right; font-variant-numeric: tabular-nums; font-size: 12px; }
.copy-pnl.pnl-up { color: var(--green); }
.copy-pnl.pnl-down { color: var(--red); }
.copy-hold { text-align: right; font-size: 12px; color: var(--text-dim); }
.copy-sim { text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; }

/* The mobile treatment for this row lives in the table-scroll block at the end
   of this file. Two rules used to reshape it here and again at 900px — three
   columns in one, four in the other — while the renderer emitted thirteen
   cells into whichever won. */

.copy-head {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: none !important;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding-bottom: 4px;
}
.copy-head:hover { border-color: transparent; border-bottom-color: var(--line); }

/* Copy risk tiles: the state of the machine, not of the market. Compact enough
   to sit above the roster without pushing it off the screen. */
.copy-risk {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 10px 0 4px;
}
.risk-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-2);
  min-width: 96px;
}
.risk-label {
  font-size: 10px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.risk-value { font-size: 13px; font-weight: 600; }
.risk-ok .risk-value { color: var(--text); }
.risk-warn { border-color: rgba(251, 191, 36, 0.4); }
.risk-warn .risk-value { color: var(--amber); }
.risk-bad { border-color: rgba(248, 113, 113, 0.45); }
.risk-bad .risk-value { color: var(--red); }

/* --- Discover and Overview rows -----------------------------------------
   These tables have DIFFERENT columns from the wallet roster, so they get
   their own grids. Reusing .copy-row's twelve fixed roster columns is what
   dropped a status into a 34px cell and a SOL amount into a 36px one, which
   is why every value read as "SCREENING PASS…" and "1.3…". Each column here
   is sized to the widest value it can actually hold. */
.disc-row,
.att-row,
.ov-row {
  display: grid;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.3;
}
/* Discover: wallet · score · 7d · win · stage · seen · why · action.
   The why column takes the slack; the action column is fixed so the buttons
   line up down the page instead of drifting with row content. */
/* Nine columns now: wallet · score · window · win · COPYABLE · stage · seen ·
   why · action. Copyability sits before the stage badge because "can I follow
   this at all" outranks "where is it in the funnel". */
/* Ten columns: wallet · SOURCE · score · window · win · copyable · stage ·
   seen · why · action. Source sits second because "where did this come from"
   is the first question asked of a candidate and the row could not answer it. */
/* wallet · source · score · window · win · HOLD · trades · copyable · stage · seen · why · action.
   Hold and trades were added because the row could not answer the two questions
   an operator actually asks of a candidate: how long does it hold, and over how
   many trades is that measured. */
.disc-row { grid-template-columns: minmax(104px, 168px) 68px 42px 58px 40px 50px 44px 112px 72px 38px minmax(64px, 1fr) 92px; }
.disc-src { font-size: 11px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Overview wallets: name · 7d · 30d · win · last trade. */
.ov-row.ov-wallets { grid-template-columns: minmax(140px, 1fr) 72px 72px 52px 96px; }
/* Overview activity: time · side · token · size · wallet. Every cell here was
   truncated before — the token and wallet links need real room. */
.ov-row.ov-events { grid-template-columns: 92px 46px minmax(96px, 1fr) 92px minmax(110px, 160px); }
.disc-row:nth-child(even),
.ov-row:nth-child(even) { background: var(--line-soft); }
.disc-row:hover,
.ov-row:hover { border-color: var(--line); }
/* Nothing wraps: a two-line row breaks the alignment of every column beside it. */
.disc-row > *,
.att-row > *,
.ov-row > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Numbers right-align on a shared decimal so columns scan vertically. */
.disc-row .r,
.att-row .r,
.ov-row .r { text-align: right; font-variant-numeric: tabular-nums; }

/* OUR execution feed: source · action · token · theirs · ours · latency ·
   status · why. Status is fixed at the width of its longest label
   ("BUY CONFIRMED") so the column never reflows as states change under a
   live poll, and the why column absorbs the slack. */
.att-row {
  grid-template-columns:
    minmax(96px, 132px) 42px minmax(84px, 1fr) 56px 62px 66px 104px minmax(80px, 1.2fr);
}
.att-row:nth-child(even) { background: var(--line-soft); }
.att-row:hover { border-color: var(--line); }
.att-state {
  font-size: 10px;
  letter-spacing: 0.03em;
  font-weight: 600;
  text-align: center;
  padding: 2px 4px;
  border: 1px solid currentColor;
  border-radius: 4px;
}
/* Four tones, not five: SENT and BUILDING share one because to an operator
   they mean the same thing — in flight, do not touch. */
.att-state.st-good { color: var(--green); }
.att-state.st-bad { color: var(--red); }
.att-state.st-warn { color: var(--amber); }
.att-state.st-idle { color: var(--text-faint); }
.att-state.st-work { color: var(--blue); }
/* Short refusal codes fit on one line; the crash-recovery sentences are three
   times longer and were ellipsised away at 1024px. Two lines, clamped, so the
   reason stays readable without letting one long note stretch the row
   indefinitely. This is the cell the operator reads to understand a refusal,
   so it is the one that gets the extra room. */
.att-why {
  font-size: 11px;
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.25;
}
/* MOBILE. The eight-column grid needs ~670px; inside a 326px panel every
   reason cell was clipped and the feed scrolled sideways. Stacked into three
   labelled lines instead — the refusal reason is the single cell an operator
   most needs to read in full, so it is the one that gets to wrap.
   900px and not 720: the panel runs ~112px narrower than the viewport, so a
   720 breakpoint left a dead band around 768 where the desktop grid still did
   not fit and clipped every reason again. MEASURED, not guessed. */
@media (max-width: 900px) {
  .att-row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "who side   state"
      "tok theirs ours"
      "why why    lat";
    row-gap: 3px;
    padding: 8px 10px;
  }
  /* Column headings cannot label a stacked row, so they go and each number
     carries its own label instead. */
  .att-row.disc-head { display: none; }
  .att-row > *:nth-child(1) { grid-area: who; }
  .att-row > *:nth-child(2) { grid-area: side; }
  .att-row > *:nth-child(3) { grid-area: tok; }
  .att-row > *:nth-child(4) { grid-area: theirs; }
  .att-row > *:nth-child(5) { grid-area: ours; }
  .att-row > *:nth-child(6) { grid-area: lat; }
  .att-row > *:nth-child(7) { grid-area: state; }
  .att-row > *:nth-child(8) {
    grid-area: why;
    white-space: normal;
    overflow: visible;
    line-height: 1.35;
  }
  .att-row > *:nth-child(4)::before { content: 'theirs '; color: var(--text-faint); }
  .att-row > *:nth-child(5)::before { content: 'ours '; color: var(--text-faint); }
}
/* A section label inside the feed, so "ours" and "theirs" can never be read
   as one list. */
.act-head {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 10px 0 2px;
}
.act-head:first-child { margin-top: 0; }
/* The header is a label row, not data: no stripe, no hover, no link colour. */
.disc-head {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: none !important;
  border-color: transparent !important;
  padding-bottom: 2px;
}
.disc-score { font-weight: 600; font-size: 14px; }
.disc-score.grade-strong { color: var(--green); }
.disc-score.grade-good { color: #7ed9a8; }
.disc-score.grade-fair { color: var(--amber); }
.disc-score.grade-poor { color: var(--red); }
.disc-why { font-size: 12px; }
/* Source and copy side by side. Six columns, each sized for its widest value,
   because the whole point is comparing two numbers that must never be added. */
.ov-row.perf-row { grid-template-columns: minmax(120px, 1fr) 92px 100px 82px 74px 82px; }
@media (max-width: 1100px) {
  .ov-row.perf-row { grid-template-columns: minmax(110px, 1fr) 88px 96px 78px; }
  .ov-row.perf-row > :nth-child(5),
  .ov-row.perf-row > :nth-child(6) { display: none; }
}
/* A score the funnel has overruled. Struck through rather than hidden: the
   number is still a fact about observed trades, it just is not an endorsement
   any more, and deleting it would hide the disagreement instead of showing it. */
.copy-status.verdict-overruled,
.verdict-overruled {
  text-decoration: line-through !important;
  opacity: 0.75;
}
/* A score older than the scoring cadence is dimmed, so "stale" and "fresh" are
   distinguishable at a glance rather than only in a tooltip. The numbers stay
   readable — they are still true, just not current. */
.disc-row.disc-stale .disc-score,
.disc-row.disc-stale .r { opacity: 0.55; }
/* The row opens the evidence. It must look like it does. */
.disc-row.disc-click { cursor: pointer; }
.disc-row.disc-click:hover { background: var(--line-soft); border-color: var(--line); }

/* Score breakdown inside the candidate modal: a bar per component, so the
   score reads as five measured things rather than one opaque number. */
.wd-comp { margin: 8px 0 12px; }
.wd-comp-head { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 3px; }
.wd-bar { height: 4px; background: var(--line-soft); border-radius: 2px; overflow: hidden; }
.wd-bar-fill { height: 100%; background: var(--green); }
.wd-reasons { margin: 4px 0 12px 18px; font-size: 12px; line-height: 1.6; }
.ov-name { font-weight: 500; }
/* A stage badge: readable at a glance, and it cannot bleed into its neighbour. */
.disc-row .pill {
  font-size: 10px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 6px;
  border: 1px solid currentColor;
  border-radius: 999px;
  justify-self: start;
  max-width: 100%;
}
/* The action button must not set the row height, or rows with one stand taller
   than rows without and the table develops a visible limp down the page.
   Sized to sit inside the text line box instead. */
.disc-row button.tiny {
  justify-self: end;
  white-space: nowrap;
  padding: 2px 8px;
  font-size: 11px;
  line-height: 1.4;
  height: 21px;
}
/* A fixed height, not a minimum: rows with a button and rows without must be
   the same, or the table limps visibly down the page. */
.disc-row:not(.disc-head) { height: 34px; box-sizing: border-box; }

/* Narrow screens: drop the columns that are context, keep the ones that are
   the decision. Nothing is ever allowed to wrap into a second line instead.
   
   Between 901 and 1100px only. Below 901 these tables scroll sideways instead
   (see the table-scroll block at the end of this file), so there is no longer
   anything to be gained by hiding a column — and the two this used to drop on
   the smallest screens were "why / flags", which is the whole explanation of a
   rejection, and "seen", which is the sample it rests on. Cutting those from a
   phone left the reader the verdict and none of the evidence for it. */
@media (min-width: 901px) and (max-width: 1100px) {
  /* Keep wallet, score, window, COPYABLE and the action. Copyability survives
     the cut because it is the one column that can stop a bad decision. */
  /* Indices shifted by one when Source was inserted second. Keeping them in
     terms of the OLD order silently hid the wrong columns. */
  .disc-row { grid-template-columns: minmax(96px, 1fr) 74px 40px 58px 122px 88px; }
  .disc-row > :nth-child(5), /* win */
  .disc-row > :nth-child(7), /* stage */
  .disc-row > :nth-child(8), /* seen */
  .disc-row > :nth-child(9) { display: none; } /* why */
  .ov-row.ov-events { grid-template-columns: 78px 44px minmax(80px, 1fr) 84px; }
  .ov-row.ov-events > :nth-child(5) { display: none; }
  .ov-row.ov-wallets { grid-template-columns: minmax(120px, 1fr) 68px 68px 48px; }
  .ov-row.ov-wallets > :nth-child(5) { display: none; }
}

/* --- Copy control centre ------------------------------------------------- */

/* A dash for a missing metric reads as a bug. The status column says what the
   system is waiting for instead. */
.copy-status { font-size: 9px; letter-spacing: 0.02em; text-transform: uppercase; }
.copy-num { text-align: right; font-size: 12px; font-variant-numeric: tabular-nums; }
.tone-ok { color: var(--green); }
.tone-warn { color: var(--amber); }
.tone-bad { color: var(--red); }
.tone-muted { color: var(--text-faint); }

.chain-link { color: var(--blue); text-decoration: none; }
.chain-link:hover { text-decoration: underline; }

.risk-click:hover { border-color: var(--blue); }

.copy-sec {
  margin: 18px 0 6px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Activity, as trading events rather than a log dump. */
.act-row {
  display: grid;
  grid-template-columns: minmax(90px, 0.8fr) 46px minmax(90px, 1fr) 90px 76px 40px;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 12px;
}
.act-row:nth-child(odd) { background: var(--line-soft); }
.act-row:hover { background: var(--panel-2); }
.act-who { font-weight: 600; letter-spacing: 0.03em; }
.act-side { font-weight: 700; font-size: 11px; }
.side-buy { color: var(--green); }
.side-sell { color: var(--red); }
.act-sol { text-align: right; font-variant-numeric: tabular-nums; }
.act-when { text-align: right; font-size: 11px; }
/* The leader feed had the same mobile fault as our own: six columns needing
   490px inside a 328px panel, so size, time and the explorer link sat off the
   right edge where nobody could reach them. Two lines at narrow widths, on the
   same 900px boundary as the attempt feed so the two never disagree. */
@media (max-width: 900px) {
  .act-row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "who  side sol"
      "tok  when tx";
    row-gap: 3px;
    padding: 8px;
  }
  .act-row > *:nth-child(1) { grid-area: who; }
  .act-row > *:nth-child(2) { grid-area: side; }
  .act-row > *:nth-child(3) { grid-area: tok; }
  .act-row > *:nth-child(4) { grid-area: sol; }
  .act-row > *:nth-child(5) { grid-area: when; }
  .act-row > *:nth-child(6) { grid-area: tx; }
  .act-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* Open positions — THE COPY WORKSPACE'S LIST, NOT THE DASHBOARD'S TABLE.
 *
 * EVERY SELECTOR HERE IS SCOPED TO .pos-list AND MUST STAY THAT WAY.
 *
 * TOMBSTONE: these were bare `.pos-row` rules, and `.pos-row` is used by two
 * completely different things. The Copy workspace builds a list of <div>s and
 * needs a grid. The dashboard's "Open positions" panel builds a real <table>
 * whose <tr>s also carry class="pos-row" — for `cursor: pointer` and a hover
 * tint, defined far above at the other .pos-row rule.
 *
 * Same specificity, and this block comes later, so `display: grid` won this
 * cascade and applied to the <tr>s as well. That silently switched the table
 * body out of table layout: the <thead> kept laying out by the table algorithm
 * across the full panel, while every <tr> laid itself out on a grid of fixed
 * pixel columns totalling roughly 726px. The result on screen was a header row
 * whose labels lined up with nothing, cell contents overlapping each other, and
 * a horizontal scrollbar under a table that had no need to scroll.
 *
 * It is the same failure as the two things called "Wallets": one name meaning
 * two things in two parts of the app, where the collision is invisible until
 * something renders wrong. Scoping is what keeps them apart — a future rule
 * added bare here would silently reach back into the dashboard table again.
 */
.pos-list { display: flex; flex-direction: column; gap: 2px; }
.pos-list .pos-row {
  display: grid;
  grid-template-columns: minmax(90px, 1fr) 96px 96px 72px 72px 84px 96px 70px;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 12px;
}
.pos-list .pos-row:nth-child(odd) { background: var(--line-soft); }
.pos-list .pos-head {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: none !important;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
}
.pos-list .pos-row .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Wallet detail. */
.wd-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px 16px;
  margin: 10px 0;
}
.kv { display: flex; flex-direction: column; gap: 1px; }
.kv-k { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }
.kv-v { font-size: 13px; }
.wd-head { margin: 16px 0 6px; font-size: 12px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-dim); }
.wd-score { display: flex; align-items: baseline; gap: 8px; margin: 8px 0; }
.wd-score-n { font-size: 34px; font-weight: 700; }
.wd-score-of { color: var(--text-faint); }
.wd-unproven { border: 1px solid var(--amber); border-radius: 6px; padding: 10px; margin: 10px 0; }
.wd-unproven-title { color: var(--amber); font-weight: 700; letter-spacing: 0.06em; font-size: 12px; }

/* Score components, as bars, because the shape of a score is the explanation. */
.wd-bar-row { display: grid; grid-template-columns: 130px 1fr 70px; align-items: center; gap: 10px; margin-top: 8px; }
.wd-bar-label { font-size: 12px; }
.wd-bar { height: 6px; background: var(--line); border-radius: 3px; overflow: hidden; }
.wd-bar-fill { display: block; height: 100%; background: var(--grad); }
.wd-bar-pts { text-align: right; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.wd-bar-detail { font-size: 11px; margin: 2px 0 0 140px; }

.paper-table, .wd-hist { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
.paper-row { display: grid; grid-template-columns: 84px 60px 78px 132px 96px 64px; gap: 8px; font-size: 12px; padding: 4px 6px; }
.paper-row .num { text-align: right; font-variant-numeric: tabular-nums; }
.paper-head, .wd-hist-head {
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint);
  border-bottom: 1px solid var(--line);
}
.wd-hist { max-height: 320px; overflow-y: auto; }
.wd-hist-row { display: grid; grid-template-columns: 148px 44px 108px 72px 1fr 48px; gap: 8px; font-size: 12px; padding: 3px 6px; }
.wd-hist-row:nth-child(odd) { background: var(--line-soft); }
.wd-hist-row .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Refusals and alerts. */
.ref-card { border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; margin-top: 8px; }
.ref-code { font-size: 11px; letter-spacing: 0.06em; font-weight: 700; color: var(--amber); }
.ref-line { font-size: 12px; margin-top: 2px; }
.ref-measured { font-family: var(--mono); font-size: 11px; color: var(--text-dim); margin-top: 4px; }
.alert-error { border-color: rgba(248, 113, 113, 0.45); }
.alert-error .ref-code { color: var(--red); }
.alert-warn .ref-code { color: var(--amber); }
.alert-info .ref-code { color: var(--blue); }

@media (max-width: 900px) {
  /* Scoped for the same reason as the block above: a bare .pos-row here would
     hand the dashboard's table rows a three-column grid on narrow screens. */
  .pos-list .pos-row { grid-template-columns: 1fr 80px 80px; }
  .wd-hist-row { grid-template-columns: 1fr 40px 80px 60px; }
}


/* --- Copy workspace: one product, six views ------------------------------ */

/* The workspace uses the house subtabs bar (same as Trade/Launch); only the
   section spacing is its own. */
.copy-main { min-width: 0; }
.copy-sec-page > .panel { margin-top: 12px; }
.copy-sec .copy-sec { margin-top: 18px; }


/* Per-wallet copy settings, inside the wallet detail modal. Appended at the
   end of the file deliberately: this stylesheet has several later override
   layers, and a rule added beside the early form primitives would lose to
   them. */
.wcfg { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }
.wcfg .field { margin-top: 10px; }
.wcfg select { width: 100%; }
/* The inherit placeholder is the whole point of the blank state, so it has to
   read as text rather than the usual near-invisible grey. */
.wcfg input::placeholder { color: var(--text-faint); opacity: 1; }
.wcfg .btn-row { align-items: center; }
.wcfg-out { margin-top: 0; }
.wcfg-ok { color: var(--green); }
/* One column below the modal's own breakpoint: two number fields side by side
   inside an already-narrow modal leaves neither readable. */
@media (max-width: 560px) {
  .wcfg .row > * { flex: 1 1 100%; }
}

/* Action groups could not wrap on a phone.
   .row-actions carries `flex: none !important` (styles.css:2862) so the button
   group keeps its natural width and is not squashed on a desktop row. The cost
   is that it can never shrink either, so the `flex-wrap: wrap` added later
   never engages: on the Wallet Lab generate row the three buttons measured 394px
   inside a 294px row and the third sat outside the panel, unreachable.
   Given the whole row rather than its content width, the existing wrap does the
   rest. Desktop is untouched — this only applies where the row has already
   collapsed to a single column. */
@media (max-width: 720px) {
  .row-actions {
    flex: 1 1 100% !important;
    min-width: 0;
  }
  .row-actions > button { flex: 1 1 auto; }

  /* Two fields per line is too many once the panel is ~294px wide. The basis
     was 130px, so a pair shared a line neither could fill: Settings > Trading
     put "a share of the supply" in a 152px select needing 179px, and a select
     cannot ellipsize its own content away — the text is simply cut.
     Raising the basis to 180px changes no layout that still fits; it only makes
     a pair wrap when the two of them could not have been read side by side. */
  .row > * { flex: 1 1 180px; }
}

/* --- Dense tables on a phone --------------------------------------------- */
/*
   The roster, Discover, the overview wallet list and the source-vs-copy table
   are all real tables: nine to thirteen columns whose headings are the only
   thing that says what a number means. Squeezed into a ~326px panel they did
   not shrink gracefully, they clipped — a 356px row drawn inside 326px, a name
   cut at 81px, "+1126.52" cut to fit 50px. The page itself never overflowed, so
   nothing looked wrong; the data was simply cut off inside the panel.
   
   Reshaping them into stacked cards would drop the headings, and a column of
   bare numbers with no headings is worse than one that scrolls. So they scroll,
   sideways, inside their own box — heading row and data together, so the two
   stay aligned — and every column keeps its width and its meaning.
   
   Desktop is untouched: above 900px there is room for the whole row and the
   container never scrolls. */
.table-scroll { min-width: 0; }
@media (max-width: 900px) {
  .table-scroll {
    overflow-x: auto;
    /* Momentum scrolling on iOS, and a thin bar rather than one that eats a
       row's worth of height on desktop-class browsers that show one. */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
  /* ONE element decides the width of every row in a table.
     Sizing each row to its own max-content instead lets the heading row and the
     data rows compute different widths — measured 158px against 197px on the
     second column — so the headings sat over the wrong numbers. That is worse
     than clipping: a cut-off value looks wrong, a mislabelled one does not.
     min-width 100% keeps a short table filling the panel rather than shrinking
     to its content. */
  .table-inner { width: max-content; min-width: 100%; }
  .table-inner > .copy-list { width: 100%; }
  .table-inner > .copy-row,
  .copy-list > .copy-row,
  #discover-list > .disc-row,
  #copy-overview-active > .ov-row,
  #copy-overview-events > .ov-row,
  .perf-table > .ov-row { width: 100%; }

  /* The Discover filter bar is ten controls in a flex row that never wrapped:
     779px of them inside 328px, so everything past the third sat off the edge
     with no way to reach it. Wrapping is right here rather than scrolling —
     these are independent controls, not columns of one table, so there is no
     alignment to keep and no reason to make someone scroll to find one. */
  .copy-toolbar input[type="search"] { flex: 1 1 100%; max-width: none; min-height: 40px; }
}

/* The filter bar wraps at every width, and its controls are never squeezed.
   A select is already as wide as its longest option — that is its natural
   size — so the labels were being cut by flex SHRINKING them, not by the box
   being too small: "best score" wanted 92px in 87px at 1420px, "every status"
   108px in 102px. Pinning a single min-width just moved the problem, since ten
   controls at a shared minimum no longer fit the bar either.
   Refusing to shrink and allowing the bar to wrap fixes both directions at
   once: each control keeps exactly the width its own text needs, and the bar
   takes a second line when they no longer fit on one. */
.copy-toolbar { flex-wrap: wrap; }
.copy-toolbar select { flex: 0 0 auto; min-width: 0; }

/* The wallet name column used to be minmax(104px, 1fr) and so absorbed every
   spare pixel: measured 388px at 1391px wide, which put a hand's width of empty
   space between a name and its address and made the row read as two unrelated
   halves. It is capped now, and the slack goes to the actions column at the end
   where it is doing something.

   Row actions. Deliberately plain: these sit inside a dense table and a pair of
   filled buttons on every row would fight the data for attention. */
.copy-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  align-items: center;
}
.row-act {
  background: none;
  border: 1px solid var(--line);
  color: var(--text-dim);
  border-radius: 5px;
  padding: 3px 8px;
  font-size: 11px;
  line-height: 1.4;
  /* A real touch target on a phone without making the desktop row taller. */
  min-height: 24px;
  white-space: nowrap;
  cursor: pointer;
}
.row-act:hover { border-color: var(--blue); color: var(--text); }
.row-act-bad:hover { border-color: var(--red); color: var(--red); }
.row-act:disabled { opacity: 0.5; cursor: default; }
@media (max-width: 900px) {
  /* Inside a horizontally scrolling table the buttons must not be the thing
     that decides the row height. */
  .row-act { min-height: 28px; padding: 4px 10px; }
}

/* --- Discover filter panel ------------------------------------------------ */
/*
   Replaces a flex row of ten unlabelled selects. That row relied on each
   control's first option to say what the control was ("any evidence"), so the
   moment it was squeezed the labels were the first thing lost — "any e…" —
   and the panel became a row of controls nobody could identify.

   Each control now has a real label above it. The option text is therefore
   free to describe the VALUE ("No flags") instead of the control, and a
   truncated option can no longer hide the control's identity. */
.disc-filter-panel {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 12px 0 10px;
  background: var(--panel-2);
}
.disc-filter-top { display: flex; gap: 10px; align-items: center; }
/* THE SEARCH BOX THAT WAS 21 PIXELS TALL.
   TOMBSTONE. This rule used to set `appearance: none` — which strips the input's
   native padding, border and background — and then supply no replacement for any
   of them. The result rendered as a bare 21px line of text: measured 2026-08-21
   at 21px here against 40px for the plain text inputs sitting beside it on the
   same page, and 32px for the otherwise identical box in .copy-toolbar. Three
   heights for one control.
   Worse, the only rule that repaired it — min-height: 40px — lived inside the
   (pointer: coarse) media query below, so the control was correct on a phone and
   broken on every desktop. That is the reverse of the usual failure and it is why
   it survived: whoever checked it on mobile saw nothing wrong.
   Matched to .copy-toolbar deliberately. Two search boxes that look different are
   two controls the operator has to learn separately. */
.disc-filter-top input[type="search"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 340px;
  min-width: 0;
  max-width: 460px;
  /* Matched to .copy-toolbar above, and to the 39px inputs beside it. */
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(6, 8, 15, 0.6);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  outline: none;
}
.disc-filter-top input[type="search"]::placeholder { color: var(--text-faint); }
.disc-filter-top input[type="search"]:focus { border-color: var(--blue); }
.disc-filter-top input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
/* THE COLLAPSE THAT EXISTED AND WAS SWITCHED OFF ON DESKTOP.
   TOMBSTONE. This said "the toggle is for phones only", so a desktop operator got
   every filter at once: ten <select>s on Discover, six on Activity, eleven on
   Logs. Measured 2026-08-21 on a 720px viewport, Discover's chrome alone was
   611px tall — 85% of the screen consumed before a single candidate row rendered.
   That is the whole "way too much scrolling" complaint, and the machinery to fix
   it was already built and disabled.
   The filters are now behind the toggle everywhere. They are not gone: the button
   states how many are active, so a narrowed list never looks like an empty one. */
.disc-filters-toggle { display: inline-flex; flex: 0 0 auto; }
.disc-filter-body { display: none; }
.disc-filter-body.is-open { display: block; }

/* PROSE HAS A MEASURE, EVEN HERE.

   Measured 2026-08-21 at a 1280px viewport: every explanatory paragraph in the
   copy workspace ran 1168px wide, about 195 characters a line, against the 45-75
   that reading research puts as comfortable. Monospace makes it WORSE rather
   than better — every character takes the width of the widest one, so the same
   sentence occupies more line, and the eye has further to travel to find the
   start of the next one.

   This does not touch the typeface. BRANDING_BRIEF.md:139 is explicit — the app
   is monospace throughout — and that is a decision about identity, not a defect.
   Only the measure changes.

   Scoped to #page-copy on purpose. The operator asked for the copy workspace and
   nothing else, and a global change here would reflow Trade, Wallet Lab and
   Settings without anybody having looked at them. */
#page-copy .hint,
#page-copy .panel-body > p {
  max-width: 92ch;
}
/* A table cell is not prose. Anything laid out on a grid keeps the full width
   it was given, or the columns stop lining up with their own heading. */
#page-copy .copy-row .hint,
#page-copy .ov-row .hint,
#page-copy .wd-grid .hint { max-width: none; }

/* THE COPY SIZE CONTROL, WHICH SHIPPED WITH MARKUP AND NO STYLESHEET.

   TOMBSTONE. 29a8d89 added .cw-size, .cw-size-row and .cw-size-unit to the HTML
   and defined none of them here, so the block fell back to the generic
   `input { width: 100% }` at the top of this file. The result, which the
   operator screenshotted: a 1168px-wide input holding the four characters
   "0.02", with the unit and the Save button dropped onto the line beneath it,
   and the unit rendering LARGER than the label above it because a bare <span>
   inherits body size while the label is 11px. The hierarchy read backwards —
   the thing that never changes shouted, the thing being edited whispered. */
.cw-size {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.cw-size > label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 6px;
}
.cw-size-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* Sized to what it holds. A SOL amount is at most a handful of digits, and an
   input far wider than its content reads as an empty field rather than a set
   one. tabular-nums so 0.02 and 0.05 occupy the same width. */
.cw-size-row input[type="number"] {
  width: 130px;
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.cw-size-unit {
  font-size: 12px;
  color: var(--text-dim);
  flex: 0 0 auto;
}
.cw-size .hint { margin: 8px 0 0; }
/* The round-trip failure lands here. It is the one message on this panel that
   means the operator's change did NOT take, so it is not styled as a hint. */
#cw-size-result:not(:empty) {
  border-left: 2px solid var(--amber);
  padding-left: 10px;
  color: var(--text);
}

/* ACTIVITY: the two realised-PnL blocks, side by side.
   They are the answer the tab exists to give, so they sit above the event list
   rather than below it — and side by side rather than stacked, because stacking
   them cost ~190px of the fold for two blocks that are each a handful of rows.
   Collapses to one column under 900px, where two columns would just be two
   narrow columns. */
.act-perf {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: start;
  margin-bottom: 4px;
}
.act-perf-col { min-width: 0; }
/* The first heading in each column must not push the row down past the panel
   padding that is already there. */
.act-perf-col .copy-sec { margin-top: 0; }
@media (max-width: 900px) {
  .act-perf { grid-template-columns: 1fr; gap: 4px; }
}

/* DISCOVER: things you read once, folded away from things you read every time.
   The pipeline diagnostics, the explainer and the by-hand seed form were all
   permanently open above the candidate list. Folded, they cost one line each
   until wanted. Deliberately NOT applied to #discover-status-banner, which is
   the alarm and stays unconditional — see the tombstone in index.html. */
/* NO SUMMARY STYLING HERE, AND THAT IS THE POINT.

   TOMBSTONE, AND IT WAS MINE. This block first shipped with its own border,
   background and a text-glyph '▸' marker, and a later pass widened that marker
   to .cw-audit and .ref-card "for consistency". Both were wrong: styles.css:3201
   already carries a deliberate shared idiom — its comment reads "one consistent
   chevron, replacing the ad-hoc ::before markers" — and its selector,
   .panel details:not(.launch-panel):not([id]) > summary, ALREADY MATCHED all
   three of these. It gives the summary a flex card with border, radius,
   background, list-style:none and a chevron drawn from a rotated border box.

   At (0,3,2) that rule also outranks .disc-fold > summary at (0,1,1), so the
   marker rules here never applied to .disc-fold at all — they were dead. The
   one place they DID win was .cw-audit, because #page-copy dragged the
   specificity to (1,2,2) — so the only visible effect of a change made in the
   name of consistency was to give Lock history a text glyph while every other
   disclosure in the product kept the drawn chevron. Exactly backwards.

   What was found by grepping for a class name and concluding "no rule anywhere"
   was a styled element whose rule does not mention the class. Left minimal on
   purpose: spacing only, and the shared idiom does the rest. */
/* THE GAPS BETWEEN BLOCKS WERE COSTING MORE THAN THE BLOCKS.

   MEASURED ON THE LIVE DEPLOYMENT, 2026-08-21, 551px window, real roster of ten
   wallets. Everything stacked above the first wallet row summed to 441px of
   actual content — and the row sat at y=578. The missing 119 PIXELS WERE GAP:
   twenty-odd pixels of margin between each of seven blocks, none of it carrying
   any information.

   That is why the tab reads as "too much scrolling" even after the content on it
   was cut. The content was never the whole problem; the air around it was.

   Tightened, not crushed. BRANDING_BRIEF.md calls this UI "dense,
   information-first", so a panel header that spends 57px announcing one word was
   already off-brief. The panel keeps its 22px side padding — horizontal room is
   what makes dense text readable — and gives up vertical room, which is what was
   pushing the data off screen.

   Scoped to #page-copy. The same .panel-head and .panel-body serve Trade, Wallet
   Lab and Settings, and those were not asked about and have not been looked at. */
#page-copy .panel-head { padding-top: 13px; padding-bottom: 13px; }
#page-copy .panel-body { padding-top: 14px; padding-bottom: 16px; }
#page-copy .copy-toolbar { margin-top: 8px; margin-bottom: 8px; }
#page-copy .copy-sec { margin-top: 16px; margin-bottom: 8px; }
#page-copy .copy-sec:first-child { margin-top: 0; }
#page-copy #copy-subtabs { margin-bottom: 10px; }

/* A BUTTON IS NOT A TEXT FIELD AND MUST NOT BE SIZED LIKE ONE.

   .row > * carries flex: 1 1 130px so the fields in a form row share the width
   evenly. That is right for inputs and wrong for the submit beside them: the
   button sits in its own .field wrapper, inherits the same flex, and grows to
   match. MEASURED IN A REAL BROWSER, 2026-08-21: "Add" on the Copying tab
   rendered 382 PIXELS WIDE — the exact width of the wallet-address input next to
   it — for three characters. The Seed button on Discover does the same.

   A control that is five times the size of its own label reads as a mistake, and
   it also breaks the scan: the eye lands on the largest thing in the row, which
   here is the least important one.

   :has() rather than a new class, so the markup does not have to carry a hint
   about its own layout. Supported everywhere this app runs. */
#page-copy .row .field:has(> button) {
  flex: 0 0 auto;
}
#page-copy .row .field > button {
  min-width: 104px;
  width: auto;
}

/* AN EMPTY CONTAINER STILL RESERVES ITS MARGINS.

   Nearly every box in this workspace is filled by app.js and starts life empty:
   the status banner, the tile grids, the source line, the blocker list, the two
   PnL blocks. Height collapses to 0 when they are empty, but MARGIN does not —
   so each one leaves a band of nothing exactly as tall as its margins.

   MEASURED IN A REAL BROWSER, 2026-08-21: on Discover, #discover-tiles (0px
   tall, 10px margins) and #discover-sources (0px tall, 6px/12px margins) put 38
   pixels of emptiness between two adjacent folds, which is why the spacing
   between three identical rows read as 63px, then 35px. The Dashboard does the
   same thing under its panel header. It is the single biggest source of the
   "gaps everywhere, looks unfinished" quality, and none of it is visible in the
   markup — every one of those elements looks fine until it is empty at runtime.

   Collapsed rather than removed, because they are all correct once data arrives.
   :empty is exact here: these are cleared with textContent = "" rather than left
   holding whitespace, so nothing that has content can match. */
#page-copy .panel-body > div:empty,
#page-copy .panel-body > p:empty,
#page-copy .panel-body > span:empty {
  margin: 0;
  padding: 0;
  border: 0;
}

/* THREE SECONDARY FOLDS ARE NOT THREE PRIMARY ONES.

   The shared idiom at styles.css:3201 gives every <details> in a panel a full
   card: 12px/16px padding, a 12px radius, a border and a background. That is
   right for ONE prominent disclosure. It is wrong for three stacked secondary
   ones, and the difference is only visible on screen — which is why the
   previous pass, working from element heights alone, could not see it.

   MEASURED IN A REAL BROWSER, 2026-08-21, 551px viewport: each fold rendered
   46px, 138px for the three, and #discover-list — the candidate list this whole
   tab exists to show — began at y=565. Entirely below the fold. The operator had
   to scroll past three bordered cards, showing nothing, to reach the first
   wallet. The folds had become heavier than the content they were hiding.

   So these three opt OUT of the card and become what they always were: three
   lines you can click. The chevron stays, because that is the product-wide
   gesture for "this opens" and consistency of GESTURE is worth keeping even
   where consistency of WEIGHT is not. Everything else is stripped.

   Deliberately scoped to .disc-fold. Lock history on the Dashboard is a single
   disclosure at the end of a panel and keeps the shared card. */
.disc-fold { margin: 0; }
#page-copy .disc-fold > summary {
  padding: 5px 2px;
  border: 0;
  border-radius: 0;
  background: none;
  font-weight: 400;
  font-size: 12px;
  color: var(--text-faint);
}
#page-copy .disc-fold > summary:hover { background: none; color: var(--text-dim); }
/* The three sit as a group, with one rule under them separating the folded
   material from the search box that is always available. */
#page-copy .disc-fold:last-of-type { margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--line-soft); }
.disc-fold > *:not(summary) { padding: 6px 2px 2px; }
.disc-fold > .row { padding: 8px 2px; }
.disc-fold > .hint { padding: 6px 2px 2px; margin: 0; }
.disc-fold > .wd-grid { padding: 6px 2px 2px; }

/* auto-fit, so the controls choose their own number of columns for the width
   they are given instead of being crushed into one line. 168px is the width of
   the longest label plus its widest option, measured, not guessed. */
.disc-filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 10px 12px;
  margin-top: 10px;
}
.disc-filter-grid .field { min-width: 0; }
.disc-filter-grid select { width: 100%; }
.disc-source-field { margin-top: 10px; }
.disc-source-field .chip-row { display: flex; flex-wrap: wrap; gap: 6px; }

.disc-adv-toggle {
  background: none;
  border: none;
  color: var(--blue);
  font-size: 11.5px;
  padding: 8px 0 0;
  cursor: pointer;
  text-align: left;
}
.disc-adv-toggle::after { content: ' ▾'; }
.disc-adv-toggle.is-open::after { content: ' ▴'; }
.disc-advanced { border-top: 1px solid var(--line-soft); padding-top: 10px; }

/* The chip strip: what is narrowing the list, and a way to undo each one on its
   own. "Clear all" is rarely what you meant. */
.disc-active-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
}
.disc-af-count {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-right: 4px;
}
.disc-af-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 11.5px;
  color: var(--text-dim);
  cursor: pointer;
  min-height: 26px;
}
.disc-af-chip:hover { border-color: var(--red); color: var(--text); }
.disc-af-k { color: var(--text-faint); }
.disc-af-v { color: var(--text); }
.disc-af-x { color: var(--text-faint); font-size: 14px; line-height: 1; }
.disc-af-chip:hover .disc-af-x { color: var(--red); }
.disc-af-clear {
  background: none;
  border: none;
  color: var(--blue);
  font-size: 11.5px;
  cursor: pointer;
  padding: 3px 6px;
  min-height: 26px;
}

@media (max-width: 900px) {
  /* Ten controls do not belong on a phone screen at once. They fold behind a
     count, which is also where the number of active filters is legible. */
  .disc-filters-toggle { display: inline-flex; flex: 0 0 auto; }
  .disc-filter-body { display: none; }
  .disc-filter-body.is-open { display: block; }
  .disc-filter-grid { grid-template-columns: 1fr; }
  /* A real touch target, not a desktop control shrunk. */
  .disc-filter-panel select { min-height: 40px; }
  .disc-filter-top input[type="search"] { min-height: 40px; }
}

/* --- iOS zoom guard, restated where it can actually win ------------------- */
/*
   The original guard lives at ~line 1956 and sets 16px on touch devices, which
   is the threshold below which iOS Safari zooms the page on focus and leaves
   the operator scrolled sideways into a field they then have to pinch back out
   of. A later rule — same specificity, ~1100 lines further down — re-declares
   font-size: 13.5px on the same selector list, and later wins. The guard has
   been dead ever since.

   Restated at the end of the file so it is the last word, and with
   input[type='search'] added: the original list predates the search boxes on
   Discover and the roster, so those two were never covered even before the
   override killed the rest. */
@media (hover: none) and (pointer: coarse) {
  input[type='text'],
  input[type='number'],
  input[type='password'],
  input[type='search'],
  input[type='datetime-local'],
  select,
  textarea,
  /* Class-scoped rules outrank a bare element selector however late they are,
     so the two search boxes that live inside toolbars have to be named. */
  .copy-toolbar input[type="search"],
  .disc-filter-top input[type="search"] {
    font-size: 16px;
  }
}

/* Discover: the trader's identity where a source published one. */
.disc-ident { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
/* The ADDRESS leads and the name follows it, dimmer and smaller. A name that
   turns out to be a feed label ("GMGN smart money feed") is then obviously
   decoration rather than identity, instead of standing where the address should
   have been. */
.disc-ident-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  color: var(--text-dim);
  flex: 0 1 auto;
}
.disc-ident-x { font-size: 10px; color: var(--blue); text-decoration: none; flex: 0 0 auto; }

/* "In Copying" + the one move back. The pair reads as a state and its reverse,
   which is what the operator asked for: Discover -> Copying means copying, and
   the only other move is back. */
.disc-inpool { display: flex; align-items: center; gap: 6px; justify-content: flex-end; min-width: 0; }
.disc-inpool-tag {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--green, #4ade80);
  border: 1px solid currentColor;
  border-radius: 999px;
  padding: 1px 6px;
  white-space: nowrap;
  flex: 0 0 auto;
}
.disc-ident-x.muted { color: var(--text-faint); }

/* --- Live Activity: our execution feed ------------------------------------ */
.act-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 12px; }
.act-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 14px;
  min-height: 44px;
  cursor: pointer;
  color: var(--text-dim);
}
.act-stat:hover { border-color: var(--blue); }
.act-stat-v { font-size: 18px; font-variant-numeric: tabular-nums; color: var(--text); line-height: 1.1; }
.act-stat-k { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; }
/* Unresolved is amber and failed is red, and they are never the same colour,
   because they are not the same thing: one is unproven, the other is proven not
   to have happened. */
.act-stat.is-warn { border-color: var(--amber); }
.act-stat.is-warn .act-stat-v { color: var(--amber); }
.act-stat.is-bad { border-color: var(--red); }
.act-stat.is-bad .act-stat-v { color: var(--red); }

.exec-table { display: block; }
.exec-row {
  display: grid;
  grid-template-columns:
    76px minmax(90px, 118px) 42px minmax(78px, 1fr) 74px 74px 60px 66px 84px 104px 62px 74px 34px 66px;
  gap: 10px;
  align-items: center;
  padding: 6px 10px;
  border: 1px solid transparent;
  border-radius: 6px;
  font-size: 12.5px;
  line-height: 1.3;
}
.exec-row > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.exec-row .r { text-align: right; font-variant-numeric: tabular-nums; }
.exec-row:nth-child(even) { background: var(--line-soft); }
.exec-row:hover { border-color: var(--line); }
.exec-head {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: none !important;
  border-color: transparent !important;
}
.exec-time { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.exec-who { font-weight: 600; letter-spacing: 0.02em; }
.exec-badge {
  font-size: 10px;
  letter-spacing: 0.03em;
  font-weight: 600;
  text-align: center;
  padding: 2px 4px;
  border: 1px solid currentColor;
  border-radius: 4px;
}
.exec-badge.st-good { color: var(--green); }
.exec-badge.st-bad { color: var(--red); }
.exec-badge.st-warn { color: var(--amber); }
.exec-badge.st-idle { color: var(--text-faint); }
.exec-badge.st-work { color: var(--blue); }
.exec-pos { font-size: 10px; letter-spacing: 0.04em; }
.exec-pos.pos-open { color: var(--blue); }
.exec-pos.pos-closed { color: var(--text-dim); }

/* MOBILE: a card per execution, not the desktop table shrunk.
   The state and the outcome have to survive without horizontal scrolling, so
   the row becomes a labelled block and every number carries its own label —
   the column heading is not there to supply one. */
@media (max-width: 900px) {
  .exec-head { display: none; }
  .exec-row {
    grid-template-columns: 1fr 1fr;
    gap: 4px 12px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
    margin-bottom: 8px;
    background: var(--panel-2);
  }
  .exec-row:nth-child(even) { background: var(--panel-2); }
  /* Line 1: action and outcome, the two things that must never be missed. */
  .exec-row > *:nth-child(3) { grid-column: 1; grid-row: 1; justify-self: start; font-size: 13px; }
  .exec-row > *:nth-child(10) { grid-column: 2; grid-row: 1; justify-self: end; }
  /* Line 2: who and which token. */
  .exec-row > *:nth-child(2) { grid-column: 1; grid-row: 2; }
  .exec-row > *:nth-child(4) { grid-column: 2; grid-row: 2; justify-self: end; }
  /* Then the measurements, each labelled by CSS since the heading row is gone. */
  .exec-row > *:nth-child(5)::before { content: 'source '; color: var(--text-faint); }
  .exec-row > *:nth-child(6)::before { content: 'ours '; color: var(--text-faint); }
  .exec-row > *:nth-child(7)::before { content: 'size '; color: var(--text-faint); }
  .exec-row > *:nth-child(8)::before { content: 'latency '; color: var(--text-faint); }
  .exec-row > *:nth-child(9)::before { content: 'decision '; color: var(--text-faint); }
  .exec-row > *:nth-child(11)::before { content: 'position '; color: var(--text-faint); }
  .exec-row > *:nth-child(12)::before { content: 'p&l '; color: var(--text-faint); }
  /* APPENDED AS 14, NEVER INSERTED. Every selector in this block addresses a
     cell by ordinal, so inserting a column mid-row would renumber ten rules at
     once and a single miss puts the wrong label on a number — which this file
     already argues is worse than clipping. Appending leaves 1-13 untouched. */
  .exec-row > *:nth-child(14)::before { content: 'slot lag '; color: var(--text-faint); }
  .exec-row > *:nth-child(5),
  .exec-row > *:nth-child(6),
  .exec-row > *:nth-child(7),
  .exec-row > *:nth-child(8),
  .exec-row > *:nth-child(9),
  .exec-row > *:nth-child(11),
  .exec-row > *:nth-child(12),
  .exec-row > *:nth-child(14) { text-align: left; font-size: 11.5px; }
  /* Time and transaction close the card. */
  .exec-row > *:nth-child(1) { grid-column: 1; font-size: 11px; }
  .exec-row > *:nth-child(13) { grid-column: 2; justify-self: end; }
  /* The decision badge must not stretch across its cell. */
  .exec-row > *:nth-child(9),
  .exec-row > *:nth-child(10) { justify-self: start; }
  .exec-row > *:nth-child(10) { justify-self: end; }
  /* The card is the scroll unit, so the table itself must not scroll. */
  .exec-table { min-width: 0; width: 100%; }
  .act-stat { flex: 1 1 calc(50% - 4px); }
}

/* The engine's own blocker list, inside the Add-to-Copy review. Deliberately
   plain: these are facts about the deployment, not warnings to be dressed up. */
.blocker-list {
  margin: 6px 0 0;
  padding-left: 18px;
  color: var(--text-dim);
  font-size: 11.5px;
  line-height: 1.5;
}
.blocker-list li { margin-bottom: 4px; }

/* --- Post-promotion health and the review queue --------------------------- */
.copy-review { margin: 10px 0 14px; }
.review-card {
  border: 1px solid var(--amber);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 10px;
  background: var(--panel-2);
}
.review-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.review-name { font-weight: 600; font-size: 13px; }
.review-when { font-size: 11px; }
.review-signals {
  margin: 10px 0 4px;
  padding-left: 16px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-dim);
}
.review-signals li { margin-bottom: 5px; }
/* The severity the monitor assigned, not a colour chosen here. A 'watch'
   signal is not a warning and must not read as one. */
.review-signals li.sig-warn::marker { color: var(--amber); }
.review-signals li.sig-watch::marker { color: var(--text-faint); }
.sig-key { color: var(--text); font-weight: 600; margin-right: 6px; }
.sig-detail { color: var(--text-dim); }

@media (max-width: 900px) {
  .review-card { padding: 12px; }
  .review-head { gap: 8px; }
  /* Real targets: these three buttons change what the engine will do. */
  .review-card .row-act { min-height: 36px; flex: 1 1 auto; }
}

/* --- Touch targets that are actually tappable ----------------------------- */
/*
   The guard at ~line 1966 raises buttons to 34px on touch devices and then
   immediately lets `button.tiny` back down to 28px. That exemption was written
   when .tiny meant a secondary control in a dense table; it now also covers
   the primary action on Discover ("Add to Copy") and both roster actions
   ("To Discover", "Remove"), which are the most consequential taps in the
   product — one adds a wallet, one deletes it.

   Measured under touch emulation at 360px: 28px tall.

   36px, not 44. Apple's 44pt guidance assumes a control standing alone; these
   sit in table rows where 44 would push the row past a phone screen and cost
   more in lost context than it gains in accuracy. 36 clears the range where
   mis-taps actually happen while keeping a row readable.

   The row-act pair also gets horizontal room: two 28px buttons abutting each
   other is how you delete a wallet meaning to demote it. */
@media (hover: none) and (pointer: coarse) {
  button.tiny,
  .row-act {
    min-height: 36px;
    padding-left: 12px;
    padding-right: 12px;
  }
  .copy-actions { gap: 10px; }
}
/* A QUIET EMPTY STATE.
   "Nothing flagged for review" is good news and should read like a footnote,
   not like the warning it replaces. Same slot, same size, less voice — so the
   region still catches the eye on the day it says something. */
#page-copy .hint-quiet { color: var(--text-faint); margin: 6px 0; }

/* --- Copy speed: the eight-stage stopwatch ----------------------------- */
/* The CPU/NETWORK badge carries the only distinction that matters here, so it
   is a colour and not a word buried in a tooltip: green is ours and winnable,
   dim is somebody else's server. */
.copy-speed { margin: 0 0 18px; }
.speed-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.speed-label {
  font-size: 10px;
  letter-spacing: 0.09em;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid var(--line);
}
.speed-label.is-ok { color: var(--green); border-color: rgba(52, 211, 153, 0.35); }
.speed-label.is-warn { color: var(--amber); border-color: rgba(251, 191, 36, 0.35); }

.speed-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}
.speed-total {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 14px;
}
.speed-total-v { font-size: 20px; font-variant-numeric: tabular-nums; color: var(--text); line-height: 1.1; }
.speed-total-k { font-size: 11px; color: var(--text-dim); }
.speed-total-p { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

.speed-ladder {
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}
.speed-row {
  display: grid;
  grid-template-columns: minmax(120px, 1.6fr) 70px repeat(4, minmax(64px, 1fr));
  gap: 8px;
  padding: 7px 12px;
  align-items: center;
  font-size: 12px;
  border-top: 1px solid var(--line-soft);
}
.speed-row:first-child { border-top: 0; }
.speed-row.is-head {
  background: var(--panel-2);
  color: var(--text-faint);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.speed-row .r { text-align: right; font-variant-numeric: tabular-nums; }
.speed-stage { color: var(--text); }
.speed-kind {
  font-size: 10px;
  text-align: center;
  padding: 2px 0;
  border-radius: 4px;
  border: 1px solid var(--line);
}
.speed-kind.is-cpu { color: var(--green); border-color: rgba(52, 211, 153, 0.3); }
.speed-kind.is-net { color: var(--text-faint); }

/* --- The launch trace -------------------------------------------------- */
/* Dense on purpose. This is read once, under time pressure, with a live token
   on screen — so it is a log, not a table: monospace, one line per step, and
   the elapsed time in the left gutter where the eye scans for the gap. */
.launch-trace {
  margin-top: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  font-family: var(--mono);
}
.launch-trace > summary {
  padding: 8px 12px;
  cursor: pointer;
  font-size: 12px;
  color: var(--text-dim);
  list-style: none;
}
.launch-trace > summary::-webkit-details-marker { display: none; }
.launch-trace > summary::before { content: '▸ '; color: var(--text-faint); }
.launch-trace[open] > summary::before { content: '▾ '; }
.launch-trace.is-warn { border-color: rgba(251, 191, 36, 0.4); }
.launch-trace.is-warn > summary { color: var(--amber); }
.launch-trace.is-bad { border-color: rgba(248, 113, 113, 0.45); }
.launch-trace.is-bad > summary { color: var(--red); }

.trace-steps {
  border-top: 1px solid var(--line-soft);
  max-height: 420px;
  overflow-y: auto;
}
.trace-step {
  display: grid;
  grid-template-columns: 74px minmax(120px, 200px) 1fr;
  gap: 8px;
  padding: 4px 12px;
  font-size: 11px;
  line-height: 1.5;
  border-top: 1px solid var(--line-soft);
  color: var(--text-dim);
}
.trace-step:first-child { border-top: 0; }
.trace-step.is-warn { color: var(--amber); background: rgba(251, 191, 36, 0.05); }
.trace-step.is-error { color: var(--red); background: rgba(248, 113, 113, 0.07); }
.trace-at {
  text-align: right;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.trace-step.is-error .trace-at, .trace-step.is-warn .trace-at { color: inherit; }
.trace-name { color: var(--blue); }
.trace-step.is-error .trace-name { color: var(--red); }
.trace-detail { color: inherit; word-break: break-word; }
/* The structured extras sit under the detail rather than beside it: they are
   for the second read, not the first. */
.trace-data {
  grid-column: 3;
  color: var(--text-faint);
  font-size: 10px;
  word-break: break-all;
}
