/* ============================================================
   CopySync UI — design system for the rewritten dashboard
   Self-contained: no Bootstrap, no icon font, no external CSS.
   ============================================================ */

/* ---- Tokens -------------------------------------------------
   Light is the base palette. Dark redefines only what changes,
   so a colour can never exist in one theme and be missing in the
   other. `data-theme` on <html> wins over the system preference
   in BOTH directions, which is why the dark block is repeated:
   once for the media query (guarded against an explicit light
   choice) and once for the explicit dark choice.
   ------------------------------------------------------------ */
/* Brand palette — identical values to the previous dashboard's app.css, in
   both themes. Cyan → Purple → Green is the CopySync identity and must not
   drift between the two stylesheets. */
:root {
  --cyan:   #00D4FF;
  --purple: #B44AFF;
  --green:  #69F0AE;
  --cyan-rgb:   0, 212, 255;
  --purple-rgb: 180, 74, 255;
  --green-rgb:  105, 240, 174;
  --gradient-brand:   linear-gradient(135deg, #00D4FF 0%, #B44AFF 50%, #69F0AE 100%);
  --gradient-brand-h: linear-gradient(90deg, #00D4FF 0%, #B44AFF 50%, #69F0AE 100%);

  --bg:        #F4F6FA;
  --surface:   #FFFFFF;
  --surface-2: #E9EDF2;
  --border:        rgba(0, 0, 0, 0.08);
  --border-strong: rgba(0, 0, 0, 0.15);
  --text:      #0F172A;
  --text-dim:  #64748B;
  --text-mute: #94A3B8;

  --accent:      var(--cyan);
  --accent-text: #04222A;          /* ink on a cyan fill — cyan is a light colour */
  --accent-soft: rgba(0, 212, 255, 0.10);
  --accent-line: rgba(0, 212, 255, 0.35);

  --pos:      var(--green);
  --pos-soft: rgba(105, 240, 174, 0.14);
  --neg:      #FF5252;
  --neg-soft: rgba(255, 82, 82, 0.12);
  --warn:      #FFB74D;
  --warn-soft: rgba(255, 183, 77, 0.14);

  /* Small coloured text needs more weight than a fill does; brand green and
     amber are light, so text using them sits on a tinted chip (.badge, .alert)
     rather than on the raw page background wherever it is small. */
  --pos-ink:  #0E9F6E;
  --neg-ink:  #E03131;
  --warn-ink: #B45309;
  --accent-ink: #0284A3;           /* same cyan hue, dark enough to read on white */

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 18px;
  --r-xl: 24px;

  --shadow: 0 1px 2px rgba(11,15,25,.05), 0 4px 16px rgba(11,15,25,.05);
  --shadow-lift: 0 2px 6px rgba(11,15,25,.07), 0 16px 44px rgba(11,15,25,.11);

  --ease: cubic-bezier(.4, 0, .2, 1);
  --header-h: 56px;
  --sidebar-w: 252px;
  --page-max: 1120px;

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Dark surfaces come from the previous dashboard verbatim (#060B18 base,
   #0D1526 cards, #182240 elevated). The brand colours above are inherited
   unchanged — on dark they are already the readable variant, so the -ink
   aliases collapse back onto them. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:        #060B18;
    --surface:   #0D1526;
    --surface-2: #182240;
    --border:        rgba(255, 255, 255, 0.07);
    --border-strong: rgba(255, 255, 255, 0.15);
    --text:      #F0F2F5;
    --text-dim:  #8B95A9;
    --text-mute: #5A6478;

    --pos-ink:  var(--green);
    --neg-ink:  #FF5252;
    --warn-ink: #FFB74D;
    --accent-ink: var(--cyan);

    --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 16px rgba(0,0,0,.3);
    --shadow-lift: 0 2px 6px rgba(0,0,0,.5), 0 16px 44px rgba(0,0,0,.45);
  }
}

:root[data-theme="dark"] {
  --bg:        #060B18;
  --surface:   #0D1526;
  --surface-2: #182240;
  --border:        rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.15);
  --text:      #F0F2F5;
  --text-dim:  #8B95A9;
  --text-mute: #5A6478;

  --pos-ink:  var(--green);
  --neg-ink:  #FF5252;
  --warn-ink: #FFB74D;
  --accent-ink: var(--cyan);

  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 16px rgba(0,0,0,.3);
  --shadow-lift: 0 2px 6px rgba(0,0,0,.5), 0 16px 44px rgba(0,0,0,.45);
}

/* ---- Base --------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, figure { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

/* Numbers must not jitter while live values tick. */
.num, td.num, .figure, .stat-value { font-variant-numeric: tabular-nums; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

::selection { background: var(--accent-soft); }

/* An author-level `display:` beats the UA stylesheet's `[hidden]` rule, so
   `element.hidden = true` on .app / .auth-wrap would do nothing and both
   screens would paint at once. This makes the attribute authoritative. */
[hidden] { display: none !important; }

/* An inline <svg> without width/height stretches to fill its container. Every
   icon in this file is inline SVG, so they need a default size or a single one
   in a wide box (the all-clear banner) blows up to the full card. */
svg { width: 16px; height: 16px; flex-shrink: 0; }

.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---- App shell -----------------------------------------------
   Fixed sidebar on the left, content beside it — the same layout the
   previous dashboard used. Below 992px the sidebar slides in over the
   content and a compact top bar takes over.
   -------------------------------------------------------------- */
.app { min-height: 100vh; }

.sidebar {
  position: fixed; top: 0; left: 0; z-index: 90;
  width: var(--sidebar-w); height: 100vh; height: 100dvh;
  background: var(--surface); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  transition: transform .28s var(--ease);
}

.sidebar-brand {
  display: flex; align-items: center; justify-content: center;
  padding: 19px 20px 17px; min-height: 62px;
  border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.brand-logo { height: 26px; width: auto; display: block; }

.sidebar-nav { flex: 1; overflow-y: auto; padding: 14px 12px; }
.sidebar-section {
  padding: 14px 8px 7px; font-size: 11px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text-mute);
}
.sidebar-section:first-child { padding-top: 2px; }

.sidebar-link {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; margin-bottom: 2px; border-radius: var(--r-sm);
  color: var(--text-dim); font-size: 14px; font-weight: 500; cursor: pointer;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.sidebar-link:hover { color: var(--text); background: var(--surface-2); }
.sidebar-link svg { width: 18px; height: 18px; }
.sidebar-link.active {
  color: var(--accent-ink); background: var(--accent-soft); font-weight: 600;
}
.sidebar-link.active::before {
  content: ''; position: absolute; left: -12px; top: 8px; bottom: 8px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--cyan);
  box-shadow: 0 0 8px rgba(var(--cyan-rgb), .45);
}

.sidebar-cta { padding: 0 12px 12px; flex-shrink: 0; }
.sidebar-upgrade {
  padding: 14px; border-radius: var(--r-md);
  background: linear-gradient(135deg, rgba(var(--cyan-rgb), .13), rgba(var(--purple-rgb), .13));
  border: 1px solid var(--accent-line);
}
.sidebar-upgrade-title { font-size: 13.5px; font-weight: 650; }
.sidebar-upgrade-text { font-size: 12.5px; color: var(--text-dim); margin: 5px 0 11px; }

.sidebar-links {
  display: flex; gap: 4px; padding: 0 12px 10px; flex-shrink: 0;
}
.sidebar-ext {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 9px 4px; border-radius: var(--r-sm);
  color: var(--text-mute); font-size: 11px; font-weight: 550;
}
.sidebar-ext:hover { color: var(--text); background: var(--surface-2); }
.sidebar-ext svg { width: 16px; height: 16px; }

.sidebar-foot {
  display: flex; align-items: center; gap: 8px;
  padding: 13px 16px; border-top: 1px solid var(--border); flex-shrink: 0;
  padding-bottom: calc(13px + env(safe-area-inset-bottom, 0px));
}
.sidebar-user {
  display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0;
  cursor: pointer; border-radius: var(--r-sm); padding: 4px; margin: -4px;
}
.sidebar-user:hover { background: var(--surface-2); }
.sidebar-user-name {
  font-size: 13.5px; font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.avatar {
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center; flex-shrink: 0;
  background: var(--gradient-brand); color: #04222A;
  font-size: 12px; font-weight: 800; text-transform: uppercase;
}

.icon-btn {
  width: 32px; height: 32px; display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--text-dim); cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease);
  flex-shrink: 0;
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn svg { width: 17px; height: 17px; }
.icon-btn.danger:hover { color: var(--neg-ink); background: var(--neg-soft); }

/* Content column */
.content { margin-left: var(--sidebar-w); min-height: 100vh; }

/* Mobile top bar — hidden on desktop */
.topbar {
  display: none; position: sticky; top: 0; z-index: 40;
  height: var(--header-h); align-items: center; gap: 10px; padding: 0 14px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
/* Flex so the logo sits on the topbar row exactly as the bare image did
   before it became a link. */
.topbar-brand { display: flex; align-items: center; }

.scrim {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(6, 11, 24, .5); border: 0;
}
.scrim[hidden] { display: none; }

@media (max-width: 991px) {
  .sidebar { transform: translateX(-100%); box-shadow: var(--shadow-lift); }
  .sidebar.open { transform: none; }
  .content { margin-left: 0; }
  .topbar { display: flex; }
}
@media (min-width: 992px) {
  .scrim { display: none !important; }
}

/* Installed as an app (PWA, standalone window): the status bar is drawn over
   the page, so the top bar and the drawer grow by the notch/status-bar
   height and keep their own content below it. In a normal browser tab the
   inset is 0 and none of this changes anything. */
@media (display-mode: standalone) {
  .topbar {
    height: calc(var(--header-h) + env(safe-area-inset-top, 0px));
    padding-top: env(safe-area-inset-top, 0px);
  }
  .sidebar { padding-top: env(safe-area-inset-top, 0px); }
  /* No browser chrome to pull on — stop the whole page rubber-banding. */
  html, body { overscroll-behavior-y: none; }
}

/* ---- Page --------------------------------------------------- */
.page {
  flex: 1;
  width: 100%; max-width: var(--page-max);
  margin: 0 auto; padding: 34px 20px 72px;
}
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; margin-bottom: 22px; flex-wrap: wrap;
}
.page-title { font-size: 25px; font-weight: 660; letter-spacing: -.02em; }
.page-sub { color: var(--text-dim); font-size: 14px; margin-top: 3px; }

/* ---- Hero (the at-a-glance answer) --------------------------- */
.hero { padding: 18px 0 4px; }

.hero-greeting {
  font-size: 14px; color: var(--text-dim); font-weight: 550;
  margin-bottom: 6px;
}
/* Sits inline after the name. Small enough not to compete with the status
   line under it, coloured enough to be noticed once. */
.hero-badge {
  display: inline-flex; align-items: center;
  margin-left: 8px; padding: 2px 9px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 11.5px; font-weight: 650; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
  transition: filter .12s var(--ease);
}
.hero-badge:hover { filter: brightness(.94); }

.hero-status {
  display: flex; align-items: center; gap: 11px;
  font-size: 27px; font-weight: 640; letter-spacing: -.022em;
}
.hero-sub { color: var(--text-dim); font-size: 15px; margin-top: 7px; }

.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.dot.ok    { background: var(--pos); }
.dot.warn  { background: var(--warn); }
.dot.bad   { background: var(--neg); }
.dot.idle  { background: var(--text-mute); }
.dot.live::after {
  content: ''; display: block; width: 10px; height: 10px;
  border-radius: 50%; background: inherit; opacity: .55;
  animation: pulse 2.2s var(--ease) infinite;
}
@keyframes pulse {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .dot.live::after { animation: none; }
}

/* A responsive grid, not a flex row: every figure gets the same column width,
   so the values line up in a row instead of drifting apart. */
.hero-figures {
  display: grid; gap: 26px 32px;
  grid-template-columns: repeat(auto-fit, minmax(206px, 1fr));
  margin: 28px 0 8px;
}
.hero-figure { min-width: 0; }

/* Each value sits in its own card. Centred, so with equal columns the numbers
   spread evenly across the row instead of hugging the left edge of each one. */
.hero-figure {
  text-align: center;
  display: flex; flex-direction: column; justify-content: center;
  padding: 16px 14px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.hero-figure:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.figure-label {
  font-size: 11px; font-weight: 650; letter-spacing: .055em;
  text-transform: uppercase; color: var(--text-mute); margin-bottom: 7px;
}
.figure {
  font-size: 30px; font-weight: 630; letter-spacing: -.03em; line-height: 1.1;
  /* Break only between the amount and the currency — `anywhere` split
     "+4.967,00 USD" into "+4.967,00 US" / "D". */
  overflow-wrap: normal; word-break: normal;
}
.figure.pos { color: var(--pos-ink); }
.figure.neg { color: var(--neg-ink); }
.figure-note { font-size: 12.5px; color: var(--text-dim); margin-top: 6px; line-height: 1.35; }

/* ---- Action items ------------------------------------------- */
.actions { margin-top: 40px; }
.actions-title {
  font-size: 12.5px; font-weight: 600; letter-spacing: .045em;
  text-transform: uppercase; color: var(--text-mute); margin-bottom: 12px;
}
.action {
  display: flex; align-items: center; gap: 13px;
  width: 100%; padding: 14px 16px; margin-bottom: 8px;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--r-md); cursor: pointer; text-align: left;
  transition: background .12s var(--ease), transform .12s var(--ease);
}
.action:hover { background: var(--surface-2); }
.action:active { transform: translateY(1px); }
.action.bad  { border-left-color: var(--neg); }
.action.warn { border-left-color: var(--warn); }
.action-body { flex: 1; min-width: 0; }
.action-text { font-size: 14.5px; font-weight: 550; }
.action-hint { font-size: 13px; color: var(--text-dim); margin-top: 2px; }
.action-go { color: var(--text-mute); flex-shrink: 0; }
.action-go svg { width: 16px; height: 16px; display: block; }
/* Sits between the text and the arrow. flex-shrink:0 keeps the label on one
   line when a long account name squeezes the row. The .btn background
   (--bg) already reads as raised against the row's --surface, hovered or
   not, so the colours are left alone. */
.action-cta { flex-shrink: 0; }

.all-clear {
  display: flex; align-items: center; gap: 10px;
  padding: 15px 16px; border-radius: var(--r-md);
  background: var(--pos-soft); color: var(--pos-ink);
  font-size: 14.5px; font-weight: 550;
}

/* ---- Quick links under the hero ------------------------------ */
.quick { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 38px; }

/* ---- Buttons ------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 16px; border-radius: var(--r-sm);
  border: 1px solid var(--border-strong); background: var(--bg);
  color: var(--text); font-size: 14px; font-weight: 550;
  cursor: pointer; white-space: nowrap;
  transition: background .12s var(--ease), border-color .12s var(--ease),
              opacity .12s var(--ease);
}
.btn:hover { background: var(--surface); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn svg { width: 15px; height: 15px; }
.btn-primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text);
}
.btn-primary:hover { background: var(--accent); filter: brightness(1.07); }
.btn-ghost { border-color: transparent; background: transparent; color: var(--text-dim); }
.btn-ghost:hover { background: var(--surface); color: var(--text); }
.btn-danger { border-color: var(--neg); color: var(--neg-ink); background: transparent; }
.btn-danger:hover { background: var(--neg-soft); }
.btn-sm { padding: 6px 11px; font-size: 13px; }
.btn-block { width: 100%; }

/* ---- Cards & sections ---------------------------------------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 20px;
}
/* Only stacked cards get the gap. Inside a .grid the gap comes from the grid
   itself; applying it here too pushed every card after the first down by 16px
   and made rows of stat cards sit visibly out of line. */
.card + .card { margin-top: 16px; }
.grid > .card + .card, .grid > .card { margin-top: 0; }

/* Used for stat blocks. Visually a card, but it never nests inside one, which
   is what produced card-in-card boxes on the billing and account pages. */
.tile {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 16px;
}
.card .tile { background: var(--bg); }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px; flex-wrap: wrap;
}
.card-title { font-size: 16px; font-weight: 620; letter-spacing: -.01em; }
.card-sub { font-size: 13px; color: var(--text-dim); margin-top: 2px; }

/* A second subject inside one card. Used where two settings belong together
   but are not the same thing — email and Telegram on the notifications page —
   so they read as one topic with two parts instead of two unrelated cards
   stacked on each other. The rule carries the full card width by cancelling
   its padding, which is what makes it read as a division rather than a box. */
.card-sec {
  margin: 18px -20px 0; padding: 18px 20px 0;
  border-top: 1px solid var(--border);
}
/* The section carries a normal card head, so it needs no type rules of its
   own — only a little less air beneath it than a card's own heading, which has
   the whole card under it rather than one block. */
.card-sec .card-head { margin-bottom: 14px; }

/* Categories under a master switch. Indented and hairline-marked so it reads
   as "these belong to the one above" rather than as more equal settings, and
   dimmed while the master is off — they genuinely do nothing then.
   Used by both channels on the notifications page, which is the point: the
   same relationship should look the same on both. */
.cat-group {
  margin: 4px 0 0 14px; padding-left: 14px;
  border-left: 2px solid var(--border);
  transition: opacity .15s ease;
}
.cat-group.off { opacity: .45; }
.cat-group .switch + .switch { margin-top: 2px; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.stat-label {
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-mute);
}
.stat-value {
  font-size: 24px; font-weight: 620; letter-spacing: -.02em; margin-top: 7px;
}
.stat-note { font-size: 13px; color: var(--text-dim); margin-top: 4px; }
.pos { color: var(--pos-ink); }
.neg { color: var(--neg-ink); }
.mute { color: var(--text-dim); }

/* ---- Account rows -------------------------------------------- */
.rows { display: flex; flex-direction: column; }
.row-item {
  display: flex; align-items: center; gap: 14px;
  padding: 15px 4px; border-bottom: 1px solid var(--border);
}
.row-item:last-child { border-bottom: 0; }
.row-main { flex: 1; min-width: 0; }
.row-name {
  font-size: 14.5px; font-weight: 570;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row-meta {
  font-size: 12.5px; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row-value { text-align: right; flex-shrink: 0; }
.row-amount { font-size: 14.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.row-amount-note { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.row-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* ---- Add-on rows ---------------------------------------------- */
/* Three columns rather than "text left, buttons hard right": on a wide card
   the two-part version leaves a metre of empty space in the middle and the
   usage bar ends up floating under the text with nothing beside it. The middle
   column gives the bar a home and the same width in every row, so the numbers
   line up down the card and can be compared at a glance. */
/* Every column is a fixed width except the description. Each row is its own
   grid — CSS only aligns columns WITHIN one grid, never across several — so an
   `auto` action column made the rows that offer Reduce wider there and pushed
   their usage bars ~100px out of line with the rows that do not. Pinning the
   width is what makes the bars sit under each other. 220px fits
   Reduce + field + Buy; rows without Reduce right-align into the same box. */
.addon-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 190px 220px;
  align-items: center; gap: 12px 28px;
  padding: 16px 4px; border-bottom: 1px solid var(--border);
}
.addon-row:last-child { border-bottom: 0; }
.addon-price { font-weight: 600; color: var(--text-dim); }
/* Reduce sits BEFORE the quantity box on purpose. Ordered the other way, the
   rows that offer it push Buy out of line with the rows that do not, and the
   column reads as ragged. */
.addon-actions { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
/* Scoped, because .input sets width:100% further down the file and would win
   on equal specificity. flex-basis pins it against grow/shrink too. */
.addon-row .addon-qty { width: 62px; flex: 0 0 62px; text-align: center; }
.addon-row .addon-actions .btn { flex-shrink: 0; }

@media (max-width: 780px) {
  .addon-row { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .addon-actions { justify-content: flex-start; }
}

/* ---- Badges --------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 620; letter-spacing: .01em;
  background: var(--surface-2); color: var(--text-dim);
  white-space: nowrap;
}
.badge.ok   { background: var(--pos-soft);  color: var(--pos-ink); }
.badge.bad  { background: var(--neg-soft);  color: var(--neg-ink); }
.badge.warn { background: var(--warn-soft); color: var(--warn-ink); }
.badge.accent { background: var(--accent-soft); color: var(--accent-ink); }

/* Managed-hosting trial row in the admin user editor: a state badge, its
   detail, and the reset button pushed to the far edge so it never sits close
   enough to the text to be clicked by accident. Wraps rather than squeezing —
   the detail line carries two dates on a used trial. */
.trial-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.trial-detail { color: var(--text-dim); font-size: 13px; min-width: 0; }
.trial-row .btn { margin-left: auto; }

/* ---- Tables ---------------------------------------------------
   Wide tables scroll inside their own container; the page body
   must never scroll sideways.
   -------------------------------------------------------------- */
.table-wrap { overflow-x: auto; margin: 0 -20px; padding: 0 20px; }

/* A long list that would otherwise stretch its card down the whole page
   scrolls inside itself instead. The height is generous on purpose — the
   point is to cap a runaway card, not to squeeze the content into a slot.
   The header stays put while the rows move, otherwise you lose track of
   which column you are reading halfway down. */
/* ---- Incident update timeline ---------------------------------
   The messages posted while an incident ran, newest first. The line
   down the left is drawn on the container rather than per item, so
   it does not stub out under the last entry.
   -------------------------------------------------------------- */
.inc-timeline { position: relative; padding-left: 18px; margin-top: 4px; }
.inc-timeline::before {
  content: ''; position: absolute; left: 3px; top: 6px; bottom: 6px;
  width: 2px; background: var(--border);
}
.inc-item { position: relative; padding: 0 0 16px; }
.inc-item:last-child { padding-bottom: 0; }
.inc-item::before {
  content: ''; position: absolute; left: -18px; top: 6px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--border);
}
.inc-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 5px; }
.inc-head .cell-dim { font-size: 12px; }
.inc-msg { color: var(--text); font-size: 14px; line-height: 1.55; white-space: pre-wrap; }

/* The height sits on .table-wrap rather than on this outer element on
   purpose: a sticky header attaches itself to the nearest scrolling
   ancestor, and .table-wrap already is one because of its horizontal
   overflow. Capping the outer div instead left the header pinned to a
   container that never scrolls vertically, so it simply scrolled away.
   Both axes therefore belong to the same element. */
.table-scroll .table-wrap {
  max-height: 520px; overflow-y: auto; overscroll-behavior: contain;
  margin: 0; padding: 0;
}
.table-scroll thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface);
}
@media (max-width: 720px) {
  .table-scroll .table-wrap { max-height: 420px; }
}
table { width: 100%; border-collapse: collapse; font-size: 14px; }
thead th {
  text-align: left; padding: 0 14px 10px 0;
  font-size: 11.5px; font-weight: 620; letter-spacing: .045em;
  text-transform: uppercase; color: var(--text-mute);
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
tbody td {
  padding: 12px 14px 12px 0; border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:hover td { background: var(--surface-2); }
/* Every column except the first gets a left gap. Relying on the right padding
   alone failed wherever a right-aligned column sat next to another one — the
   header then read "P/LSTATUS" with the two labels touching. */
thead th + th, tbody td + td { padding-left: 18px; }
td.num, th.num {
  text-align: right; padding-right: 0;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Short values that read badly against either edge — a latency, a role letter.
   Tabular figures keep the digits from dancing as the numbers change. */
td.ctr, th.ctr {
  text-align: center;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.cell-strong { font-weight: 570; }
.cell-dim { color: var(--text-dim); font-size: 13px; }

/* ---- Forms ----------------------------------------------------- */
.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }
.label {
  display: block; font-size: 13px; font-weight: 570;
  margin-bottom: 6px; color: var(--text);
}
.hint { font-size: 12.5px; color: var(--text-dim); margin-top: 5px; }

/* What the settings above add up to, in this account's own figures. Reads as
   the answer to the sentence, so it is set as prose with a quiet rule down
   the side rather than in the monospace box the lot formula uses — that one
   holds an equation, this one holds a statement. */
.eqs-result {
  font-size: 14px; line-height: 1.65; color: var(--text);
  background: var(--surface-2); border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  padding: 12px 14px; margin: 14px 0 16px;
}
.eqs-result.is-empty {
  color: var(--text-dim); border-left-color: var(--border-strong);
}

/* Lot sizing readout: the arithmetic the picked mode actually performs.
   Monospaced because it is a formula, and set apart from .hint so the rule
   and the prose explaining it do not read as one paragraph. */
.formula {
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  color: var(--text); background: var(--surface-2);
  border-radius: var(--r-sm); padding: 9px 12px; margin-bottom: 10px;
}
.input, .select, .textarea {
  width: 100%; padding: 9px 12px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  transition: border-color .12s var(--ease), box-shadow .12s var(--ease);
}
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input:disabled, .select:disabled { background: var(--surface-2); color: var(--text-dim); }
.textarea { min-height: 96px; resize: vertical; }
.select {
  appearance: none; padding-right: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238A94A3' stroke-width='1.6' stroke-linecap='round'%3E%3Cpath d='M4 6.5 8 10.5l4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
  background-size: 15px;
}
/* iOS zooms the whole page when you focus a field whose font-size is under
   16px, and does not zoom back out afterwards. Ours inherit the 15px body size
   (`font: inherit` above) — one pixel short, which is enough. The page then
   sits scaled up, and anything positioned against viewport coordinates,
   the interactive tutorial's highlight boxes above all, points at the wrong
   place.

   `user-scalable=no` in the viewport meta is the usual suggestion and does not
   work: Safari has ignored it since iOS 10 so that people can always zoom. The
   font size is the only lever, and 16px is the documented threshold.

   Scoped to coarse pointers rather than a width breakpoint: this is a property
   of touch devices, and an iPad in landscape is wider than any of our mobile
   breakpoints while behaving exactly the same way. Desktop keeps 15px. */
@media (pointer: coarse) {
  .input, .select, .textarea,
  input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="search"], input[type="tel"],
  input[type="url"], input[type="date"], input[type="datetime-local"],
  select, textarea {
    font-size: 16px;
  }
}

.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Divides two independent rules inside one card. A bare <hr> would render as
   the browser's default rule, which is a light 3D groove and reads as a
   scratch on a dark surface. */
.sep { border: 0; border-top: 1px solid var(--border); margin: 18px 0; }
.row-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }

.switch { display: flex; align-items: center; gap: 11px; cursor: pointer; padding: 4px 0; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 38px; height: 22px; border-radius: 999px; flex-shrink: 0;
  background: var(--border-strong); position: relative;
  transition: background .16s var(--ease);
}
.switch-track::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%; background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
  transition: transform .16s var(--ease);
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track::after { transform: translateX(16px); }
.switch input:focus-visible + .switch-track { box-shadow: 0 0 0 3px var(--accent-soft); }
.switch > span:last-child { display: block; }
.switch-text { display: block; font-size: 14px; }
.switch-sub { font-size: 12.5px; color: var(--text-dim); margin-top: 1px; }

/* ---- Segmented control ----------------------------------------- */
.seg {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--surface-2); border-radius: var(--r-sm);
}
.seg button {
  padding: 5px 13px; border: 0; border-radius: 6px;
  background: transparent; color: var(--text-dim);
  font-size: 13px; font-weight: 550; cursor: pointer;
}
.seg button.active { background: var(--bg); color: var(--text); box-shadow: var(--shadow); }

/* ---- Modal ------------------------------------------------------ */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(6, 9, 16, .55);
  display: grid; place-items: center; padding: 20px;
  overflow-y: auto;
}
.modal-backdrop[hidden] { display: none; }
.modal {
  width: 100%; max-width: 520px; margin: auto;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lift);
  animation: modal-in .16s var(--ease);
}
.modal.wide { max-width: 760px; }
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.99); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .modal { animation: none; } }
.modal-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; padding: 20px 20px 0;
}
.modal-title { font-size: 17px; font-weight: 620; letter-spacing: -.01em; }
.modal-sub { font-size: 13.5px; color: var(--text-dim); margin-top: 3px; }
.modal-body { padding: 18px 20px; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 9px;
  padding: 0 20px 20px;
}

/* ---- Toast ------------------------------------------------------- */
.toasts {
  position: fixed; bottom: 20px; right: 20px; z-index: 200;
  display: flex; flex-direction: column-reverse; gap: 9px;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 10px;
  min-width: 250px; max-width: 380px; padding: 12px 15px;
  background: var(--bg); border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md); box-shadow: var(--shadow-lift);
  font-size: 14px; pointer-events: auto;
  animation: toast-in .18s var(--ease);
}
.toast.ok  { border-left-color: var(--pos); }
.toast.bad { border-left-color: var(--neg); }
.toast.warn { border-left-color: var(--warn); }
@keyframes toast-in {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

/* ---- Empty & loading ---------------------------------------------- */
.empty { padding: 46px 20px; text-align: center; }
.empty-title { font-size: 15.5px; font-weight: 570; }
.empty-text { font-size: 14px; color: var(--text-dim); margin-top: 5px; }
.empty .btn { margin-top: 18px; }

.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.3s linear infinite;
  border-radius: 6px; color: transparent !important;
}
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

.spinner {
  width: 15px; height: 15px; flex-shrink: 0;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.alert {
  padding: 12px 14px; border-radius: var(--r-sm);
  font-size: 13.5px; margin-bottom: 14px; line-height: 1.5;
}
/* An alert may lead with an icon. Flex only kicks in when one is present, so
   plain text alerts keep their existing block layout. */
.alert:has(> svg) { display: flex; align-items: flex-start; gap: 10px; }
.alert > svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }
.alert code {
  font-size: 12.5px; padding: 1px 5px; border-radius: 4px;
  background: rgba(128, 128, 128, .18);
}
.alert.bad  { background: var(--neg-soft);  color: var(--neg-ink); }
.alert.ok   { background: var(--pos-soft);  color: var(--pos-ink); }
.alert.warn { background: var(--warn-soft); color: var(--warn-ink); }
.alert.info { background: var(--accent-soft); color: var(--accent-ink); }

/* ---- Login ----------------------------------------------------------
   The previous design: a full-bleed backdrop with three slowly drifting
   brand-coloured orbs behind a frosted card. Values taken from app.css so
   it matches what customers know.
   --------------------------------------------------------------------- */
.auth-wrap {
  position: relative; min-height: 100vh;
  display: grid; place-items: center; padding: 24px;
  background: var(--bg); overflow: hidden;
}

.auth-bg { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.auth-glow { position: absolute; border-radius: 50%; filter: blur(140px);
             animation: glowDrift 16s ease-in-out infinite; }
.auth-glow-1 { width: 500px; height: 500px; background: var(--cyan);
               top: -15%; left: -10%; opacity: .15; }
.auth-glow-2 { width: 420px; height: 420px; background: var(--purple);
               top: 40%; right: -8%; opacity: .12; animation-delay: -5s; }
.auth-glow-3 { width: 360px; height: 360px; background: var(--green);
               bottom: -10%; left: 30%; opacity: .08; animation-delay: -10s; }
@keyframes glowDrift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(25px, -18px) scale(1.05); }
  66%      { transform: translate(-18px, 12px) scale(.95); }
}
@media (prefers-reduced-motion: reduce) { .auth-glow { animation: none; } }

.auth-theme {
  position: absolute; top: 16px; right: 16px; z-index: 10;
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-dim);
}
.auth-theme:hover { color: var(--text); }

.auth-card {
  position: relative; z-index: 1; width: 100%; max-width: 420px;
}
.auth-card > .card {
  padding: 40px 34px; border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(32px) saturate(1.5);
  -webkit-backdrop-filter: blur(32px) saturate(1.5);
  box-shadow: var(--shadow-lift), 0 0 60px rgba(var(--cyan-rgb), .05);
}
@supports not (backdrop-filter: blur(1px)) {
  .auth-card > .card { background: var(--surface); }
}
.auth-brand {
  display: flex; flex-direction: column; align-items: center;
  margin-bottom: 30px;
}
.auth-brand img {
  height: 46px; width: auto; display: block;
  filter: drop-shadow(0 4px 12px rgba(var(--cyan-rgb), .2));
}
.auth-tagline {
  font-size: 13px; color: var(--text-mute); margin-top: 12px; letter-spacing: .02em;
}
.auth-title { font-size: 20px; font-weight: 620; text-align: center; }
.auth-sub {
  font-size: 14px; color: var(--text-dim);
  text-align: center; margin: 6px 0 24px;
}
.auth-foot { text-align: center; margin-top: 18px; font-size: 13.5px; color: var(--text-dim); }
.auth-foot a { color: var(--accent-ink); font-weight: 550; }
.link { color: var(--accent-ink); cursor: pointer; font-weight: 550; }
.link:hover { text-decoration: underline; }

/* ---- Copy-to-clipboard field ---------------------------------------- */
.copy-field { display: flex; gap: 8px; }
.copy-field .input {
  font-family: var(--mono); font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis;
}

/* ---- Chart ----------------------------------------------------------- */
/* Chart.js sizes the canvas itself from this box. Forcing width/height on the
   canvas with !important desynchronises its internal coordinate system from the
   painted size, which squeezed every series into the left edge. Only the box is
   sized here; the canvas is left alone. */
.chart-box { position: relative; height: 260px; width: 100%; }
.chart-box canvas { display: block; }

/* ---- Responsive -------------------------------------------------------
   The nav collapses into a bottom bar on phones: reachable with a thumb
   and it keeps the header free for identity + theme only.
   ---------------------------------------------------------------------- */
@media (max-width: 860px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .figure { font-size: 30px; }
}

@media (max-width: 680px) {
  body { font-size: 14.5px; }
  .page { padding: 24px 16px 96px; }
  .topbar { padding: 0 14px; }
  .usermenu-name { display: none; }
  .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
  .row-2, .row-3 { grid-template-columns: minmax(0, 1fr); }
  .hero-status { font-size: 22px; }
  .figure { font-size: 27px; }
  .page-title { font-size: 21px; }
  .table-wrap { margin: 0 -16px; padding: 0 16px; }
  .card { padding: 16px; border-radius: var(--r-md); }

  .nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    height: 58px; gap: 0; padding: 0 4px;
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter: saturate(180%) blur(12px);
    border-top: 1px solid var(--border);
    justify-content: space-around;
  }
  @supports not (backdrop-filter: blur(1px)) { .nav { background: var(--bg); } }
  .nav a {
    flex: 1; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 3px;
    padding: 8px 2px; font-size: 10.5px; font-weight: 550; border-radius: 0;
  }
  .nav a.active { background: transparent; color: var(--accent-ink); }
  .nav a:hover { background: transparent; }
  .nav svg { width: 19px; height: 19px; }
  .toasts { left: 14px; right: 14px; bottom: 70px; }
  .toast { max-width: none; }
  .modal-backdrop { padding: 0; align-items: flex-end; }
  .modal { max-width: none; border-radius: var(--r-lg) var(--r-lg) 0 0; }
}

/* On desktop the nav shows text only — icons would be redundant noise. */
@media (min-width: 681px) {
  .nav svg { display: none; }
}

@media print {
  .topbar, .nav, .toasts, .row-actions, .wa-widget { display: none !important; }
  .page { max-width: none; padding: 0; }
}

/* ---- Sub-navigation inside a detail page ----------------------------- */
.tabs {
  display: flex; gap: 4px; overflow-x: auto;
  border-bottom: 1px solid var(--border); margin-bottom: 20px;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  padding: 10px 14px; border: 0; background: transparent;
  color: var(--text-dim); font-size: 14px; font-weight: 550;
  cursor: pointer; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button:hover { color: var(--text); }
.tabs button.active { color: var(--text); border-bottom-color: var(--accent); }

/* ---- Key/value list --------------------------------------------------- */
.kv { display: grid; grid-template-columns: minmax(0,auto) minmax(0,1fr); gap: 9px 20px; }
.kv dt { color: var(--text-dim); font-size: 13.5px; }
.kv dd { margin: 0; font-size: 13.5px; font-weight: 550; text-align: right;
         overflow-wrap: anywhere; }

/* ---- Full-width banner ------------------------------------------------ */
.banner {
  display: flex; align-items: center; gap: 11px;
  padding: 11px 20px; font-size: 14px;
  border-bottom: 1px solid var(--border);
}
.banner.warn { background: var(--warn-soft); color: var(--warn-ink); }
.banner.info { background: var(--accent-soft); color: var(--accent-ink); }
.banner svg { width: 16px; height: 16px; flex-shrink: 0; }
.banner-close { margin-left: auto; }

.mono { font-family: var(--mono); font-size: 13px; }

/* ---- Back link -------------------------------------------------------- */
.back {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-dim); font-size: 13.5px; font-weight: 550;
  cursor: pointer; margin-bottom: 14px;
}
.back:hover { color: var(--text); }
.back svg { width: 15px; height: 15px; transform: rotate(180deg); }

/* ---- Sparkline / mini bar --------------------------------------------- */
.meter { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.meter > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.meter.pos > span { background: var(--pos); }
.meter.neg > span { background: var(--neg); }

/* ---- Two-column detail layout ----------------------------------------- */
.split { display: grid; grid-template-columns: minmax(0,2fr) minmax(0,1fr); gap: 16px; align-items: start; }
@media (max-width: 860px) { .split { grid-template-columns: minmax(0,1fr); } }
/* Wide mode: the left column takes the whole width and the side panels drop
   underneath. Used by the trades card when it shows every column — a table
   with thirteen of them has no business in a two-thirds column. */
.split.split-wide { grid-template-columns: minmax(0,1fr); }

/* ---- Chips ------------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-dim);
  font-size: 11px; font-weight: 620; letter-spacing: .01em; white-space: nowrap;
}
.chip-accent { background: var(--accent-soft); color: var(--accent-ink); }

/* Toggleable pills — the Telegram alert categories. A raw checkbox next to
   the styled switch reads as unfinished; a pill that lights up when active
   speaks the same language as the chips used everywhere else. */
.chip-ok     { background: var(--pos-soft);    color: var(--pos-ink); }
.chip-warn   { background: var(--warn-soft);   color: var(--warn-ink); }
.chip-purple { background: rgba(var(--purple-rgb), .14); color: var(--purple); }
:root:not([data-theme="dark"]) .chip-purple { color: #7C3AED; }

/* ═══ Overview: master metric card + folding follower rows ═══════════════ */
.dgrp { margin-bottom: 14px; }

.acard {
  position: relative;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.acard::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--border); transition: background .3s, box-shadow .3s;
}
.acard.is-online::before {
  background: var(--gradient-brand-h);
  box-shadow: 0 0 12px rgba(var(--cyan-rgb), .2);
}
.acard.is-offline { opacity: .72; }
.acard.is-offline:hover { opacity: .95; }
.acard:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }

/* ── An account in a bad state is outlined, not dimmed ──────────────────
   Applied from accountState().tone === 'bad': offline, setup failed, risk
   limit hit. Paused and "setting up" are warn and stay as they were — they
   are states someone chose or is waiting through, not faults.

   is-offline fades the card to .72, which is right for "nothing happening"
   and exactly wrong for a fault, so the opacity is restored here: a problem
   should be the first thing the eye lands on, not the faintest. The border
   carries the signal rather than a fill, so the numbers inside stay
   readable in both themes. */
.acard.is-bad,
.acc-card.is-bad {
  border-color: var(--neg);
  box-shadow: 0 0 0 1px var(--neg-soft);
  opacity: 1;
}
.acard.is-bad::before { background: var(--neg); box-shadow: none; }
.acard.is-bad:hover,
.acc-card.is-bad:hover { border-color: var(--neg); box-shadow: 0 0 0 3px var(--neg-soft); }

.drow.is-bad {
  border-color: var(--neg);
  background: var(--neg-soft);
  opacity: 1;
}
.drow.is-bad:hover { border-color: var(--neg); }

/* In the list view the row has no border of its own, so the marker is a bar
   on the leading edge — visible without tinting a whole table row red. */
tr.is-bad > td:first-child { box-shadow: inset 3px 0 0 var(--neg); }

/* The card is a row: a full-height fold rail on the left, then the content.
   Putting the control on the edge and centring it over the whole card makes
   it read as "this whole card opens", which a small corner button did not. */
.acard { display: flex; align-items: stretch; }

.acard-fold {
  flex-shrink: 0; width: 52px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 0; border-right: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-dim);
  cursor: pointer;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.acard-fold svg {
  width: 20px; height: 20px;
  transition: transform .2s var(--ease);
}
/* The count tells you what is behind the fold before you open it. */
.acard-fold-count {
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}
.acard-fold:hover { background: var(--accent-soft); color: var(--accent-ink); }
.dgrp.open .acard-fold { background: var(--accent-soft); color: var(--accent-ink); }
.dgrp.open .acard-fold svg { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .acard-fold svg { transition: none; } }

.acard-body { flex: 1; min-width: 0; padding: 18px 20px; cursor: pointer; }
.acard-top { margin-bottom: 14px; }
.acard-name-row {
  display: flex; align-items: center; gap: 9px; margin-bottom: 8px; flex-wrap: wrap;
}
.acard-name { font-size: 16px; font-weight: 640; letter-spacing: -.01em; }
.acard-tags { display: flex; gap: 5px; flex-wrap: wrap; }

.acard-metrics {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
  padding: 14px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.acard-metric { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.acard-metric-label {
  font-size: 10.5px; font-weight: 650; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-mute);
}
.acard-metric-value {
  font-size: 16px; font-weight: 640; font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.acard-metric-sub { font-size: .78em; opacity: .8; font-weight: 550; }

.acard-foot {
  display: flex; gap: 14px; flex-wrap: wrap;
  padding-top: 12px; font-size: 12.5px; color: var(--text-dim);
}
.acard-foot-followers { margin-left: auto; font-weight: 600; color: var(--accent-ink); }

/* The fold */
.dgrp-followers {
  display: grid; grid-template-rows: 0fr; visibility: hidden;
  transition: grid-template-rows .22s var(--ease), visibility .22s;
}
.dgrp.open .dgrp-followers { grid-template-rows: 1fr; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .dgrp-followers { transition: none; } }
.dgrp-inner {
  min-height: 0; overflow: hidden;
  margin-left: 26px; padding-left: 22px;
  border-left: 2px dashed var(--border-strong);
}

.drow {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px; margin-top: 8px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); cursor: pointer;
  transition: transform .15s var(--ease), border-color .15s var(--ease);
}
.drow:hover { transform: translateX(2px); border-color: var(--border-strong); }
.drow.is-offline { opacity: .72; }
@media (prefers-reduced-motion: reduce) { .drow { transition: none; } }

.drow-branch {
  position: absolute; left: -22px; top: 50%;
  width: 20px; height: 2px; background: var(--border-strong);
}
.drow-avatar {
  width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 12px; font-weight: 750;
}
.drow-name-wrap { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.drow-name {
  font-size: 14px; font-weight: 570;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.drow-name-wrap small { font-size: 11.5px; color: var(--text-mute); }
.drow-status {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-dim); white-space: nowrap;
}
.drow-value {
  font-size: 13.5px; font-weight: 600; font-variant-numeric: tabular-nums;
  min-width: 96px; text-align: right; white-space: nowrap;
}
.drow-orders { font-size: 12px; color: var(--text-mute); white-space: nowrap; }

.dgrp-empty {
  position: relative; margin-top: 8px; padding: 13px 15px;
  font-size: 13.5px; color: var(--text-dim);
  border: 1px dashed var(--border-strong); border-radius: var(--r-md);
}
.dgrp-empty::before {
  content: ''; position: absolute; left: -22px; top: 50%;
  width: 20px; height: 2px; background: var(--border-strong);
}

/* ═══ Accounts page: master card, connector, follower cards on a rail ═════ */
.agrp {
  position: relative;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 16px; margin-bottom: 20px; overflow: hidden;
}
.agrp::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--gradient-brand-h); opacity: 0; transition: opacity .2s var(--ease);
}
.agrp.is-online::before { opacity: 1; }

.acc-card {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 14px 16px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.acc-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.acc-card-body { flex: 1; min-width: 0; cursor: pointer; }

.acc-card-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
.acc-card-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.acc-card-name { font-size: 15px; font-weight: 620; letter-spacing: -.01em; }
.acc-card-sub { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 12px; }

.acc-stats { display: grid; gap: 8px; }
.acc-stats-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.acc-stats-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.acc-stat {
  padding: 8px 10px; border-radius: var(--r-sm);
  background: var(--surface-2); text-align: center;
}
.acc-stat-label {
  font-size: 10px; font-weight: 650; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-mute);
}
.acc-stat-value {
  font-size: 13.5px; font-weight: 620; margin-top: 3px;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}

.acc-actions { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; }
.acc-actions .btn { justify-content: flex-start; }

/* Connector pill, hanging off the master card by a rounded elbow */
.agrp-conn {
  position: relative;
  display: inline-flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin: 12px 4px 10px 24px; padding: 6px 14px;
  border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12.5px; color: var(--text-dim);
}
.agrp-conn::before {
  content: ''; position: absolute; left: -12px; top: -10px;
  width: 12px; height: calc(50% + 10px);
  border-left: 2px dashed var(--border-strong);
  border-bottom: 2px dashed var(--border-strong);
  border-bottom-left-radius: 10px;
}
.agrp-conn-label { display: inline-flex; align-items: center; gap: 5px; color: var(--text); font-weight: 600; }
.agrp-conn-label svg { width: 14px; height: 14px; color: var(--accent-ink); }
.agrp-conn-label strong { color: var(--accent-ink); font-weight: 700; }
.agrp-stats { display: inline-flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.agrp-stat { display: inline-flex; align-items: center; gap: 6px; font-weight: 550;
             font-variant-numeric: tabular-nums; }

/* Followers hang off a dashed rail */
.agrp-followers {
  display: flex; flex-direction: column; gap: 10px;
  margin: 0 0 4px 24px; padding-left: 20px;
  border-left: 2px dashed var(--border-strong);
}
.agrp-followers > .acc-card, .agrp-empty, .agrp-add { position: relative; }
.agrp-followers > .acc-card::before,
.agrp-empty::before,
.agrp-add::before {
  content: ''; position: absolute; left: -22px; top: 28px;
  width: 20px; height: 2px; background: var(--border-strong);
}
.agrp-empty::before, .agrp-add::before { top: 50%; }
.agrp-empty {
  padding: 13px 15px; font-size: 13.5px; color: var(--text-dim);
  border: 1px dashed var(--border-strong); border-radius: var(--r-md);
}
.agrp-add { align-self: flex-start; margin-top: 2px; }

.agrp-head { display: flex; align-items: center; gap: 12px; padding: 2px 4px 12px; }
.agrp-orphan { border-color: var(--warn); }

@media (max-width: 860px) {
  .acard-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .acc-stats-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
  .acc-card { flex-direction: column; }
  .acc-actions { flex-direction: row; width: 100%; }
  .acc-actions .btn { flex: 1; justify-content: center; }
  .acc-stats-3, .acc-stats-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .drow { flex-wrap: wrap; gap: 8px; }
  .drow-value { min-width: 0; }
  .dgrp-inner { margin-left: 14px; padding-left: 16px; }
  .drow-branch { left: -16px; width: 14px; }
  .agrp-followers { margin-left: 12px; padding-left: 16px; }
  .agrp-conn { margin-left: 12px; }
  .agrp-followers > .acc-card::before, .agrp-empty::before, .agrp-add::before {
    left: -18px; width: 16px;
  }
}

/* Heading row above a section on the overview */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 42px 0 12px;
}
.section-head .link { font-size: 13.5px; }

/* ---- Plan usage strip ---------------------------------------------------
   Neutral while there is room; the numbers and fills only take on colour once
   a category passes 80%. Sitting above the accounts it answers "how much of
   my plan am I using?" without competing with the health headline.
   ------------------------------------------------------------------------ */
.usage {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  padding: 13px 18px; margin-bottom: 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.usage-tight { border-color: var(--warn); background: var(--warn-soft); }
.usage-plan { flex-shrink: 0; }

.usage-bars {
  flex: 1; min-width: 0;
  display: grid; gap: 8px 24px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.usage-bar { min-width: 0; }
.usage-bar-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 12px; color: var(--text-dim); margin-bottom: 5px;
}
.usage-bar-num { font-weight: 650; font-variant-numeric: tabular-nums; color: var(--text); }
.usage-bar-num.warn { color: var(--warn-ink); }
.usage-bar-num.bad  { color: var(--neg-ink); }

.usage-track {
  height: 5px; border-radius: 999px; background: var(--surface-2); overflow: hidden;
}
.usage-fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--text-mute);
  transition: width .3s var(--ease);
}
.usage-fill.warn { background: var(--warn); }
.usage-fill.bad  { background: var(--neg); }
@media (prefers-reduced-motion: reduce) { .usage-fill { transition: none; } }

@media (max-width: 680px) {
  .usage { gap: 12px; padding: 12px 14px; }
  .usage-bars { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Overview figures on phones ----------------------------------------
   One value per row turned the top of the dashboard into a long scroll. Two
   compact tiles per row keep all of it on one screen, and centring inside a
   bordered tile reads as a set of numbers rather than an unfinished list.
   ------------------------------------------------------------------------ */
@media (max-width: 680px) {
  .hero-figures {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    margin: 24px 0 4px;
  }
  .hero-figure { padding: 13px 12px; }
  .hero-figure .figure-label {
    font-size: 9.5px; letter-spacing: .05em; margin-bottom: 5px;
  }
  .hero-figure .figure { font-size: 20px; letter-spacing: -.02em; }
  .hero-figure .figure-note { font-size: 11px; margin-top: 4px; line-height: 1.35; }
  /* An odd number of values would leave the last tile stranded in a half row;
     letting it span both columns closes the grid off cleanly. */
  .hero-figure:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

@media (max-width: 420px) {
  .hero-figure .figure { font-size: 18px; }
}

/* Faint equity trace behind a master card on the overview. */
.acard-spark {
  position: absolute; left: 0; right: 0; bottom: 0;
  width: 100%; height: 46px; pointer-events: none; opacity: .8;
}
.acard-body, .acard-fold { position: relative; z-index: 1; }

/* ---- Download cards -----------------------------------------------------
   Three options side by side; the recommended one is lifted out with a
   stronger border rather than a different colour, so the page stays calm.
   ------------------------------------------------------------------------ */
.dl-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.dl-card {
  position: relative;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 28px 22px 20px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.dl-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.dl-primary { border-color: var(--accent); box-shadow: var(--shadow); }

.dl-icon {
  width: 56px; height: 56px; border-radius: var(--r-md);
  display: grid; place-items: center; margin-bottom: 16px;
  background: var(--accent-soft); color: var(--accent-ink);
}
.dl-icon svg { width: 26px; height: 26px; }
.dl-primary .dl-icon { background: var(--accent-soft); }

.dl-badge { margin-bottom: 10px; }
.dl-platform {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-mute); margin-bottom: 5px;
}
.dl-title { font-size: 19px; font-weight: 650; letter-spacing: -.015em; }
.dl-desc {
  font-size: 13.5px; color: var(--text-dim); margin: 10px 0 16px;
  line-height: 1.5;
}

.dl-features {
  width: 100%; margin: 0 0 18px; padding: 16px 0 0;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 10px;
}
.dl-features li {
  display: flex; align-items: center; gap: 9px;
  font-size: 13.5px; text-align: left;
}
.dl-features svg { width: 15px; height: 15px; color: var(--pos-ink); flex-shrink: 0; }

.dl-meta {
  display: flex; align-items: center; justify-content: center;
  gap: 14px; flex-wrap: wrap; margin-top: 12px;
  font-size: 12px; color: var(--text-mute);
}
.dl-count { font-weight: 650; color: var(--accent-ink); cursor: help; }

.dl-hint {
  margin: 22px auto 0; max-width: 760px; text-align: center;
  font-size: 13px; color: var(--text-dim); line-height: 1.6;
}
.dl-hint b { color: var(--text); }

/* Einrichtungsanleitung unter den Downloads.
   Zwei Karten nebeneinander ab Tablet-Breite, darunter gestapelt. Die
   Nummernkreise sind absichtlich klein und ruhig: die Anleitung soll neben den
   Download-Karten stehen, nicht mit ihnen um Aufmerksamkeit ringen. */
.sg-wrap {
  margin: 26px auto 0; max-width: 980px;
  display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sg-steps { display: grid; gap: 14px; margin-top: 14px; }
.sg-step { display: flex; gap: 12px; align-items: flex-start; }
.sg-num {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  color: var(--accent-text); font-size: 11px; font-weight: 700;
  line-height: 20px; text-align: center;
}
.sg-title { font-size: 13.5px; font-weight: 600; color: var(--text); margin-bottom: 2px; }
.sg-text { font-size: 13px; color: var(--text-dim); line-height: 1.6; }
.sg-text b { color: var(--text); font-weight: 600; }
.sg-text a { color: var(--accent-text); }
@media (max-width: 860px) { .sg-wrap { grid-template-columns: minmax(0, 1fr); } }

.dl-tp {
  margin: 26px auto 0; max-width: 420px; padding: 18px;
  text-align: center; border-radius: var(--r-md);
  background: var(--pos-soft); border: 1px solid var(--border);
}
.dl-tp-text { font-size: 13.5px; color: var(--text-dim); margin-bottom: 12px; }

.banner-text { flex: 0 1 auto; }
.banner-code {
  padding: 2px 9px; border-radius: 6px; font-weight: 700; font-size: 12.5px;
  background: color-mix(in srgb, currentColor 14%, transparent);
  border: 1px dashed currentColor;
}
.banner-link { font-weight: 650; text-decoration: underline; }
/* Same weight as the link, but a button: the install prompt happens in
   place, and an underlined "Install" would promise a page that never comes. */
.banner-btn {
  padding: 4px 12px; border-radius: 999px; border: 1px solid currentColor;
  background: transparent; color: inherit; font: inherit; font-size: 13px;
  font-weight: 650; cursor: pointer; white-space: nowrap;
}
.banner-btn:hover { background: color-mix(in srgb, currentColor 12%, transparent); }

/* "Add to home screen" walkthrough — three numbered steps, each with the
   glyph the person is about to look for on their screen. */
.install-steps { list-style: none; margin: 0 0 12px; padding: 0; counter-reset: step; }
.install-steps li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; font-size: 14px; line-height: 1.45;
  border-bottom: 1px solid var(--border);
}
.install-steps li:last-child { border-bottom: 0; }
.install-steps b { font-weight: 650; }
.install-ico {
  flex-shrink: 0; width: 34px; height: 34px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-ink);
}
.install-ico svg { width: 18px; height: 18px; }

/* ---- Login form, matching the previous dashboard -------------------------
   Field, button and link styling lifted from app.css so the sign-in screen
   customers know is unchanged apart from sitting in the new shell.
   ------------------------------------------------------------------------ */
.auth-head { font-size: 17px; font-weight: 700; text-align: center; margin: 0 0 6px; }
.auth-head-sub {
  font-size: 13px; color: var(--text-dim); text-align: center; margin: 0 0 18px;
}

.auth-field { margin-bottom: 16px; }
.auth-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.auth-label {
  display: block; font-size: 12px; font-weight: 600;
  color: var(--text-dim); text-transform: uppercase;
  letter-spacing: .05em; margin-bottom: 6px;
}

.auth-input {
  width: 100%; padding: 10px 14px; font-size: 14px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.auth-input::placeholder { color: var(--text-mute); }
.auth-input:focus {
  outline: none;
  border-color: rgba(var(--cyan-rgb), .45);
  box-shadow: 0 0 0 3px rgba(var(--cyan-rgb), .10), 0 0 12px rgba(var(--cyan-rgb), .08);
}
.auth-code {
  text-align: center; letter-spacing: .35em; font-size: 20px; font-weight: 600;
}

/* The brand gradient button from the previous design. */
.auth-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin-bottom: 14px; padding: 11px 22px;
  background: linear-gradient(135deg, var(--cyan), var(--green));
  color: #04222A; font-weight: 650; font-size: 14px;
  border: 0; border-radius: var(--r-sm); cursor: pointer;
  box-shadow: 0 4px 16px rgba(var(--cyan-rgb), .18);
  transition: filter .2s var(--ease), transform .12s var(--ease);
}
.auth-btn:hover { filter: brightness(1.06); }
.auth-btn:active { transform: translateY(1px); }
.auth-btn:disabled { opacity: .6; cursor: not-allowed; }
.auth-btn svg { width: 16px; height: 16px; }

.auth-ghost {
  display: block; width: 100%; padding: 8px 16px;
  background: transparent; border: 0; cursor: pointer;
  color: var(--text-dim); font-size: 13px;
}
.auth-ghost:hover { color: var(--accent-ink); }
.auth-ghost strong { color: var(--accent-ink); }

.auth-forgot { text-align: center; margin-bottom: 14px; }
.auth-forgot .link { font-size: 13px; color: var(--text-dim); font-weight: 500; }
.auth-forgot .link:hover { color: var(--accent-ink); }

.auth-check {
  display: flex; align-items: flex-start; gap: 9px; margin-bottom: 16px;
  font-size: 12.5px; color: var(--text-dim); line-height: 1.45; cursor: pointer;
}
.auth-check input {
  flex-shrink: 0; margin-top: 2px; width: 16px; height: 16px;
  accent-color: var(--cyan); cursor: pointer;
}
.auth-check a { color: var(--accent-ink); }
.auth-check a:hover { text-decoration: underline; }

.auth-error, .auth-success {
  text-align: center; font-size: 13px; margin-top: 14px;
  padding: 10px 14px; border-radius: var(--r-sm);
}
.auth-error   { color: var(--neg-ink); background: var(--neg-soft); border: 1px solid var(--neg-soft); }
.auth-success { color: var(--pos-ink); background: var(--pos-soft); border: 1px solid var(--pos-soft); }

.auth-foot-note {
  text-align: center; font-size: 12px; color: var(--text-mute); margin: 12px 0 0;
}

@media (max-width: 420px) {
  .auth-row { grid-template-columns: 1fr; }
  .auth-card > .card { padding: 30px 22px; }
}

/* Coupon code inside a promo banner — a button, because it is copyable. */
.banner-code {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 6px; cursor: pointer;
  font-weight: 700; font-size: 12.5px; color: inherit;
  background: color-mix(in srgb, currentColor 14%, transparent);
  border: 1px dashed currentColor;
  transition: background .14s var(--ease);
}
.banner-code:hover { background: color-mix(in srgb, currentColor 24%, transparent); }
.banner-code svg { width: 13px; height: 13px; opacity: .75; }
.banner-code.copied { background: var(--pos-soft); border-style: solid; }



/* Several currencies inside one figure card, one under the other. */
.figure-multi { display: flex; flex-direction: column; gap: 4px; }
.figure-line { font-size: inherit; font-weight: inherit; letter-spacing: inherit; line-height: 1.15; }
.figure-line.pos { color: var(--pos-ink); }
.figure-line.neg { color: var(--neg-ink); }
/* With more than one currency the amounts share the card, so they step down a
   size — otherwise two full-size figures crowd the tile and wrap. */
.hero-figure-stack .figure-line { font-size: .72em; letter-spacing: -.02em; }
.hero-figure-stack .figure-multi > .figure-line:nth-child(n+3) { font-size: .62em; }

/* ---- API key card on the accounts page ---------------------------------- */
.apikey { padding: 0; overflow: hidden; }
.apikey-head {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 16px 20px; border: 0; background: transparent;
  cursor: pointer; text-align: left; color: inherit; font: inherit;
}
.apikey-head:hover { background: var(--surface-2); }
.apikey-head > svg:first-child { color: var(--accent-ink); }
.apikey-chevron {
  margin-left: auto; display: grid; place-items: center; color: var(--text-mute);
  transition: transform .2s var(--ease);
}
.apikey.open .apikey-chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .apikey-chevron { transition: none; } }

.apikey-body {
  display: grid; grid-template-rows: 0fr; visibility: hidden;
  transition: grid-template-rows .22s var(--ease), visibility .22s;
}
.apikey.open .apikey-body { grid-template-rows: 1fr; visibility: visible; }
.apikey-inner { min-height: 0; overflow: hidden; padding: 0 20px; }
.apikey.open .apikey-inner { padding-bottom: 18px; }
@media (prefers-reduced-motion: reduce) { .apikey-body { transition: none; } }

@media (max-width: 680px) {
  .apikey-head { padding: 14px 16px; }
  .apikey-inner { padding: 0 16px; }
  .apikey.open .apikey-inner { padding-bottom: 16px; }
  .apikey .copy-field { flex-wrap: wrap; }
  .apikey .copy-field .input { flex: 1 1 100%; }
}

/* ---- Trade detail table --------------------------------------------------
   Label left, value right, a line between rows — the layout the previous
   dashboard used for a single record.
   ------------------------------------------------------------------------ */
.detail-card { padding-bottom: 4px; }
.detail-card .card-head { margin-bottom: 4px; }
.detail-card .card-title { display: flex; align-items: center; gap: 8px; }
.detail-card .card-title svg { color: var(--accent-ink); }

.detail-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.detail-table th,
.detail-table td { padding: 11px 0; border-bottom: 1px solid var(--border); }
.detail-table tr:last-child th,
.detail-table tr:last-child td { border-bottom: 0; }
.detail-table th {
  text-align: left; font-weight: 500; color: var(--text-dim);
  width: 45%; text-transform: none; letter-spacing: 0; font-size: 14px;
}
.detail-table td {
  text-align: right; font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.detail-table td.pos { color: var(--pos-ink); font-weight: 600; }
.detail-table td.neg { color: var(--neg-ink); font-weight: 600; }

/* ---- Ticket conversation ------------------------------------------------
   Customer on the left, support on the right, so who wrote what is obvious
   before reading a single word.
   ------------------------------------------------------------------------ */
.thread { display: flex; flex-direction: column; gap: 14px; }

.msg { display: flex; align-items: flex-start; gap: 11px; max-width: 88%; }
.msg-support { flex-direction: row-reverse; margin-left: auto; }

.msg-avatar {
  width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--text-dim);
  font-size: 11px; font-weight: 750; letter-spacing: .02em;
}
.msg-support .msg-avatar { background: var(--gradient-brand); color: #04222A; }

.msg-body {
  flex: 1; min-width: 0; padding: 11px 14px;
  background: var(--surface-2); border-radius: var(--r-md);
  border: 1px solid var(--border);
}
.msg-support .msg-body {
  background: var(--accent-soft); border-color: var(--accent-line);
}

.msg-meta {
  display: flex; align-items: center; gap: 9px; margin-bottom: 5px;
}
.msg-who { font-size: 13px; font-weight: 620; }
.msg-support .msg-who { color: var(--accent-ink); }
.msg-time { font-size: 11.5px; color: var(--text-mute); }
/* Read receipt on support replies, admin panel only. Quiet by design: it is a
   status you glance at, not something that should compete with the message.
   "Unseen" is the louder of the two because that is the one worth acting on. */
.msg-seen {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 11px; color: var(--pos-ink); white-space: nowrap;
}
.msg-seen svg { width: 11px; height: 11px; }
.msg-unseen { color: var(--text-mute); font-style: italic; }
.msg-del { margin-left: auto; width: 24px; height: 24px; }
.msg-del svg { width: 13px; height: 13px; }
.msg-text { font-size: 14px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }

@media (max-width: 680px) {
  .msg { max-width: 100%; }
}

/* ── Feedback mini-survey ─────────────────────────────────────────────────
   Sits above the hero on the dashboard. The dismiss button is absolute, so
   the card gets right padding to keep the title clear of it. */
.fb { position: relative; padding-right: 52px; }
.fb-title { font-size: 16px; font-weight: 650; letter-spacing: -.01em; }
.fb-sub   { margin-top: 3px; font-size: 13px; color: var(--text-dim); }
.fb-x {
  position: absolute; top: 12px; right: 12px;
  display: grid; place-items: center; width: 30px; height: 30px;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  color: var(--text-mute); transition: background .15s var(--ease), color .15s var(--ease);
}
.fb-x:hover { background: var(--surface-2); color: var(--text); }
.fb-x svg { width: 15px; height: 15px; }

.fb-stars { display: flex; gap: 4px; margin-top: 14px; }
.fb-star {
  display: grid; place-items: center; width: 38px; height: 38px;
  border: 0; border-radius: 9px; background: none; cursor: pointer;
  color: var(--text-mute); transition: color .12s var(--ease), transform .12s var(--ease);
}
.fb-star svg { width: 24px; height: 24px; }
/* One shape for both states — lit stars just fill in. */
.fb-star.on { color: var(--warn); }
.fb-star.on svg { fill: currentColor; }
.fb-star:hover { transform: translateY(-1px); }

.fb-more { margin-top: 16px; }
.fb-label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 550; }
.fb-more .input { width: 100%; resize: vertical; }
.fb-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }

@media (max-width: 520px) {
  .fb-actions { flex-direction: column-reverse; }
  .fb-actions .btn { width: 100%; justify-content: center; }
}

/* ── Managed-hosting trial card ───────────────────────────────────────────
   One row: mark, the pitch, the button. Tinted rather than plain surface so
   it reads as an offer and not as another panel of account data — but it
   stays a .card, so it lines up with everything else on the page. */
.trial-card {
  position: relative;
  display: flex; align-items: center; gap: 16px;
  padding-right: 52px;
  background: linear-gradient(120deg, var(--accent-soft), transparent 70%), var(--surface);
  border-color: color-mix(in srgb, var(--accent) 34%, var(--border));
}
/* No dismiss button on Plan & billing, so the room reserved for it is not
   wanted either — otherwise the row sits visibly off-centre. */
.trial-card:not(:has(> .fb-x)) { padding-right: 20px; }

.trial-card-mark {
  flex: none;
  display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: 11px; background: var(--accent-soft); color: var(--accent-ink);
}
.trial-card-mark svg { width: 21px; height: 21px; }

.trial-card-body { flex: 1 1 auto; min-width: 0; }
.trial-card-title { font-size: 16px; font-weight: 650; letter-spacing: -.01em; }
.trial-card-sub   { margin-top: 3px; font-size: 13px; line-height: 1.55; color: var(--text-dim); }

.trial-card-cta { flex: none; }

@media (max-width: 640px) {
  .trial-card { flex-wrap: wrap; }
  .trial-card-body { flex-basis: 100%; }
  .trial-card-cta { width: 100%; justify-content: center; }
}

/* The countdown while a trial runs. Same row as .trial-card so the two never
   look like different features, but neutral rather than inviting — it is a
   status, not an offer. In the last three days it takes the warning tint,
   which is the only moment the page should raise its voice. */
.trial-status {
    display: flex; align-items: center; gap: 16px;
    background: var(--surface);
    border-color: color-mix(in srgb, var(--accent) 24%, var(--border));
}

.trial-status .trial-card-mark { background: var(--accent-soft); color: var(--accent-ink); }

.trial-status.is-soon {
    background: linear-gradient(120deg, var(--warn-soft), transparent 70%), var(--surface);
    border-color: color-mix(in srgb, var(--warn) 40%, var(--border));
}

.trial-status.is-soon .trial-card-mark { background: var(--warn-soft); color: var(--warn-ink); }
.trial-status.is-soon .trial-card-title { color: var(--warn-ink); }

@media (max-width: 640px) {
    .trial-status { flex-wrap: wrap; }
    .trial-status .trial-card-body { flex-basis: 100%; }
    .trial-status .trial-card-cta { width: 100%; justify-content: center; }
}

/* ── Section and dialog icons ─────────────────────────────────────────────
   A tinted square left of a heading. Purely orientation: it repeats what the
   text already says, so it carries aria-hidden and never becomes the only
   label for anything. */
.head-icon {
  flex: none;
  width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-ink);
}
.head-icon svg { width: 18px; height: 18px; }
/* Tone variants for the few headings that carry meaning beyond "a section". */
.head-icon.pos  { background: var(--pos-soft);  color: var(--pos-ink); }
.head-icon.neg  { background: var(--neg-soft);  color: var(--neg-ink); }
.head-icon.warn { background: var(--warn-soft); color: var(--warn-ink); }

/* The icon sits beside the title block, so the two must be laid out as a row
   while the title and its subtitle keep stacking. */
.head-with-icon { display: flex; align-items: center; gap: 12px; min-width: 0; }
.head-with-icon .card-title,
.head-with-icon .modal-title { min-width: 0; }

.modal-head .head-icon { width: 36px; height: 36px; border-radius: 10px; }
.modal-head .head-icon svg { width: 19px; height: 19px; }

@media (max-width: 520px) {
  .head-icon { width: 30px; height: 30px; border-radius: 8px; }
  .head-icon svg { width: 16px; height: 16px; }
}

/* ── Plan chooser ─────────────────────────────────────────────────────────
   Cards side by side inside the "Change plan" dialog, one column per plan,
   collapsing to a single column on a phone. */
.plan-seg { width: 100%; margin-bottom: 18px; }
.plan-seg button { flex: 1; display: flex; align-items: center; justify-content: center; gap: 7px; }
.plan-seg-hint {
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  padding: 2px 6px; border-radius: 5px;
  background: var(--pos-soft); color: var(--pos-ink);
}

/* Four plans should stand in one row rather than 3 + a lonely one, so this
   dialog gets its own width. Below that the columns fall back to two and then
   to one, and the cards stay readable at every step. */
.modal.plans { max-width: 1000px; }
.plan-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.plan {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 16px; background: var(--surface);
}
.plan.current { border-color: var(--accent); box-shadow: var(--shadow); }

.plan-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 10px; min-height: 22px;
}
.plan-name { font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.plan-price { font-size: 25px; font-weight: 680; letter-spacing: -.02em; }
.plan-per { font-size: 13px; font-weight: 550; color: var(--text-dim); margin-left: 2px; }
.plan-note { font-size: 12.5px; color: var(--text-dim); margin-top: 3px; }
.plan-desc {
  font-size: 12.5px; color: var(--text-dim); margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border);
}

.plan-list { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 7px; }
.plan-list li {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 13px; line-height: 1.4;
}
.plan-list li.off { color: var(--text-mute); }
.plan-mark { flex: none; display: grid; place-items: center; width: 16px; height: 18px; }
.plan-mark svg { width: 13px; height: 13px; }
.plan-list li.on  .plan-mark { color: var(--pos-ink); }
.plan-list li.off .plan-mark { color: var(--text-mute); }

/* Pushes every button onto the same baseline no matter how long the lists are. */
.plan-foot { margin-top: auto; padding-top: 16px; }

@media (max-width: 560px) {
  .plan-grid { grid-template-columns: 1fr; }
  .plan-seg-hint { display: none; }
}

/* Capacity editor above the dedicated-IP list: label, number and button on one
   row, stacking on a narrow screen. */
.dip-cap {
  display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px;
}
.dip-cap-field { display: flex; flex-direction: column; }
.dip-cap-field .label { margin-bottom: 6px; }
.dip-cap-field .input { width: 130px; }
/* Sits on the input's baseline rather than the label's. */
.dip-cap .btn { margin-bottom: 1px; }
@media (max-width: 520px) {
  .dip-cap, .dip-cap-field { align-items: stretch; }
  .dip-cap-field, .dip-cap-field .input, .dip-cap .btn { width: 100%; }
}

/* ── Marks on figure captions ─────────────────────────────────────────────
   A small glyph in front of the uppercase caption of a tile or hero figure.
   Deliberately the caption's own colour and weight: the number below is the
   point of these cards, and a coloured badge up here would outrank it. */
/* Laid out inline rather than with flex: a caption like "Connected accounts"
   wraps on a phone, and a flex row would then park the glyph beside the middle
   of two lines. Inline, it simply travels with the first word. */
.stat-mark, .figure-mark {
  display: inline-block; width: 14px; height: 14px;
  margin-right: 5px; vertical-align: -2px; color: currentColor;
}
.stat-mark svg, .figure-mark svg { width: 14px; height: 14px; stroke-width: 2; display: block; }
/* The figure's tone belongs to the number, not to the caption. */
.tile .stat-value.pos ~ *, .tile .stat-value.neg ~ * { color: inherit; }

/* ── Onboarding wizard ──────────────────────────────────────────────────── */
.onb-text { margin: 0 0 14px; font-size: 14px; line-height: 1.55; color: var(--text-dim); }
.onb-points { display: grid; gap: 9px; }
.onb-points > div {
  display: flex; align-items: center; gap: 9px;
  font-size: 13.5px; font-weight: 550;
}
.onb-points svg { width: 16px; height: 16px; color: var(--accent-ink); flex: none; }
.onb-dots { display: flex; justify-content: center; gap: 6px; margin-top: 18px; }
.onb-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--surface-2); transition: background .15s var(--ease);
}
.onb-dot.on { background: var(--accent); }

/* ── Hosting server cards ─────────────────────────────────────────────────
   One card per VPS: fill state, health, and the full action row. */
/* One recurring add-on: its price, both Stripe ids and a save button. Boxed
   per add-on because saving is per add-on too — the separation on screen has
   to match the separation in what the button actually writes. */
.stripe-addon {
  border: 1px solid var(--border); border-radius: 12px;
  padding: 14px 16px; margin-bottom: 14px;
}
.stripe-addon:last-of-type { margin-bottom: 0; }
.stripe-addon-head { margin-bottom: 10px; font-size: 14px; }

.hsrv-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.hsrv-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* The action row. `flex: 1` used to stretch the buttons across the whole card,
   which pinned the outer two to the card's edges and left them cramped once the
   row grew to eight. Fixed-width buttons, centred, wrapping onto a second line
   on the narrowest cards — a card is only 230px wide at the low end of the
   grid, where eight 30px buttons plus their gaps do not fit on one line. */
.hsrv-actions {
  display: flex; justify-content: center; gap: 2px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border);
}
/* `flex: 0 1 30px` — never wider than 30px, but allowed to shrink so all eight
   stay on one line down to the grid's narrowest card. The previous `flex: 1`
   stretched them across the full width instead, which pinned the outer two to
   the card edges; wrapping them looked worse still, because eight buttons break
   into an unbalanced 6 + 2. min-width keeps the hit target usable while
   shrinking. */
.hsrv-actions .icon-btn { flex: 0 1 30px; width: auto; min-width: 22px; height: 30px; }
.hsrv-actions .icon-btn svg { width: 15px; height: 15px; }

/* Module checkboxes in the support-role editor. */
.modgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 6px 12px; margin-top: 4px;
}
.modchk {
  display: flex; align-items: center; gap: 7px;
  font-size: 13px; cursor: pointer; user-select: none;
}
.modchk input { accent-color: var(--accent); width: 15px; height: 15px; }

/* ── Marketplace ─────────────────────────────────────────────────────────
   The shop window of the product, so it gets the most generous treatment in
   the app: real whitespace, one restrained brand accent per card, and the
   equity spark as the centrepiece — the curve is what a buyer actually
   evaluates, everything else is caption.

   Everything is drawn from tokens so dark mode needs no second set of rules,
   and every value a reader compares sits in ink colours, never in brand
   colours (polarity is carried by the sign plus pos-/neg-ink). */

.mk-card {
  cursor: pointer; display: flex; flex-direction: column;
  padding: 22px; border-radius: var(--r-lg);
  /* A resting shadow lets the card sit ON the page; the border alone drew it
     as an outline, which is the cheaper of the two readings. */
  box-shadow: var(--shadow);
  transition: border-color .18s var(--ease), box-shadow .22s var(--ease),
              transform .22s var(--ease);
}
.mk-card:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-lift);
  transform: translateY(-3px);
}
.mk-card:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .mk-card { transition: none; }
  .mk-card:hover { transform: none; }
}

/* Featured is a hairline of the brand gradient along the top edge — a mark of
   place, not a louder card. Painted on an inset pseudo-element so it follows
   the radius. */
.mk-card { position: relative; overflow: hidden; }
.mk-card.mk-featured::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--gradient-brand-h);
}

/* Head: provider avatar, title, price. */
.mk-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 14px; }
.mk-avatar {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 14px; font-weight: 700; letter-spacing: .02em;
  box-shadow: inset 0 0 0 1px var(--border);
}
.mk-title-wrap { min-width: 0; flex: 1; }
/* Two lines before the ellipsis: signal names are long, and cutting
   "Prop Desk EU Si…" mid-word on a card that has the room reads as cheap. */
.mk-title {
  font-size: 15.5px; font-weight: 640; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mk-provider { font-size: 12.5px; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mk-price {
  flex: 0 0 auto; padding: 4px 10px; border-radius: 999px;
  font-size: 12.5px; font-weight: 640; white-space: nowrap;
}
.mk-price.free { background: var(--pos-soft); color: var(--pos-ink); }
.mk-price.paid { background: transparent; color: var(--text); box-shadow: inset 0 0 0 1px var(--border-strong); }
.mk-price .per { font-weight: 400; color: var(--text-dim); }

/* Two lines of description, then an ellipsis — cards must not ragged the grid
   because one seller writes essays. */
.mk-desc {
  font-size: 13px; line-height: 1.5; color: var(--text-dim);
  margin: 0 0 14px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The spark. A shape, deliberately not a chart: no axes, no grid, no numbers —
   those live one click away in the track record. Ink is accent-ink (the cyan
   that actually reads on the surface), the fill a soft wash of the same hue. */
.mk-spark { height: 62px; margin: 0 -4px 16px; }
.mk-spark svg { width: 100%; height: 100%; display: block; }
.mk-spark-empty {
  height: 56px; margin-bottom: 14px; border-radius: var(--r-sm);
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--text-mute); font-size: 11.5px;
}

/* KPI strip: three figures separated by hairlines. Labels whisper, values
   speak, and only the value that has a direction wears a direction colour. */
/* minmax(0,…), not 1fr: a bare fr track takes an AUTO minimum, so it refuses
   to shrink below its content — which is how "-65.591,12 USD" was running out
   of its cell and over the drawdown figure beside it. min-width on the item
   cannot fix that; only the track definition can. */
.mk-kpis {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-block: 1px solid var(--border);
  padding: 10px 0; margin-bottom: 12px;
}
.mk-kpi { min-width: 0; padding: 0 10px; position: relative; }
/* Inset rules, not full-height borders: a divider that stops short of the
   band's edges is the detail that separates a considered layout from a
   gridded one. */
.mk-kpi + .mk-kpi::before {
  content: ''; position: absolute; left: 0; top: 3px; bottom: 3px;
  width: 1px; background: var(--border);
}
.mk-kpi .k {
  font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 4px;
}
.mk-kpi .v {
  font-size: 16px; font-weight: 640; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The currency rides small and muted so the magnitude keeps the eye. */
.mk-kpi .v .cu { font-size: 10px; font-weight: 620; color: var(--text-mute); margin-left: 3px; }
.mk-kpi .v.pos { color: var(--pos-ink); }
.mk-kpi .v.neg { color: var(--neg-ink); }
.mk-kpi .v.warn { color: var(--warn-ink); }

.mk-meta { font-size: 12px; color: var(--text-mute); margin-bottom: 14px; }
.mk-meta b { color: var(--text-dim); font-weight: 620; }

.mk-actions { display: flex; gap: 9px; margin-top: auto; }
.mk-actions .btn-primary { flex: 1; }

/* Empty states carry the page while the marketplace is young — most visitors
   will meet one of these before they ever meet a card, so they get the same
   care: a soft brand mark, a welcome, and a next step. */
.mk-empty { text-align: center; padding: 56px 24px; }
.mk-empty .mk-empty-ico {
  width: 56px; height: 56px; margin: 0 auto 18px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent-ink);
}
.mk-empty .mk-empty-ico svg { width: 26px; height: 26px; }
.mk-empty-title { font-size: 17px; font-weight: 660; margin-bottom: 6px; }
.mk-empty-text {
  font-size: 13.5px; color: var(--text-dim); line-height: 1.55;
  max-width: 380px; margin: 0 auto 18px;
}

/* ── Track-record modal (.mk-tr) ─────────────────────────────────────────
   The one place a buyer decides, so it reads like a report, not a form: one
   hero figure, quiet companions, the curve, then the fine print. */

/* The header chip wears the brand: the only gradient in the modal, so it
   stays a signature rather than a theme. */
.mk-tr .head-icon {
  background: var(--gradient-brand); color: #fff; border: 0;
}
.mk-tr .modal-title { font-size: 19px; letter-spacing: -.015em; }
.mk-tr .head-icon { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22); }

/* A report needs room a form does not. Desktop only: this rule sits later in
   the file than the mobile sheet-mode override (max-width: none), so left
   unscoped it would win there and un-fullscreen the modal on phones. */
@media (min-width: 681px) {
  .mk-tr.modal { max-width: 860px; }
}

.mk-tr-seg {
  margin-bottom: 18px; padding: 4px; border-radius: 999px; gap: 3px;
  box-shadow: inset 0 0 0 1px var(--border);
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.mk-tr-seg::-webkit-scrollbar { display: none; }
.mk-tr-seg button { flex: 0 0 auto; }
.mk-tr-seg button {
  padding: 6px 15px; border-radius: 999px; font-weight: 560;
  transition: background .16s var(--ease), color .16s var(--ease);
}
@media (prefers-reduced-motion: reduce) { .mk-tr-seg button { transition: none; } }

/* The report scrolls in ONE place: the modal body. The previous attempt put a
   max-height on .mk-tr-trades — the outer wrapper, which has no overflow — so
   it clipped nothing while the inner .table-wrap kept its own 520px scroller,
   and the table spilled straight over the modal footer. Bounding the body
   instead fixes that and keeps Close/Follow reachable on every view; the
   table's sticky header now sticks to the body scroller, so it still holds. */
.mk-tr .modal-body { max-height: min(72vh, 700px); overflow-y: auto; overscroll-behavior: contain; }
.mk-tr .mk-tr-trades .table-wrap { max-height: none; overflow: visible; }
.mk-tr .mk-tr-trades thead th {
  background: var(--bg); font-size: 10px; font-weight: 600;
  letter-spacing: .085em; color: var(--text-dim); padding-bottom: 12px;
}
/* Rows breathe: 14px of leading turns a dense ledger into something a reader
   scans instead of decodes. */
.mk-tr .mk-tr-trades tbody td { padding-top: 9px; padding-bottom: 9px; }
.mk-side {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-dim);
  font-size: 11.5px; font-weight: 620; white-space: nowrap;
}

/* An expandable trade row. The chevron is the affordance; it turns rather
   than swapping glyphs so the row does not reflow on open. */
.mk-trow { cursor: pointer; }
.mk-trow:hover { background: var(--surface-2); }
.mk-trow:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
.mk-trow.open { background: var(--surface-2); }
.mk-trow-c {
  display: inline-block; margin-left: 8px; color: var(--text-mute);
  font-weight: 400; transition: transform .15s var(--ease);
}
.mk-trow.open .mk-trow-c { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .mk-trow-c { transition: none; } }

/* The detail panel spans the full table width and is inset on the left, so
   it reads as belonging to the row above rather than as another row. */
.mk-trdet > td { padding: 0 !important; background: var(--surface-2); }
.mk-td {
  padding: 14px 16px 15px; border-left: 2px solid var(--accent-ink);
}
.mk-td-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 26px;
}
@media (max-width: 680px) { .mk-td-grid { grid-template-columns: minmax(0, 1fr); } }
.mk-td-head {
  font-size: 10px; font-weight: 600; letter-spacing: .085em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 8px;
}
.mk-td-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 14px; padding: 3px 0; font-size: 12.5px;
}
.mk-td-row .k { color: var(--text-dim); white-space: nowrap; }
.mk-td-row .v {
  font-weight: 600; text-align: right; font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.mk-td-row .v.pos { color: var(--pos-ink); }
.mk-td-row .v.neg { color: var(--neg-ink); }
.mk-td-row .v .mut { color: var(--text-mute); font-weight: 500; }
/* Net is the line the other three add up to, so it is ruled off from them. */
.mk-td-row.net {
  margin-top: 5px; padding-top: 7px; border-top: 1px solid var(--border);
  font-size: 13.5px;
}
.mk-td-row.net .k { color: var(--text); font-weight: 620; }
.mk-td-row.net .v { font-weight: 700; }

/* Symbol breakdown: name and figures on one line, the share bar beneath.
   Share is magnitude, so the bar keeps to one hue; the money keeps polarity. */
.mk-syms { display: flex; flex-direction: column; gap: 14px; }
.mk-sym-top {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin-bottom: 5px;
}
.mk-sym-name { font-size: 13.5px; font-weight: 640; }
.mk-sym-figs { font-size: 12.5px; color: var(--text-dim); white-space: nowrap; }
.mk-sym-bar {
  height: 7px; border-radius: 999px; background: var(--surface-2); overflow: hidden;
}
.mk-sym-bar i {
  display: block; height: 100%; min-width: 4px;
  background: var(--accent-ink); border-radius: 999px;
}

/* Hero band: growth speaks, four companions stand beside it. Every column is
   minmax(0,…) — a bare fr track refuses to shrink below its content, which is
   exactly how long currency strings were poking out of the band. */
.mk-tr-hero {
  display: grid; grid-template-columns: minmax(0, 1.35fr) repeat(4, minmax(0, 1fr));
  gap: 0;
  border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 16px 0; margin-bottom: 14px;
}
.mk-tr-hero > * { padding: 0 16px; min-width: 0; position: relative; }
.mk-tr-hero > * + *::before {
  content: ''; position: absolute; left: 0; top: 2px; bottom: 2px;
  width: 1px; background: var(--border);
}
.mk-tr-net .k, .mk-tr .mk-kpi .k {
  font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Proportional figures on purpose: tabular-nums gives every digit the width
   of a 0 and a hero-sized number starts to look loose. clamp() is the
   overflow guard for six-figure accounts. */
.mk-tr-net .hv {
  font-size: clamp(21px, 3.4vw, 30px); font-weight: 700; line-height: 1.12;
  letter-spacing: -.022em; font-variant-numeric: normal; overflow-wrap: anywhere;
}
.mk-tr-net .hv.pos { color: var(--pos-ink); }
.mk-tr-net .hv.neg { color: var(--neg-ink); }
.mk-tr-net .hs { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.mk-tr .mk-kpi { padding-top: 2px; }
.mk-tr .mk-kpi .v {
  font-size: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mk-tr .mk-kpi .v .cu { font-size: 10px; font-weight: 620; color: var(--text-mute); margin-left: 3px; }
.mk-tr .mk-kpi .n { font-size: 11px; color: var(--text-mute); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 680px) {
  .mk-tr-hero { grid-template-columns: 1fr 1fr; gap: 14px 0; }
  .mk-tr-hero > * + *::before { display: none; }
  .mk-tr-net { grid-column: 1 / -1; }
}

/* Price in the modal header. Sized to be read at a glance but kept in text
   ink, not the accent — the primary button below is the call to action; this
   is the fact it depends on, and two competing accents would blunt both. */
.mk-hprice {
  margin-left: auto; text-align: right; flex: 0 0 auto; padding-top: 2px;
  align-self: center;
}
.mk-hprice .a {
  display: block; font-size: 18px; font-weight: 700; letter-spacing: -.015em;
  white-space: nowrap;
}
.mk-hprice.free .a { color: var(--pos-ink); }
.mk-hprice .p {
  display: block; font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim); margin-top: 2px;
}
@media (max-width: 480px) { .mk-hprice .a { font-size: 16px; } }

/* Caption between the two stacked plots. It names the lower one, which is why
   that plot needs no legend of its own. */
/* A signal heading opens the listing it names. Underlining only the title on
   hover keeps the block from lighting up like a button — it is a heading that
   happens to lead somewhere, not a call to action. */
.sig-link { cursor: pointer; min-width: 0; }
.sig-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 3px; border-radius: var(--r-sm); }
.sig-go {
  display: inline-block; margin-left: 6px; color: var(--text-mute);
  font-weight: 400; transition: transform .15s var(--ease);
}
.sig-link:hover .card-title, .sig-link:hover .row-name { text-decoration: underline; }
.sig-link:hover .sig-go { transform: translateX(3px); color: var(--text-dim); }
@media (prefers-reduced-motion: reduce) { .sig-go { transition: none; } }

/* Chart tab strip. Same pill as the report's own tabs, one step quieter —
   it switches a view inside a view and should not compete with the tabs that
   switch the whole report. */
.mk-chart-seg {
  margin-bottom: 14px; padding: 3px; border-radius: 999px; gap: 2px;
  box-shadow: inset 0 0 0 1px var(--border);
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.mk-chart-seg::-webkit-scrollbar { display: none; }
.mk-chart-seg button {
  flex: 0 0 auto; padding: 5px 13px; border-radius: 999px;
  font-size: 12.5px; font-weight: 550;
}
.mk-chart-seg button[hidden] { display: none; }

/* Key for the cash-flow dots. They are drawn by a canvas plugin, so Chart's
   own legend cannot list them — and the edge each one sits on is the primary
   distinction, which is why the key names the edge rather than only the
   colour. */
.mk-flowkey {
  display: flex; flex-wrap: wrap; gap: 16px; margin-top: 8px;
  font-size: 11px; color: var(--text-dim);
}
.mk-flowkey span { display: inline-flex; align-items: center; gap: 6px; }
.mk-flowkey i { width: 7px; height: 7px; border-radius: 50%; display: block; }
.mk-flowkey i.in { background: var(--pos-ink); }
.mk-flowkey i.out { background: var(--neg-ink); }
.mk-flowkey em { font-style: normal; color: var(--text-mute); }

/* Caption above the plot. It names all three series in one line, which is
   why the chart carries no title of its own. */
.mk-uw-head {
  margin: 0 0 10px; font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim);
}
/* The second caption divides the two plots, so it carries the rule. */
.mk-uw-head.sep { margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--border); }

/* Account facts as quiet chips under the hero — the numbers a subscriber
   checks once, not the ones they compare. */
.mk-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.mk-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 11px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); max-width: 100%;
}
.mk-chip .ck { font-size: 10.5px; color: var(--text-mute); white-space: nowrap;
  letter-spacing: .02em; }
.mk-chip .cv { font-size: 12.5px; font-weight: 640; white-space: nowrap; }
.mk-chip .cv.pos { color: var(--pos-ink); }
.mk-chip .cv.neg { color: var(--neg-ink); }


/* Year-by-month growth grid. Percent only, tabular figures so the columns
   align; negatives inked, the year total set bold. Rides inside .table-wrap
   so thirteen columns scroll sideways on narrow screens instead of bursting
   the modal. */
.mk-ygrid { width: 100%; border-collapse: collapse; font-size: 12px; }
.mk-ygrid th, .mk-ygrid td {
  padding: 7px 8px; text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; border-bottom: 1px solid var(--border);
}
.mk-ygrid thead th {
  font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim);
}
.mk-ygrid tbody th { text-align: left; font-weight: 660; }
.mk-ygrid tbody tr:last-child th, .mk-ygrid tbody tr:last-child td { border-bottom: 0; }
.mk-ygrid td.neg { color: var(--neg-ink); }
.mk-ygrid td.mut { color: var(--text-mute); }
.mk-ygrid td.yt { font-weight: 700; border-left: 1px solid var(--border); }

/* Monthly net: a diverging strip around a hairline baseline. Bars are thin,
   rounded only on their free end, and anchored to the line they grow from —
   the exact figure lives in the tooltip, not printed on every bar. */
.mk-monthly { margin-top: 16px; }
.mk-monthly-head {
  font-size: 10px; font-weight: 600; letter-spacing: .085em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 10px;
}
/* One grid, four fixed rows, columns supplied inline by month count. Every
   column's cells land on the SAME row boundaries by construction — the
   previous per-column flex stacks each sized their own rows, so columns whose
   cells measured differently sat at different heights and the zero line
   stopped being a line. gap: 0 is also load-bearing: the zero line is the
   bottom border of the row-1 cells, so any column gap would break it into one
   dash per month. */
.mk-monthly-strip {
  display: grid; grid-auto-flow: column; gap: 0;
  grid-template-rows: 48px 48px auto auto;
}
.mk-monthly-strip .up, .mk-monthly-strip .dn {
  display: flex; justify-content: center; min-width: 0; cursor: default;
}
.mk-monthly-strip .up { align-items: flex-end; border-bottom: 1px solid var(--border-strong); }
.mk-monthly-strip .dn { align-items: flex-start; }
.mk-monthly-strip i {
  display: block; width: 56%; max-width: 52px; min-height: 3px;
}
.mk-monthly-strip .up i { background: var(--pos-ink); border-radius: 5px 5px 0 0; }
.mk-monthly-strip .dn i { background: var(--neg-ink); border-radius: 0 0 5px 5px; }
.mk-monthly-strip .m {
  text-align: center; font-size: 10.5px; color: var(--text-mute); margin-top: 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mk-monthly-strip .mp {
  text-align: center; font-size: 11px; font-weight: 640; margin-top: 1px;
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mk-monthly-strip .mp.neg { color: var(--neg-ink); }

/* The fine print in two columns on a wide modal — a single 700px-long dl
   wastes the width the modal asked for. The dl is a grid of dt/dd cells, so
   two PAIRS per row means four columns, label-value-label-value. */
@media (min-width: 680px) {
  .mk-tr .kv {
    grid-template-columns: minmax(0,auto) minmax(0,1fr) minmax(0,auto) minmax(0,1fr);
    column-gap: 24px;
  }
}

/* Detail modal: the win/loss split. One bar, two fills, a 2px surface gap
   between them so the segments never fuse; the counts are printed beside it
   because green-vs-red alone is exactly the pair colourblind readers lose. */
.mk-split { display: flex; height: 8px; border-radius: 999px; overflow: hidden; gap: 2px; margin: 6px 0 4px; }
.mk-split .w { background: var(--pos-ink); }
.mk-split .l { background: var(--neg-ink); }
.mk-split-legend { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-dim); }

/* ---- WhatsApp support button ---------------------------------------
   Same widget as the marketing site, but the popup body uses the
   dashboard's tokens rather than the site's hard-coded dark values: the
   dashboard also has a light theme, and #0F1829 panels with white text
   would be the one unreadable element on it. WhatsApp's own green and
   the #075E54 header stay — those are what make it recognisable.

   Mounted only when the admin switch is on, so `body.has-wa` is what
   moves the toasts out of the way; without the class nothing shifts. */
/* Below the modal backdrop (100), the sidebar (90) and its scrim (80), above
   the mobile bottom nav (50). At 210 the button floated over everything: on a
   phone it sits at bottom:70px, which is exactly where a dialog's sticky
   button bar lands, so it covered the Create button of the account dialog.
   Sitting behind the backdrop also makes it unclickable while a dialog is
   open, which is the right answer anyway — nothing there is reachable then. */
.wa-widget { position: fixed; bottom: 24px; right: 24px; z-index: 70; font-family: var(--font); }
/* The z-index alone puts it behind the dialog, but the backdrop is only 55 %
   opaque, so a bright green circle would still shimmer through it. #modal-root
   is emptied on close, so its content IS "a dialog is open". Browsers without
   :has() simply keep the z-index behaviour, which already fixes the overlap. */
body:has(#modal-root .modal-backdrop) .wa-widget { opacity: 0; pointer-events: none; }
.wa-fab {
  width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer;
  background: #25D366; color: #fff; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 24px rgba(37, 211, 102, .4), 0 2px 8px rgba(0, 0, 0, .2);
  transition: transform .3s var(--ease), box-shadow .3s var(--ease); position: relative;
}
.wa-fab:hover { transform: scale(1.08); box-shadow: 0 6px 32px rgba(37, 211, 102, .5), 0 4px 12px rgba(0, 0, 0, .25); }
.wa-fab-icon, .wa-fab-close { transition: transform .3s, opacity .3s; }
.wa-fab-close { position: absolute; display: flex; opacity: 0; transform: rotate(-90deg) scale(.5); }
.wa-widget.open .wa-fab-icon { opacity: 0; transform: rotate(90deg) scale(.5); }
.wa-widget.open .wa-fab-close { opacity: 1; transform: rotate(0) scale(1); }
.wa-fab::before {
  content: ''; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid rgba(37, 211, 102, .5); animation: wa-pulse 2s ease-out infinite;
}
.wa-widget.open .wa-fab::before { animation: none; opacity: 0; }
@keyframes wa-pulse { 0% { transform: scale(1); opacity: .6; } 100% { transform: scale(1.4); opacity: 0; } }
/* The ring is decoration that never stops; honour a reduced-motion request. */
@media (prefers-reduced-motion: reduce) { .wa-fab::before { animation: none; } }

.wa-popup {
  position: absolute; bottom: 72px; right: 0; width: 340px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden; box-shadow: var(--shadow-lift);
  transform: scale(.9) translateY(16px); opacity: 0; pointer-events: none;
  transition: transform .3s var(--ease), opacity .3s var(--ease); transform-origin: bottom right;
}
.wa-widget.open .wa-popup { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
.wa-popup-header { background: #075E54; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; }
.wa-popup-header-info { display: flex; align-items: center; gap: 12px; }
.wa-avatar {
  width: 38px; height: 38px; border-radius: 50%; background: rgba(255, 255, 255, .15);
  display: flex; align-items: center; justify-content: center; color: #fff; flex-shrink: 0;
}
.wa-popup-name { font-weight: 600; font-size: 14px; color: #fff; }
.wa-popup-status { font-size: 12px; color: rgba(255, 255, 255, .7); margin-top: 1px; }
.wa-popup-close { background: none; border: none; color: rgba(255, 255, 255, .7); cursor: pointer; display: flex; padding: 4px; transition: color .2s; }
.wa-popup-close:hover { color: #fff; }
.wa-popup-body { padding: 18px; background: var(--bg); }
.wa-bubble {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 0 var(--r-md) var(--r-md) var(--r-md); padding: 12px 15px; max-width: 270px;
}
.wa-bubble p { font-size: 14px; color: var(--text); margin: 0 0 6px; line-height: 1.5; }
.wa-bubble p:last-of-type { margin-bottom: 4px; }
.wa-bubble-time { font-size: 11px; color: var(--text-mute); float: right; margin-top: 2px; }
.wa-popup-footer { padding: 12px 18px 16px; background: var(--bg); }
.wa-send-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%;
  padding: 11px; border-radius: var(--r-sm); border: none; background: #25D366; color: #fff;
  font-weight: 600; font-size: 14px; font-family: inherit; cursor: pointer;
  transition: background .2s, transform .2s, box-shadow .2s; text-decoration: none;
}
.wa-send-btn:hover { background: #20bd5a; transform: translateY(-1px); box-shadow: 0 4px 16px rgba(37, 211, 102, .35); }

/* Toasts stack up from the same corner. Only shifted when the widget is
   actually there, so a disabled button leaves the layout untouched. */
body.has-wa .toasts { bottom: 92px; }

@media (max-width: 680px) {
  /* The mobile tab bar owns the bottom 58px of the screen. */
  .wa-widget { bottom: 70px; right: 16px; }
  .wa-popup { width: calc(100vw - 32px); }
  .wa-fab { width: 50px; height: 50px; }
  body.has-wa .toasts { bottom: 132px; }
}

/* ── Admin analytics ─────────────────────────────────────────────────────
   Bars, a stacked share bar and a sparkline. Thin marks, recessive grid, and
   the 2px surface gap between touching segments that keeps two fills from
   reading as one. Series colours are set inline from a validated palette in
   ui-admin.js — deliberately not tokens here, because they are chart colours
   and must not drift with the UI accent. */
.an-head { display: flex; justify-content: space-between; align-items: flex-start;
  gap: 16px; margin-bottom: 14px; flex-wrap: wrap; }
.an-head .select { width: auto; min-width: 160px; }

.an-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 14px; }
.an-kpis .tile { padding: 14px 16px; }
.an-kpis .cell-dim { font-size: 12px; }
.an-kpis .stat-value { margin-top: 2px; }

.an-bars { display: flex; flex-direction: column; gap: 9px; }
.an-row { display: grid; grid-template-columns: 116px 1fr 96px; align-items: center; gap: 10px; }
.an-label { font-size: 13px; color: var(--text); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.an-track { height: 10px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.an-fill { height: 100%; border-radius: 999px; transition: width .25s var(--ease); }
.an-val { font-size: 12.5px; text-align: right; white-space: nowrap; }

.an-stack { display: flex; height: 30px; border-radius: 8px; overflow: hidden;
  background: var(--surface-2); gap: 2px; }
.an-seg { display: flex; align-items: center; justify-content: center; min-width: 3px;
  transition: flex-grow .25s var(--ease); }
/* Ink on the fill is a text token's job everywhere else, but here it sits ON a
   saturated mark: near-white is the only value that stays legible on all four. */
.an-seg span { font-size: 11.5px; font-weight: 700; color: #FFFFFF; letter-spacing: .01em; }
.an-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 10px; font-size: 12.5px; }
.an-leg { display: inline-flex; align-items: center; gap: 7px; }
.an-leg i { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.an-line { width: 100%; height: 150px; display: block; overflow: visible; }
.an-line circle { cursor: crosshair; }
.an-axis { display: flex; justify-content: space-between; font-size: 11.5px;
  color: var(--text-mute); margin-top: 6px; }

@media (max-width: 780px) {
  .an-kpis { grid-template-columns: repeat(2, 1fr); }
  .an-row { grid-template-columns: 92px 1fr 84px; gap: 8px; }
}

/* ─── "Did you mean …?" for the broker server name ────────────────────────
   MetaTrader resolves the server name case-sensitively, so "Cxmdirect-demo"
   never connects while CXMDirect-Demo sits right there in its list. Three of
   eight provisioning failures on the Wine servers in Sept 2026 were nothing
   but capitalisation. Sits directly under the input, hidden until there is
   something to ask. */
.srv-ask {
  display: none;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--warn-soft);
  color: var(--warn-ink);
  font-size: 12.5px;
  line-height: 1.5;
}
.srv-ask.on { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.srv-ask code {
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(0, 0, 0, .18);
  font-size: 12.5px;
}
.srv-ask .btn { margin-left: auto; }

/* ─── "Wir arbeiten daran" ──────────────────────────────────────────────
   Ein verwaltetes Konto braucht von der Anlage bis zum verbundenen Terminal
   im Mittel rund zwei Minuten, gelegentlich laenger. Bisher stand dort nur
   das Wort "Setting up…" — und ein Wort, das sich nicht bewegt, sieht nach
   zwei Minuten aus wie etwas, das haengt. Der Punkt erbt seine Farbe vom
   Text, damit derselbe Spinner in jedem Chip und jedem Badge passt. */
.spin-xs {
  display: inline-block;
  width: 11px; height: 11px;
  flex: none;
  vertical-align: -1px;
  margin-right: 6px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  opacity: .7;
  animation: spin .8s linear infinite;
}
/* Groesser, wo er allein in einem Hinweis steht statt neben einem Wort. */
.spin-sm { width: 16px; height: 16px; margin-right: 0; vertical-align: 0; }
@media (prefers-reduced-motion: reduce) { .spin-xs { animation: none; } }

/* Der Ladekreis auf der Kontenseite. Er tritt an die Stelle des farbigen
   Punkts links vom Kontonamen — der auffaelligsten Stelle der Karte, und
   waehrend der Einrichtung ist die Karte sonst leer (kein Kontostand, keine
   Equity, kein Broker). Traegt bewusst die Warnfarbe weiter, die der Punkt
   dort vorher hatte: die Aussage bleibt "noch nicht fertig", sie bewegt sich
   nur jetzt. */
.spin-ring {
  display: inline-block;
  width: 13px; height: 13px;
  flex: none;
  border: 2px solid var(--warn);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .spin-ring { animation: none; opacity: .6; } }

/* ─── Pfeile an einer scrollbaren Reiterleiste ───────────────────────────
   Die Leiste scrollt seit jeher waagerecht, aber ohne Scrollbalken
   (scrollbar-width: none) — auf dem Desktop gab es damit keinen Hinweis
   darauf, dass rechts noch etwas steht, und keine Moeglichkeit, ohne
   Wischgeste dorthin zu kommen.

   Die Pfeile liegen UEBER der Leiste, nicht daneben: ein eigener Platz
   wuerde die Reiter schmaler machen, und genau der Platzmangel ist ja das
   Problem. Der Farbverlauf daneben laesst den abgeschnittenen Reiter
   auslaufen, statt ihn hart unter dem Pfeil enden zu lassen. */
.tabs-wrap { position: relative; }
.tabs-arrow {
  position: absolute; top: 0; bottom: 1px;
  width: 30px; padding: 0; border: 0;
  display: none; align-items: center; justify-content: center;
  cursor: pointer; color: var(--text-dim); z-index: 2;
}
.tabs-arrow:hover { color: var(--text); }
.tabs-arrow svg { width: 16px; height: 16px; }
.tabs-arrow.on { display: flex; }
.tabs-arrow.left  { left: 0;  background: linear-gradient(to right, var(--bg) 55%, transparent); }
.tabs-arrow.right { right: 0; background: linear-gradient(to left,  var(--bg) 55%, transparent); }
@media (prefers-reduced-motion: reduce) { .tabs-wrap .tabs { scroll-behavior: auto; } }
.tabs-wrap .tabs { scroll-behavior: smooth; }
