/* VolatilityFarmer design system — custom tokens layered on Pico CSS.
 *
 * Dark-only. Brand fonts: Syne (headings) + DM Sans (body).
 * Pico provides the base typography/forms/buttons. We override
 * CSS variables to give the app the marketing-site brand identity.
 */

/* ─── Tokens (dark-only) ─── */
:root {
  /* Brand fonts */
  --vf-font-heading: 'Syne', sans-serif;
  --vf-font-body: 'DM Sans', sans-serif;

  /* Brand surfaces */
  --vf-bg-base: #060912;
  --vf-bg-surface: #0a0f1e;
  --vf-bg-card: #111827;
  --vf-bg-card-hover: #1a2235;
  --vf-border: #1e2d40;

  /* Brand accent palette */
  --vf-blue: #2563eb;
  --vf-blue-light: #3b82f6;
  --vf-emerald: #10b981;
  --vf-amber: #f59e0b;
  --vf-red: #ef4444;

  /* Brand text */
  --vf-text-primary: #ffffff;
  --vf-text-secondary: #94a3b8;
  --vf-text-muted: #475569;

  /* Primary action color */
  --vf-primary: #2563eb;
  --vf-primary-hover: #3b82f6;
  --vf-primary-muted: rgba(37, 99, 235, 0.15);

  /* Semantic colors */
  --vf-success: #10b981;
  --vf-success-bg: rgba(16, 185, 129, 0.14);
  --vf-warning: #f59e0b;
  --vf-warning-bg: rgba(245, 158, 11, 0.14);
  --vf-danger: #dc2626;
  --vf-danger-bg: rgba(220, 38, 38, 0.14);
  --vf-info: #2563eb;
  --vf-info-bg: rgba(37, 99, 235, 0.14);

  /* Stable body text token (not rebound by Pico inside buttons). */
  --vf-text-body: #ffffff;

  /* Override Pico tokens — dark only */
  --pico-primary: var(--vf-primary);
  --pico-primary-hover: var(--vf-primary-hover);
  --pico-primary-focus: var(--vf-primary-muted);
  --pico-border-radius: 0.5rem;
  --pico-form-element-spacing-vertical: 0.6rem;
  --pico-form-element-spacing-horizontal: 0.85rem;
  --pico-spacing: 1rem;
  --pico-background-color: var(--vf-bg-base);
  --pico-card-background-color: var(--vf-bg-card);
  --pico-card-border-color: var(--vf-border);
  --pico-card-sectioning-background-color: var(--vf-bg-surface);
  --pico-color: var(--vf-text-primary);
  --pico-muted-color: var(--vf-text-secondary);
  --pico-h1-color: var(--vf-text-primary);
  --pico-h2-color: var(--vf-text-primary);
  --pico-h3-color: var(--vf-text-primary);

  /* Typography type scale (pinned). */
  --vf-text-xs:  0.75rem;
  --vf-text-sm:  0.8125rem;
  --vf-text-md:  0.9375rem;
  --vf-text-lg:  1.0625rem;
  --vf-text-xl:  1.1875rem;
  --vf-text-2xl: 1.375rem;
  --vf-text-3xl: 1.625rem;

  /* Breakpoint canon (documentation — CSS vars can't be used in @media). */
  --vf-bp-xs:  480px;
  --vf-bp-sm:  640px;
  --vf-bp-md:  768px;
  --vf-bp-lg: 1024px;
  --vf-bp-xl: 1280px;
}

/* D5a / TYP-002 (2026-05-23): h1-h6 reference the type scale tokens
 * so Pico's defaults can't silently shift them on upgrade. h5/h6
 * stay at body size; weight differentiates them. */
body {
  background: var(--vf-bg-base);
  font-family: var(--vf-font-body);
  color: var(--vf-text-primary);
  padding-top: 56px;
}
h1, h2, h3 { font-family: var(--vf-font-heading); }

h1 { font-size: var(--vf-text-3xl); }
h2 { font-size: var(--vf-text-2xl); }
h3 { font-size: var(--vf-text-xl); }
h4 { font-size: var(--vf-text-lg); }
h5, h6 { font-size: var(--vf-text-md); font-weight: 600; }

/* Mobile foundation (2026-05-27): type scale tweaks at ≤480px.
 * Body text bumps to 16px (1rem) — prevents iOS Safari's "zoom on
 * focus" behavior on inputs (triggered when computed font-size is
 * below 16px). h1 widens to 28px to retain visual hierarchy against
 * the larger body text. Other scale steps stay; the tighten at
 * desktop-density is still right for tablets and up. */
@media (max-width: 480px) {
  :root {
    --vf-text-md:  1rem;       /* 16px — was 15px */
    --vf-text-3xl: 1.75rem;    /* 28px — was 26px */
  }
}

/* Light-mode and [data-theme] blocks removed — dark-only. */

/* ─── A11Y utilities (visually hidden, skip-link) ───
   A11Y-003 (skip-link) + A11Y-015 (vf-sr-only utility for backup-codes
   copy feedback, used in [SENSITIVE] commit later in this phase). */

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

.vf-skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  padding: 0.5rem 1rem;
  background: var(--vf-primary);
  color: #fff;
  border-radius: 0 0 0.4rem 0.4rem;
  font-weight: 600;
  text-decoration: none;
}
.vf-skip-link:focus {
  left: 0;
  outline: 2px solid var(--vf-primary-hover);
  outline-offset: 2px;
}

/* ─── Top navigation header ─── */

.vf-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: rgba(6, 9, 18, 0.95);
  border-bottom: 1px solid var(--vf-border);
  height: 56px;
  padding: 0 1.5rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.vf-header__logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
}
.vf-header__logo:hover .vf-wordmark { opacity: 0.85; }
.vf-logo-mark { flex-shrink: 0; }
.vf-wordmark {
  font-family: var(--vf-font-heading);
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: -0.02em;
  color: var(--vf-text-primary);
}
.vf-wordmark__accent { color: var(--vf-blue); }
.vf-header__nav {
  display: flex;
  gap: 0.25rem;
  flex: 1;
}
.vf-header__nav a {
  padding: 0.4rem 0.85rem;
  border-radius: var(--pico-border-radius);
  color: var(--pico-muted-color);
  text-decoration: none;
  font-size: 0.93rem;
}
.vf-header__nav a:hover,
.vf-header__nav a.vf-nav-active {
  background: var(--vf-primary-muted);
  color: var(--vf-primary);
}
.vf-header__right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.vf-header__user {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
  padding: 0.35rem 0.7rem;
  border-radius: var(--pico-border-radius);
}
.vf-header__user .vf-avatar {
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  background: var(--vf-primary);
  color: white;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  font-weight: 600;
}

/* UI-D (Finding #7): username dropdown — HTML-native <details> +
 * <summary>. The summary IS the .vf-header__user trigger (same look as
 * before, plus a caret); the .vf-user-menu__panel hangs below it on
 * open. Native behavior gives keyboard + screen-reader accessibility
 * for free; vf-app.js layers outside-click + Escape close on top. */
.vf-user-menu {
  position: relative;
}
.vf-user-menu summary {
  list-style: none;          /* hide the default disclosure triangle */
  cursor: pointer;
  user-select: none;
}
.vf-user-menu summary::-webkit-details-marker { display: none; }
.vf-user-menu summary:hover { background: var(--vf-primary-muted); color: var(--vf-primary); }
.vf-user-menu[open] summary { background: var(--vf-primary-muted); color: var(--vf-primary); }
.vf-user-menu__caret {
  font-size: 0.7rem;
  margin-left: 0.1rem;
  opacity: 0.7;
  transition: transform 0.12s ease;
}
.vf-user-menu[open] .vf-user-menu__caret { transform: rotate(180deg); }

.vf-user-menu__panel {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 11rem;
  z-index: 60;
  padding: 0.35rem;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.vf-user-menu__panel a,
.vf-user-menu__signout {
  display: block;
  padding: 0.4rem 0.7rem;
  border-radius: calc(var(--pico-border-radius) - 2px);
  color: var(--pico-color);
  text-decoration: none;
  font-size: 0.9rem;
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  width: 100%;
}
.vf-user-menu__panel a:hover,
.vf-user-menu__signout:hover {
  background: var(--vf-primary-muted);
  color: var(--vf-primary);
}
.vf-user-menu__sep {
  border: 0;
  border-top: 1px solid var(--pico-card-border-color);
  margin: 0.25rem 0;
}
.vf-icon-btn {
  background: transparent;
  border: 1px solid var(--vf-border);
  color: var(--vf-text-secondary);
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border-radius: var(--pico-border-radius);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
}
.vf-icon-btn:hover {
  border-color: var(--vf-primary);
  color: var(--vf-primary);
}
.vf-nav-admin {
  padding: 0.35rem 0.85rem;
  border-left: 2px solid var(--vf-blue);
  color: var(--vf-text-secondary);
  text-decoration: none;
  font-size: 0.9rem;
}
.vf-nav-admin:hover,
.vf-nav-admin.vf-nav-active {
  color: var(--vf-blue-light);
}
.vf-nav-admin-mobile { display: none; }
@media (max-width: 767px) {
  .vf-nav-admin { display: none; }
  .vf-nav-admin-mobile { display: block; }
}

/* ─── Hamburger nav menu (Phase 1 mobile foundation, 2026-05-27) ───
 *
 * <details data-vf-nav-menu> wraps the inline nav. At ≥768px the
 * wrapper is `display: contents` (no layout box) so .vf-header__nav
 * flows as a flex child of .vf-header exactly as before. At <768px
 * the wrapper is also `display: contents`, but the summary becomes
 * visible (the burger) and the nav becomes an absolute-positioned
 * panel anchored to the sticky .vf-header. Toggle is HTML-native
 * (Enter/Space on the summary, click anywhere). Outside-click and
 * Escape close are handled by the shared MENU_SELECTOR dispatch in
 * vf-app.js. */
.vf-nav-menu { display: contents; }
.vf-nav-menu__burger {
  list-style: none;
  cursor: pointer;
  user-select: none;
  width: 44px;
  height: 44px;
  display: none;
  align-items: center;
  justify-content: center;
  border-radius: var(--pico-border-radius);
  color: var(--pico-muted-color);
  font-size: 1.4rem;
  line-height: 1;
}
.vf-nav-menu__burger::-webkit-details-marker { display: none; }
.vf-nav-menu__burger:hover {
  background: var(--vf-primary-muted);
  color: var(--vf-primary);
}
.vf-nav-menu[open] .vf-nav-menu__burger {
  background: var(--vf-primary-muted);
  color: var(--vf-primary);
}

@media (max-width: 767px) {
  .vf-nav-menu__burger { display: flex; }
  /* .vf-header__nav lives inside .vf-nav-menu but is a flex child of
   * .vf-header because of `display: contents` on the parent. Override
   * the inline-row defaults below 768px and turn it into a
   * collapsible panel anchored to .vf-header (which is sticky, and
   * therefore a positioned containing block). */
  .vf-nav-menu .vf-header__nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    margin: 0.25rem 0.75rem 0;
    padding: 0.4rem;
    background: var(--pico-card-background-color);
    border: 1px solid var(--pico-card-border-color);
    border-radius: var(--pico-border-radius);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
    z-index: 60;
  }
  .vf-nav-menu[open] .vf-header__nav { display: flex; }
  /* Specificity (0,2,1) beats the .vf-header__nav a inline-link rule
   * (0,1,1) so block-style links win inside the panel without
   * touching the original rule. */
  .vf-nav-menu .vf-header__nav a {
    display: block;
    padding: 0.75rem 1rem;
    border-radius: var(--pico-border-radius);
    color: var(--pico-color);
    font-size: 0.95rem;
  }
}

/* Hide the body's max-width on pages with the new header so it can
 * span full width. Inner content stays constrained by .vf-container. */
body:has(.vf-header) > main.container {
  max-width: none;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
.vf-container {
  max-width: 64rem;
  margin: 2rem auto;
}
.vf-container--wide { max-width: 80rem; }
.vf-container--narrow { max-width: 32rem; }

/* ─── Status tiles + dashboard band (Phase 2 styling, light touch-up) ─── */
.status-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.85rem;
  margin-bottom: 1.5rem;
}
.status-tile {
  background: var(--vf-bg-card);
  border: 1px solid var(--vf-border);
  border-radius: 10px;
  padding: 0.85rem 1rem;
  text-align: center;
  transition: border-color 0.15s ease;
}
.status-tile:hover { border-color: var(--vf-primary); }
.status-tile .label {
  font-family: var(--vf-font-body);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--vf-text-muted);
}
.status-tile .value {
  font-family: var(--vf-font-heading);
  font-size: 1.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--vf-text-primary);
  margin-top: 0.25rem;
}
.status-tile .sub {
  font-family: var(--vf-font-body);
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--vf-text-muted);
  margin-top: 0.25rem;
}
.status-tile.bot-up    { border-left: 3px solid var(--vf-success); }
.status-tile.bot-down  { border-left: 3px solid var(--vf-danger); }

.pnl-positive { color: var(--vf-emerald); }
.pnl-negative { color: var(--vf-red); }

/* ─── Value-change flash. Triggered by JS on htmx swap when a
   [data-flash] element's data-value moves up or down. Subtle tint
   that fades out in ~0.7s. ─── */
@keyframes vf-flash-up {
  0%   { background-color: rgba(34, 197, 94, 0.22); }
  100% { background-color: transparent; }
}
@keyframes vf-flash-down {
  0%   { background-color: rgba(239, 68, 68, 0.22); }
  100% { background-color: transparent; }
}
.vf-flash-up   { animation: vf-flash-up 0.7s ease-out; border-radius: 3px; }
.vf-flash-down { animation: vf-flash-down 0.7s ease-out; border-radius: 3px; }
@media (prefers-reduced-motion: reduce) {
  .vf-flash-up, .vf-flash-down { animation: none; }
}

/* ─── Custom typeahead. Used by the Add-Pair symbol input on
       /me/bot/config. Replaces <datalist> so the dropdown shows
       below the input and can be styled to match the theme. ─── */
.vf-typeahead { position: relative; }
.vf-typeahead-dropdown {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  max-height: 18rem;
  overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.vf-typeahead-item {
  padding: 0.45rem 0.75rem;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.vf-typeahead-item:hover,
.vf-typeahead-item.is-active {
  background: var(--vf-primary);
  color: var(--pico-primary-inverse, white);
}
.vf-typeahead-empty {
  padding: 0.6rem 0.75rem;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
  font-style: italic;
}

/* ─── Cards ─── */
.vf-card {
  background: var(--vf-bg-card);
  border: 1px solid var(--vf-border);
  border-radius: 12px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1rem;
}
.vf-card h2, .vf-card h3 {
  margin-top: 0;
  font-family: var(--vf-font-heading);
  font-weight: 700;
  font-size: 1.1rem;
}
.vf-card__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
.vf-card__header h2, .vf-card__header h3 { margin-bottom: 0; }
.vf-card__sub {
  color: var(--vf-text-secondary);
  font-size: 0.9rem;
}

/* ─── Billing pricing grid (BILL-DISC) ───
   Responsive card grid for /billing. Cards inherit vf-card visual
   vocabulary (border, radius, background) but use flex-column so the
   Subscribe button stays bottom-aligned even when feature lists differ
   in length across tiers. */
.vf-billing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
}
.vf-billing-card {
  display: flex;
  flex-direction: column;
  background: var(--vf-bg-card);
  border: 1px solid var(--vf-border);
  border-radius: 12px;
  padding: 1.25rem;
  margin-bottom: 0;
}
.vf-billing-card > :last-child { margin-top: auto; }
.vf-billing-price {
  margin: 0.25rem 0 0.75rem 0;
  font-size: var(--vf-text-xl);
}
.vf-billing-features {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem 0;
  font-size: var(--vf-text-md);
}
.vf-billing-features li { padding: 0.25rem 0; }

/* ─── Coin table ─── */
.coin-table th {
  font-family: var(--vf-font-body);
  font-weight: 500;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--vf-text-muted);
  padding: 0.55rem 0.7rem;
}
.coin-table thead tr {
  background: var(--vf-bg-card);
  border-bottom: 1px solid var(--vf-border);
}
.coin-table td {
  font-variant-numeric: tabular-nums;
  padding: 0.45rem 0.7rem;
  font-size: 0.92rem;
}
.coin-table td strong {
  font-family: var(--vf-font-heading);
  font-weight: 700;
  color: var(--vf-text-primary);
}
.coin-table tbody tr:hover {
  background: var(--vf-bg-card-hover);
}

/* Phase 2 mobile foundation (2026-05-27).
 *
 * The coin table has 11 columns and is the only data surface where
 * horizontal scroll is acceptable — operators scan for outliers
 * across pairs, and column count is intrinsic to the task. The
 * pattern: a scroll wrapper (.vf-coin-table-scroll) provides
 * horizontal overflow while the page stays overflow-clean; the
 * first column (pair symbol) is sticky so the operator never loses
 * the row identity mid-scroll.
 *
 * The global `table { display: block; overflow-x: auto }` fallback
 * inside the @max-width 768px sweep is scoped *away* from .coin-table
 * — display:block destroys table semantics (thead/tbody stop being
 * announced as a grid) and the wrapper-based approach keeps the
 * structure intact for assistive tech. */
.vf-coin-table-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  /* Right-edge fade is a visual scroll affordance — signals there's
   * more content off-screen. Skipped under prefers-reduced-motion
   * since the gradient mask reads as motion when scrolling. */
  mask-image: linear-gradient(to right, black 0, black 95%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, black 0, black 95%, transparent 100%);
}
@media (prefers-reduced-motion: reduce) {
  .vf-coin-table-scroll {
    mask-image: none;
    -webkit-mask-image: none;
  }
}
.vf-coin-table-scroll:focus-visible {
  outline: 2px solid var(--vf-primary);
  outline-offset: 2px;
  border-radius: var(--pico-border-radius);
}
.coin-table {
  /* Force the table to keep its natural full width so the wrapper
   * handles overflow instead of cells compressing into illegibility. */
  min-width: 760px;
  /* No-wrap on cells keeps column widths stable while the wrapper
   * scrolls — wrapping in a sticky-first-column layout makes row
   * heights jump and the sticky cell stops aligning with its row. */
  white-space: nowrap;
}
.coin-table th:first-child,
.coin-table td:first-child {
  position: sticky;
  left: 0;
  background: var(--vf-bg-card);
  z-index: 1;
  box-shadow: 1px 0 0 var(--vf-border);
}
.coin-table tbody tr:hover td:first-child {
  background: var(--vf-bg-card-hover);
}

/* ─── Activity panel ─── */
#activity {
  max-height: 360px;
  overflow-y: auto;
  font-family: var(--pico-font-family-monospace);
  font-size: 0.85rem;
  padding: 0.5rem;
  background: var(--pico-card-sectioning-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
}
.activity-line {
  /* UI-B (Finding #5): tightened from 0.15rem v-pad + line-height 1.5
     (Pico default) which gave each line ~1.9em of vertical space. The
     log streams; readability beats white-space here. The 1px
     transparent border-bottom was acting purely as a spacer — replaced
     by a real 1px top-margin hairline on the second-and-later siblings
     so the visual separation lives in the gap, not in border padding. */
  padding: 0.05rem 0.25rem;
  line-height: 1.35;
  white-space: pre-wrap;
  word-break: break-word;
}
.activity-line + .activity-line { margin-top: 1px; }
.activity-line .ts {
  color: var(--pico-muted-color);
  margin-right: 0.5rem;
}
.activity-line.level-INFO  .badge { color: var(--vf-info); }
.activity-line.level-BUY   .badge { color: var(--vf-success); }
.activity-line.level-SELL  .badge { color: var(--vf-warning); }
.activity-line.level-ERROR .badge { color: var(--vf-danger); }
.activity-line .badge {
  font-weight: 600;
  margin-right: 0.5rem;
  display: inline-block;
  min-width: 3.5em;
}

/* ─── Inline alerts (flashes, errors) ─── */
.vf-alert {
  padding: 0.85rem 1.1rem;
  border-radius: var(--pico-border-radius);
  margin: 0 0 1.25rem;
  border-left: 3px solid;
}
.vf-alert--success { background: var(--vf-success-bg); border-left-color: var(--vf-success); }
.vf-alert--warning { background: var(--vf-warning-bg); border-left-color: var(--vf-warning); }
.vf-alert--danger  { background: var(--vf-danger-bg);  border-left-color: var(--vf-danger);  }
.vf-alert--info    { background: var(--vf-info-bg);    border-left-color: var(--vf-info);    }

/* ─── Toast container (AUDIT-UX-003: htmx error feedback) ─── */
#vf-toast-container {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  pointer-events: none;
  max-width: min(28rem, 90vw);
}
#vf-toast-container .vf-toast {
  pointer-events: auto;
  opacity: 1;
  transition: opacity 0.3s ease;
}
#vf-toast-container .vf-toast--dismiss {
  opacity: 0;
}

/* ─── Badges ─── */
.vf-badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.vf-badge--success { background: var(--vf-success-bg); color: var(--vf-success); }
.vf-badge--warning { background: var(--vf-warning-bg); color: var(--vf-warning); }
.vf-badge--danger  { background: var(--vf-danger-bg);  color: var(--vf-danger);  }
.vf-badge--info    { background: rgba(37, 99, 235, 0.15); color: #60a5fa; }
.vf-badge--muted   {
  background: var(--pico-card-sectioning-background-color);
  color: var(--pico-muted-color);
}

/* ─── UI kit (macros in _macros.html) ─── */

.vf-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: var(--pico-border-radius);
  border: 1px solid transparent;
  font-weight: 500;
  font-size: 0.95rem;
  cursor: pointer;
  text-decoration: none;
  line-height: 1.3;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}
.vf-btn:active:not([disabled]) { transform: translateY(1px); }
.vf-btn[disabled] { opacity: 0.55; cursor: not-allowed; }
.vf-btn--primary {
  background: #2563eb;
  border-color: #2563eb;
  color: #ffffff;
}
.vf-btn--primary:hover:not([disabled]) {
  background: #3b82f6;
  border-color: #3b82f6;
}
.vf-btn--secondary {
  background: transparent;
  border: 1.5px solid #334155;
  color: var(--vf-text-body);
}
.vf-btn--secondary:hover:not([disabled]) {
  border-color: var(--vf-blue);
  color: var(--vf-blue-light);
}
.vf-btn--danger {
  background: #dc2626;
  border-color: #dc2626;
  color: #ffffff;
}
.vf-btn--danger:hover:not([disabled]) {
  background: #b91c1c;
  border-color: #b91c1c;
}
.vf-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: #94a3b8;
}
.vf-btn--ghost:hover:not([disabled]) { color: var(--vf-blue-light); }
.vf-btn--full { width: 100%; justify-content: center; }
.vf-btn__icon { font-size: 1em; line-height: 1; }
.vf-btn--link { text-decoration: none; }

/* Inline-form submit buttons that should read as plain text links.
   Used for state-mutating affordances (e.g., "Skip setup" in onboarding)
   that POST + CSRF rather than GET. The visual is a subdued underlined
   link; the semantics are a button. API9-001 (B4, 2026-05-21). */
.vf-link-button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.vf-link-button:hover { color: var(--vf-primary); }

/* Avatars (initials-on-color, 8-hue palette for stability) */
.vf-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--vf-primary);
  color: white;
  border-radius: 50%;
  font-weight: 600;
  font-size: 0.85rem;
  flex-shrink: 0;
}
.vf-avatar--sm { width: 1.4rem; height: 1.4rem; font-size: 0.7rem; }
.vf-avatar--md { width: 2rem;   height: 2rem;   font-size: 0.9rem; }
.vf-avatar--lg { width: 3rem;   height: 3rem;   font-size: 1.2rem; }
.vf-avatar--xl { width: 5rem;   height: 5rem;   font-size: 2rem; }
.vf-avatar--img {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  color: transparent;
  text-indent: -9999px;
}
.vf-avatar--hue-0 { background: #5b8def; }
.vf-avatar--hue-1 { background: #16a34a; }
.vf-avatar--hue-2 { background: #d97706; }
.vf-avatar--hue-3 { background: #db2777; }
.vf-avatar--hue-4 { background: #7c3aed; }
.vf-avatar--hue-5 { background: #0891b2; }
.vf-avatar--hue-6 { background: #65a30d; }
.vf-avatar--hue-7 { background: #be185d; }

/* Page header */
.vf-page-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: 0.5rem 1rem;
  margin-bottom: 1.5rem;
}
.vf-page-header h2 { margin: 0; grid-column: 1; }
.vf-page-header__sub {
  margin: 0;
  color: var(--vf-text-secondary);
  grid-column: 1;
}
.vf-page-header__action { margin: 0; grid-column: 2; grid-row: 1 / span 2; align-self: center; }

/* Empty/error states */
.vf-empty, .vf-error {
  text-align: center;
  padding: 3rem 1.5rem;
  background: var(--pico-card-background-color);
  border: 1px dashed var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
}
.vf-empty__icon, .vf-error__icon {
  font-size: 2.5rem;
  color: var(--pico-muted-color);
  margin-bottom: 0.5rem;
}
.vf-error__icon { color: var(--vf-warning); }
.vf-empty__title, .vf-error__title {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
}
.vf-empty__body, .vf-error__body {
  color: var(--pico-muted-color);
  margin: 0 0 1rem;
  max-width: 28rem;
  margin-left: auto;
  margin-right: auto;
}

/* Skeleton loader (respects prefers-reduced-motion) */
.vf-skeleton { display: flex; flex-direction: column; gap: 0.5rem; }
.vf-skeleton__line {
  height: 0.85rem;
  border-radius: 4px;
  background: linear-gradient(90deg,
    var(--pico-card-sectioning-background-color) 0%,
    var(--pico-card-border-color) 50%,
    var(--pico-card-sectioning-background-color) 100%);
  background-size: 200% 100%;
  animation: vf-skeleton-shimmer 1.6s linear infinite;
}
.vf-skeleton__line:nth-child(odd)  { width: 95%; }
.vf-skeleton__line:nth-child(even) { width: 80%; }
@keyframes vf-skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .vf-skeleton__line { animation: none; }
}

/* Step indicator (multi-step forms) */
.vf-steps {
  display: flex;
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
  gap: 0;
}
.vf-steps__item {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 2px solid var(--pico-card-border-color);
  color: var(--pico-muted-color);
}
.vf-steps__item--active {
  border-bottom-color: var(--vf-primary);
  color: var(--vf-primary);
  font-weight: 600;
}
.vf-steps__item--done {
  border-bottom-color: var(--vf-success);
  color: var(--vf-success);
}
.vf-steps__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--pico-card-sectioning-background-color);
  color: var(--pico-muted-color);
  font-size: 0.8rem;
  font-weight: 600;
  border: 1px solid var(--pico-card-border-color);
}
.vf-steps__item--active .vf-steps__num {
  background: var(--vf-primary);
  color: white;
  border-color: var(--vf-primary);
}
.vf-steps__item--done .vf-steps__num {
  background: var(--vf-success);
  color: white;
  border-color: var(--vf-success);
}
.vf-steps__label { font-size: 0.85rem; }
@media (max-width: 640px) {
  .vf-steps__label { display: none; }
}

/* Trend arrows on KPI tiles */
.vf-trend--up   { color: var(--vf-success); }
.vf-trend--down { color: var(--vf-danger); }
.vf-trend--flat { color: var(--pico-muted-color); }

/* Mobile touch targets: ensure interactive elements are ≥44×44 px.
 *
 * Scope is broadened in Phase 1 (2026-05-27) from the original two
 * selectors (.vf-btn, .vf-icon-btn) to every interactive surface a
 * touch user reaches: nav links, in-page section nav, tabs, help
 * triggers, account-menu items, pagination, and form controls.
 *
 * Gated on `pointer: coarse` (not viewport width) so the rule applies
 * to tablets-in-portrait and touchscreens at any size, but doesn't
 * inflate desktop UI. WCAG 2.5.8 (target size minimum) recommends
 * 24×24; Apple HIG and Google Material both recommend 44×44, which
 * is the bar this site adopts. */
@media (pointer: coarse) {
  .vf-btn,
  .vf-icon-btn {
    min-height: 44px;
    min-width: 44px;
  }
  .vf-header__nav a,
  .vf-section-nav a,
  .vf-tab,
  .vf-user-menu__panel a,
  .vf-user-menu__signout,
  .vf-pagination a,
  .vf-pagination button {
    min-height: 44px;
    display: flex;
    align-items: center;
  }
  .vf-help__trigger {
    min-height: 44px;
    min-width: 44px;
  }
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="number"],
  input[type="tel"],
  input[type="url"],
  select,
  textarea {
    min-height: 44px;
  }
}

/* Reduced motion — disable our few transitions */
@media (prefers-reduced-motion: reduce) {
  .vf-btn, .status-tile { transition: none; }
}

/* ─── Responsive sweep (360px / 768px) ───
 *
 * Pico handles most of this. Below covers the custom-component spots
 * that need attention: header gap, page-header grid, table overflow,
 * status-band auto-fit cushion, KPI tile font scale.
 */
@media (max-width: 768px) {
  .vf-header {
    padding: 0.6rem 1rem;
    gap: 0.5rem;
  }
  .vf-header__logo {
    font-size: 0.95rem;
  }
  .vf-header__user span:not(.vf-avatar) {
    /* hide username text, keep avatar */
    display: none;
  }
  .vf-page-header {
    grid-template-columns: 1fr;
  }
  .vf-page-header__action {
    grid-column: 1;
    grid-row: auto;
    align-self: start;
  }
  body:has(.vf-header) > main.container {
    padding-left: 1rem;
    padding-right: 1rem;
  }
  .vf-container--narrow,
  .vf-container,
  .vf-container--wide {
    margin: 1rem auto;
  }
  .status-band {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.5rem;
  }
  .status-tile { padding: 0.65rem 0.75rem; }
  .status-tile .value { font-size: 1.2rem; }
  table:not(.coin-table) {
    /* Pico's display:block at mobile breaks layout — instead, allow
     * horizontal scroll on the table container.
     *
     * Phase 2 (2026-05-27): .coin-table is excluded — its scroll
     * wrapper (.vf-coin-table-scroll) preserves table semantics and
     * adds a sticky pair column, which display:block destroys.
     * Other tables (admin, sessions, leaderboard) keep this cheap
     * fallback until a future batch decides which deserve their own
     * treatment. */
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
  .vf-card { padding: 1rem 1.1rem; }
  .vf-filter-bar { gap: 0.35rem; }
  .vf-filter-bar input[type="search"] { min-width: 0; flex: 1; }
}

@media (max-width: 480px) {
  .vf-header__nav a {
    padding: 0.35rem 0.55rem;
    font-size: 0.85rem;
  }
  .vf-page-header h2 { font-size: 1.4rem; }
  .status-band { grid-template-columns: 1fr 1fr; }
  .vf-steps__label { display: none; }
}

@media (max-width: 360px) {
  .vf-header__nav { gap: 0.1rem; }
  .vf-header__nav a {
    padding: 0.3rem 0.4rem;
    font-size: 0.8rem;
  }
}

/* Announcement banner (Phase 6 R5) */
.vf-announcement {
  background: linear-gradient(90deg, var(--vf-primary-muted), transparent);
  border-bottom: 1px solid var(--pico-card-border-color);
  padding: 0.6rem 0;
  font-size: 0.92rem;
}
.vf-announcement__inner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

footer { margin-top: 3rem; color: var(--pico-muted-color); font-size: 0.85rem; padding-bottom: 2rem; }

/* ─── Accessible field help / tooltip (kit.field_help macro) ───
   Pattern: inline "?" trigger + sibling `[role=tooltip]` bubble.
   Visible on :hover, :focus-within (parent), or when the trigger
   carries `aria-expanded="true"` (toggled by JS — see vf-app.js).
   WCAG 1.4.13: bubble stays visible when the user moves the pointer
   onto the bubble itself (hover persists thanks to the wrapper span).
*/
.vf-help {
  position: relative;
  display: inline-block;
  margin-left: 0.25rem;
  vertical-align: baseline;
  line-height: 1;
}
.vf-help__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  margin: 0;
  border: 1px solid var(--pico-muted-border-color, currentColor);
  border-radius: 50%;
  background: transparent;
  color: var(--pico-muted-color);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  cursor: help;
}
.vf-help__trigger:hover,
.vf-help__trigger:focus-visible {
  color: var(--vf-primary);
  border-color: var(--vf-primary);
  outline: none;
}
.vf-help__trigger:focus-visible {
  box-shadow: 0 0 0 2px var(--vf-primary-muted);
}
.vf-help__bubble {
  position: absolute;
  z-index: 60;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  min-width: 14rem;
  max-width: min(22rem, 90vw);
  padding: 0.55rem 0.7rem;
  background: var(--pico-card-background-color, #1a1d24);
  color: var(--pico-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: 6px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);
  font-size: 0.82rem;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 80ms ease-in;
}
.vf-help:hover .vf-help__bubble,
.vf-help:focus-within .vf-help__bubble,
.vf-help__trigger[aria-expanded="true"] + .vf-help__bubble {
  opacity: 1;
  pointer-events: auto;
}
/* WCAG 1.4.13: the bubble must be hoverable too — when the cursor
   moves from trigger onto bubble, keep it open. */
.vf-help__bubble:hover {
  opacity: 1;
  pointer-events: auto;
}

/* ─── Terms-of-service page polish ─── */
.vf-tos h1 { margin-top: 0; }
.vf-tos h2 {
  margin-top: 2rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--pico-card-border-color);
  scroll-margin-top: 1rem;
}
.vf-tos section:first-of-type h2 { border-top: none; padding-top: 0; }
.vf-tos nav details > summary {
  cursor: pointer;
  font-size: 0.92rem;
  padding: 0.4rem 0;
}
.vf-tos nav ol {
  columns: 2;
  column-gap: 2rem;
  margin: 0.6rem 0 1rem;
  padding-left: 1.2rem;
}
.vf-tos nav li { break-inside: avoid; }
.vf-tos .vf-alert {
  margin: 0.8rem 0 1.2rem;
}
@media (max-width: 640px) {
  .vf-tos nav ol { columns: 1; }
}


/* ============================================================
 * R3 evening polish (2026-05-20) — additive visual refinements
 * Goal: dashboard looks more premium without restructuring.
 * No HTMX behavior, route, or JS changes. All values target the
 * existing class names — purely visual.
 * ============================================================ */

/* ─── Status tile refinement ─── */

.status-tile {
  position: relative;
  padding: 1rem 1.15rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  transition: border-color 0.15s ease,
              transform 0.15s ease,
              box-shadow 0.15s ease;
}
.status-tile:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
}

/* UI-A: dropped the prior 1.65rem override here — the value size now
   tracks --vf-text-2xl via the base .status-tile .value rule near the
   top of the file. Keeping the tight line-height / margin tweaks since
   those are layout-only and don't fight the type scale. */
.status-tile .value {
  line-height: 1.15;
  margin-top: 0.35rem;
}

/* Subtle PnL color treatment — tiny background tint on the value text
 * (using inline-block + padding so it reads as a "chip" without being
 * a full badge). Falls back gracefully to plain color. */
.status-tile .value.pnl-positive,
.status-tile .value.pnl-negative {
  display: inline-block;
  padding: 0.05em 0.4em;
  margin-left: -0.4em;
  border-radius: 0.4rem;
}
.status-tile .value.pnl-positive { background: var(--vf-success-bg); }
.status-tile .value.pnl-negative { background: var(--vf-danger-bg); }

/* Bot status tile — turn the left-border-stripe into a more visible
 * live-indicator dot beside the value. The status-tile.bot-up rule
 * provides the colored border-left; we add an animated heartbeat dot
 * adjacent to "running" text via a pseudo-element on the value. */
.status-tile.bot-up .value::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--vf-success);
  margin-right: 0.45rem;
  vertical-align: 0.18em;
  animation: vf-pulse 2.4s ease-in-out infinite;
}
.status-tile.bot-down .value::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--vf-danger);
  margin-right: 0.45rem;
  vertical-align: 0.18em;
}
@keyframes vf-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.85); }
}
@media (prefers-reduced-motion: reduce) {
  .status-tile.bot-up .value::before { animation: none; }
}

/* ─── Card hover elevation ─── */

.vf-card {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  transition: box-shadow 0.18s ease;
}
.vf-card:hover {
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

/* ─── Activity feed polish ─── */

#activity {
  scrollbar-width: thin;
  scrollbar-color: var(--pico-card-border-color) transparent;
}
#activity::-webkit-scrollbar { width: 8px; }
#activity::-webkit-scrollbar-track { background: transparent; }
#activity::-webkit-scrollbar-thumb {
  background: var(--pico-card-border-color);
  border-radius: 4px;
}
#activity::-webkit-scrollbar-thumb:hover {
  background: var(--pico-muted-color);
}

/* Activity level badges → small filled pills (rather than just colored text). */
.activity-line .badge {
  background: var(--pico-card-sectioning-background-color);
  border-radius: 0.3rem;
  padding: 0.05em 0.5em;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: 0.05em;
}
.activity-line.level-INFO  .badge { color: var(--vf-info);    background: var(--vf-info-bg); }
.activity-line.level-BUY   .badge { color: var(--vf-success); background: var(--vf-success-bg); }
.activity-line.level-SELL  .badge { color: var(--vf-warning); background: var(--vf-warning-bg); }
.activity-line.level-ERROR .badge { color: var(--vf-danger);  background: var(--vf-danger-bg); }
.activity-line .ts {
  font-size: 0.78rem;
  opacity: 0.7;
}

/* ─── Leaderboard top-3 podium treatment ─── */

.vf-leaderboard tbody tr:nth-child(1) td:first-child {
  background: linear-gradient(90deg, rgba(253, 224, 71, 0.18), transparent 90%);
}
.vf-leaderboard tbody tr:nth-child(2) td:first-child {
  background: linear-gradient(90deg, rgba(203, 213, 225, 0.20), transparent 90%);
}
.vf-leaderboard tbody tr:nth-child(3) td:first-child {
  background: linear-gradient(90deg, rgba(217, 119, 6, 0.16), transparent 90%);
}
.vf-leaderboard tbody tr td:first-child {
  font-size: 1.1rem;
  text-align: center;
}
.vf-leaderboard tbody tr:nth-child(n+4) td:first-child {
  color: var(--pico-muted-color);
}

/* ─── Better focus rings: brand color, accessible width ─── */

:focus-visible {
  outline: 2px solid var(--vf-primary);
  outline-offset: 2px;
  border-radius: var(--pico-border-radius);
}
.vf-btn:focus-visible,
.status-tile:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline-color: var(--vf-primary);
  box-shadow: 0 0 0 3px var(--vf-primary-muted);
}

/* ─── Primary button: flat brand blue with subtle shadow ─── */

.vf-btn--primary {
  box-shadow: 0 1px 2px rgba(37, 99, 235, 0.25);
}
.vf-btn--primary:hover:not([disabled]) {
  box-shadow: 0 3px 8px rgba(37, 99, 235, 0.30);
}
.vf-btn--primary:active:not([disabled]) {
  box-shadow: 0 1px 1px rgba(37, 99, 235, 0.25);
}

/* ─── Page-header action area: bottom-align so titles + buttons sit nicely ─── */

.vf-page-header h2 {
  font-family: var(--vf-font-heading);
  letter-spacing: -0.015em;
  font-weight: 700;
}
.vf-page-header__sub {
  font-size: 0.92rem;
}

/* ─── Numeric values: tighter optical alignment in tables ─── */

.coin-table td,
.vf-leaderboard td.vf-num {
  font-feature-settings: "tnum" 1, "ss01" 1;
}

/* ─── Empty state polish ─── */

.vf-empty, .vf-error {
  padding: 3.5rem 1.5rem;
  background: linear-gradient(180deg,
    var(--pico-card-background-color),
    var(--pico-card-sectioning-background-color));
}

/* ─── Reduce motion ─── */

@media (prefers-reduced-motion: reduce) {
  .status-tile, .vf-card, .vf-btn--primary {
    transition: none;
  }
  .status-tile:hover, .vf-card:hover {
    transform: none;
  }
}


/* ============================================================
 * D.2 (2026-05-21): P2 frontend polish
 * ============================================================ */

/* D.2.2 reverted (2026-05-21): the .htmx-request opacity fade caused
 * the dashboard to flash every second on the 1s status/snapshot poll
 * cycle. Visual cue is nice-to-have for slow requests but not for
 * fast-refresh polling. The existing .vf-flash-up/.vf-flash-down
 * animations on changed values already signal "data updated"
 * effectively. */

/* Login page narrow-mobile safety: prevent flex children from
 * overflowing on <360px viewports (was an unfixed P2 from the
 * audit). */
.vf-login-stats > div {
  min-width: 0;
}

/* Audit pagination: scroll-to-top after htmx swap. The audit
 * template uses Prev/Next anchors; this makes the visible anchor
 * the table top so users notice the swap occurred. */
.vf-audit-table {
  scroll-margin-top: 1rem;
}


/* ============================================================
 * B.4 (2026-05-21): extract repeated inline-style patterns from
 * me.html (and reusable across other templates) into utility
 * classes. Page renders identical; this is maintenance polish.
 * ============================================================ */

/* Horizontal divider used between sections of a vf-card. */
.vf-section-divider {
  margin: 1.5rem 0;
}

/* Sub-section heading inside a vf-card. Tighter than h3, larger
 * weight than body text. */
.vf-section-h4 {
  margin: 0 0 0.5rem;
  font-size: 1rem;
}

/* D5b / TYP-003 (2026-05-23): long-form prose line-length cap.
 * Pre-fix the .vf-container at max-width 64rem (~64ch body text) is
 * borderline; long-form documents (the TOS, future legal pages) can
 * exceed 75ch on wide viewports — outside WCAG 1.4.8's recommended
 * line-length range. .vf-prose caps to 75ch which is the typography
 * literature's comfortable upper bound. ch unit hugs the document's
 * own font + size so the cap stays accurate across themes. */
.vf-prose {
  max-width: 75ch;
}

/* Vertical spacers — small (~0.75rem) and medium (~1rem). Used for
 * pushing a form button or paragraph below its preceding content
 * without resorting to a wrapping div.
 *
 * D1c (2026-05-23): expanded the scale to support the inline-style
 * sweep (D1d). The original .vf-mt-sm / .vf-mt-md are preserved; the
 * additional tiers cover the patterns most-common in templates:
 *   xs (0.25rem), lg (2rem), xl (4rem), plus margin-bottom + margin-
 *   left + zero. Pattern is "token if available, else nearest existing
 *   tier" — D1d composes these to retire inline style="margin-..." values. */
.vf-mt-0  { margin-top: 0; }
.vf-mt-xs { margin-top: 0.25rem; }
.vf-mt-sm { margin-top: 0.75rem; }
.vf-mt-md { margin-top: 1rem; }
.vf-mt-lg { margin-top: 2rem; }
.vf-mt-xl { margin-top: 4rem; }
.vf-mb-sm { margin-bottom: 0.5rem; }
.vf-ml-sm { margin-left: 0.5rem; }
.vf-m-0   { margin: 0; }

/* Text utilities — D1c. Each color reference is a var(--*) token so a
 * future theme change propagates automatically. */
.vf-text-center  { text-align: center; }
.vf-text-muted   { color: var(--pico-muted-color); }
.vf-text-muted-sm { color: var(--pico-muted-color); font-size: 0.85rem; }
.vf-text-sm      { font-size: 0.85rem; }
.vf-text-xs      { font-size: 0.75rem; }
.vf-text-mono-sm { font-family: monospace; font-size: 0.85rem; }
/* UI-B (Finding #3): reset small-caps / letter-spacing inside a parent
 * that has them. Used inside .status-tile .label to keep the static
 * caption ("uptime") in small-caps while the dynamic value ("1h 54m ·
 * since May 13") renders in normal case. */
.vf-text-normal  { text-transform: none; letter-spacing: normal; }

/* Layout utilities — D1c. */
.vf-flex-center  { display: flex; align-items: center; gap: 0.5rem; }
.vf-grid-tight   { display: grid; gap: 0.75rem; margin-top: 0.75rem; }

/* Page containers — D1c. .vf-page-narrow is the standalone narrow page
 * container used by /register, /reset-password, /me/bot (max-width
 * 32rem with vertical centering via margin auto). Distinct from
 * .vf-container--narrow which is the in-page-flow variant. */
.vf-page-narrow  { max-width: 32rem; margin: 4rem auto; }

/* Site-stats tiles (TYP-007 + D5c wiring) — D1c. The login page +
 * dashboard show 3-up stat tiles ("Users / Bots / Logins 24h"). These
 * utilities externalize the inline typography in those tiles;
 * .vf-stat-value carries the tabular-nums + bold; .vf-stat-label
 * carries the uppercase muted small-caps style; .vf-stat-tile is the
 * background card. */
.vf-stat-tile {
  flex: 1;
  padding: 0.85rem;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  text-align: center;
}
.vf-stat-value {
  /* D5a / TYP-007: now references --vf-text-xl (1.25rem) instead of
   * the prior 1.4rem literal. Closes TYP-007's double-violation by
   * routing through the type-scale token. */
  font-size: var(--vf-text-xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.vf-stat-label {
  font-size: var(--vf-text-xs);
  color: var(--pico-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Button-fg overrides — D1c. Admin danger / success button surfaces
 * (Delete user, etc.) override Pico's neutral button color to draw the
 * eye. Color values reference --vf-* tokens so dark-theme stays in
 * sync. */
.vf-bg-danger-fg  { background-color: var(--vf-danger);  border-color: var(--vf-danger);  color: white; }
.vf-bg-success-fg { background-color: var(--vf-success); border-color: var(--vf-success); color: white; }

/* Opacity utility — D1c. Used for the "disabled-state" visual hint on
 * subscription tier tiles when a downgrade lapses. */
.vf-opacity-50 { opacity: 0.5; }

/* Centered cell in a grid/table header — used in the notification
 * preferences table headers and similar layouts. */
.vf-cell-center {
  text-align: center;
  font-size: 0.85rem;
}

/* Notification preferences 3-column grid (label | email | in-app).
 * Specific to the /me page's notifications section. */
.vf-notif-grid {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 0.5rem;
  align-items: center;
}
.vf-notif-grid > label { margin: 0; padding: 0.25rem 0; }
.vf-notif-grid > div { text-align: center; }

/* Profile header row — avatar + name/handle/joined + action buttons. */
.vf-me-profile {
  display: flex;
  gap: 1rem;
  align-items: center;
  margin-bottom: 1.5rem;
}
.vf-me-profile__body { flex: 1; }
.vf-me-profile__body h4 { margin: 0; }
.vf-me-profile__body p { margin: 0; color: var(--pico-muted-color); }
.vf-me-profile__meta {
  margin: 0.25rem 0 0 !important;
  font-size: 0.85rem;
  color: var(--pico-muted-color);
}
.vf-me-profile__actions {
  margin-top: 0.6rem;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Small button modifier — slightly smaller padding + font than the
 * default kit. Used in avatar upload/remove + session-revoke contexts. */
.vf-btn--sm {
  font-size: 0.85rem;
  padding: 0.35rem 0.85rem;
}

/* Inline form: zero margin so it sits flush in a flex/grid container. */
.vf-form-inline { margin: 0; }

/* Hidden file input — clicked via its <label>. */
.vf-file-input-hidden { display: none; }

/* Connected accounts list (no bullets, padded items separated by border). */
.vf-connected-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.vf-connected-list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--pico-card-border-color);
}
.vf-connected-list__btn--disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Compact data table — for sessions list etc. */
.vf-table-compact { font-size: 0.9rem; }

/* Danger card variant: red border + left stripe + red h3 heading. */
.vf-card--danger { border-color: var(--vf-danger); border-left: 4px solid var(--vf-danger); }
.vf-card--danger > .vf-card__header h3 { color: var(--vf-danger); }
.vf-text-danger { color: var(--vf-danger); }

/* Subsection divider with smaller margin (1rem vs the 1.5rem default
 * vf-section-divider). Used inside dense forms. */
.vf-divider-sm { margin: 1rem 0; }

/* D1d long-tail utility classes — D-track 2026-05-23.
 * Generated mechanically from remaining single-use inline-style
 * patterns; sequential names. Each rule's body is the original
 * inline content. Semantically-rename in a follow-up if desired. */
.vf-s-l00 { background: rgba(80, 140, 220, 0.10); border-left: 4px solid var(--vf-info); padding: 1rem; margin: 1rem 0; border-radius: var(--pico-border-radius); }
.vf-s-l01 { background: var(--pico-card-background-color); border: 1px solid var(--pico-card-border-color); border-radius: var(--pico-border-radius); margin-bottom: 0.4rem; padding: 0; }
.vf-s-l02 { background: var(--vf-card, var(--vf-bg)); border-left: 4px solid var(--vf-accent, #4a90e2); padding: 1rem; }
.vf-s-l03 { background: var(--vf-danger-bg); border-left: 4px solid var(--vf-danger); padding: 0.75rem 1rem; margin: 0 0 1rem; }
.vf-s-l04 { background: var(--vf-success-bg); border-left: 4px solid var(--vf-success); padding: 0.75rem 1rem; margin: 0 0 1rem; }
.vf-s-l05 { display:flex; justify-content:space-between; color: var(--pico-muted-color); font-size: 0.8rem; margin: 0.5rem 0 0; }
.vf-s-l06 { font-size: 1.05em; }
.vf-s-l07 { margin: 0.5rem 0 0; font-size: 0.9em; }
.vf-s-l08 { max-width: 12em; }
.vf-s-l09 { padding: 0.55rem 0.85rem; cursor: pointer; display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.vf-s-l10 { padding: 0.5rem 0.85rem 0.85rem 0.85rem; border-top: 1px solid var(--pico-card-border-color); }
.vf-s-l11 { position: sticky; bottom: 0; background: var(--pico-background-color); padding: 0.75rem 0; margin-top: 1rem; border-top: 1px solid var(--pico-card-border-color); display: flex; gap: 1rem; align-items: center; }

/* ─── D1d ex-inline-<style>-block migrations (2026-05-23) ─────
 * 4 templates carried <style> blocks. Moved here verbatim so the CSP
 * D1e tighten (drop style-src 'unsafe-inline') doesn't break them. */

/* admin/users.html — filter bar + bulk-action bar */
.vf-filter-bar {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.vf-filter-bar label { margin: 0; flex: 0 0 auto; }
.vf-filter-bar input[type="search"] { min-width: 14rem; }
.vf-bulk-bar {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  padding: 0.5rem 0.75rem;
  background: var(--pico-card-sectioning-background-color);
  border-radius: var(--pico-border-radius);
  margin-bottom: 0.5rem;
}
.vf-bulk-bar select { margin: 0; }

/* admin/audit.html — paginator */
.vf-pagination {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 1rem;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
}

/* me.html + admin — section-nav underline tabs */
.vf-section-nav {
  position: sticky;
  top: 56px;
  z-index: 50;
  background: var(--vf-bg-base);
  padding: 0;
  margin: -0.5rem -1rem 1.5rem;
  display: flex;
  gap: 0;
  overflow-x: auto;
  scroll-behavior: smooth;
  border-bottom: 1px solid var(--vf-border);
}
.vf-section-nav a {
  flex-shrink: 0;
  padding: 0.6rem 0.85rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--vf-text-muted);
  text-decoration: none;
  font-size: 0.9rem;
}
.vf-section-nav a:hover {
  color: var(--vf-blue-light);
  border-bottom-color: var(--vf-blue-light);
}
.vf-section-nav a.vf-nav-active {
  color: var(--vf-blue);
  border-bottom-color: var(--vf-blue);
  font-weight: 600;
}
#vf-pw-strength[data-strength="0"]::after { content: "—"; color: var(--pico-muted-color); }
#vf-pw-strength[data-strength="1"]::after { content: "weak"; color: var(--vf-danger); }
#vf-pw-strength[data-strength="2"]::after { content: "fair"; color: var(--vf-warning); }
#vf-pw-strength[data-strength="3"]::after { content: "good"; color: var(--vf-info); }
#vf-pw-strength[data-strength="4"]::after { content: "strong"; color: var(--vf-success); }
#vf-pw-strength { display: inline-block; min-width: 4em; }
#vf-pw-strength::before { content: ""; display: none; }
#vf-pw-strength[data-strength] { font-weight: 600; }

/* UI-D (Finding #6): me/bot.html dl/dt/dd polish. Pico's default dl
 * has dt and dd at the same weight on the same baseline; for the bot
 * status summary that hides the visual hierarchy ("State" sits beside
 * "Running" with equal emphasis). This styles dt as a small-caps muted
 * caption above each value, so the bot page reads as a scannable list
 * of "Label: Value" pairs instead of an undifferentiated block.
 *
 * The dt-above-dd flow comes from default <dl> block layout — we don't
 * need grid here. Each dt gets margin-top so consecutive pairs space
 * cleanly without inflating the height of the first one. */
/* ─── Bot status two-column layout ─── */
.vf-bot-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}
@media (min-width: 768px) {
  .vf-bot-layout { grid-template-columns: 3fr 2fr; }
}
.vf-bot-layout__status,
.vf-bot-layout__actions {
  background: var(--vf-bg-card);
  border: 1px solid var(--vf-border);
  border-radius: 12px;
  padding: 1.5rem;
}
.vf-bot-layout__actions h3 {
  margin-top: 0;
  margin-bottom: 1rem;
}
.vf-bot-layout__actions .vf-btn {
  width: 100%;
  justify-content: center;
}
.vf-bot-layout__actions form { margin: 0; }
.vf-bot-layout__actions > *:not(:first-child) { margin-top: 0.5rem; }

.vf-bot-stats dt {
  font-family: var(--vf-font-body);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--vf-text-muted);
  margin-top: 0.75rem;
}
.vf-bot-stats dt:first-of-type { margin-top: 0; }
.vf-bot-stats dd {
  font-family: var(--vf-font-body);
  font-size: 1rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--vf-text-primary);
  margin: 0.1rem 0 0;
}

/* public/leaderboard.html — tab bar + leaderboard table */
.vf-tab-bar {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--pico-card-border-color);
}
.vf-tab {
  padding: 0.5rem 1rem;
  text-decoration: none;
  color: var(--pico-muted-color);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.vf-tab:hover { color: var(--vf-primary); }
.vf-tab--active {
  color: var(--vf-primary);
  border-bottom-color: var(--vf-primary);
  font-weight: 600;
}
.vf-leaderboard {
  width: 100%;
  font-variant-numeric: tabular-nums;
}
.vf-leaderboard th.vf-num,
.vf-leaderboard td.vf-num { text-align: right; }
.vf-leaderboard__self {
  background: var(--vf-primary-muted) !important;
}
.vf-leaderboard__self td {
  border-color: var(--vf-primary) !important;
}

/* D1d dynamic-Jinja replacement classes ─────────────────────── */

/* _nav.html — header avatar size (was inline 1.7rem×1.7rem). */
.vf-avatar--header-size {
  width: 1.7rem;
  height: 1.7rem;
}

/* admin/dashboard.html — signup chart SVG dimensions. */
.vf-admin-signup-chart {
  width: 100%;
  height: 60px;
  display: block;
}

/* me/config.html — DCA level border indicator + over-cap state. */
.vf-dca-level {
  border-left: 3px solid var(--vf-primary);
  padding-left: 0.75rem;
}
.vf-dca-level--over-cap {
  border-left-color: var(--vf-warning);
  opacity: 0.65;
}
/* UI-D (Finding #11): when DCA is disabled, the level rows + add/
 * remove buttons dim and stop accepting interaction. The `inert`
 * attribute (set by both server-side render and JS toggle) blocks
 * focus + clicks; we add the opacity here for the visual cue. Pairs
 * with the .vf-dca-disabled-hint paragraph above the rows. */
.vf-dca-levels[aria-disabled="true"] {
  opacity: 0.45;
}
.vf-dca-disabled-hint {
  margin: 0.25rem 0 0.5rem;
}

/* me/config.html — Capital calculator table inside DCA section. */
.vf-capital-calc {
  margin-top: 0.75rem;
}
.vf-capital-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--vf-text-sm);
}
.vf-capital-table th,
.vf-capital-table td {
  padding: 0.2rem 0.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.vf-capital-table th:first-child,
.vf-capital-table td:first-child {
  text-align: left;
}
.vf-capital-table thead th {
  font-weight: 600;
  border-bottom: 1px solid var(--pico-card-border-color);
  color: var(--pico-muted-color);
  font-size: 0.8rem;
}
.vf-capital-table tfoot td {
  border-top: 1px solid var(--pico-card-border-color);
  padding-top: 0.35rem;
}
.vf-capital-total--warn {
  color: var(--vf-warning);
}

/* me/config.html — Bulk edit bar for multi-pair value application. */
.vf-bulk-edit-bar {
  position: sticky;
  bottom: 3.5rem;
  z-index: 2;
  background: var(--pico-card-sectioning-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  padding: 0.75rem 1rem;
  margin-top: 0.75rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}
.vf-bulk-edit-bar__header {
  flex: 0 0 100%;
  font-size: 0.9rem;
}
.vf-bulk-edit-bar__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex: 1 1 auto;
}
.vf-bulk-edit-field {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: var(--vf-text-sm);
}
.vf-bulk-edit-field input[type="number"],
.vf-bulk-edit-field select {
  width: 6rem;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: var(--vf-text-sm);
}
.vf-bulk-edit-bar--applied {
  animation: vf-flash-up 0.6s ease-out;
}
.vf-bulk-select {
  margin: 0;
  flex-shrink: 0;
  display: none;
}
.vf-bulk-mode .vf-bulk-select {
  display: inline-block;
}
.vf-bulk-controls {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.5rem;
}
.vf-bulk-dca-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex: 0 0 100%;
  font-size: var(--vf-text-sm);
}
.vf-bulk-dca-row input[type="number"] {
  width: 5rem;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: var(--vf-text-sm);
}
@media (max-width: 480px) {
  .vf-bulk-edit-bar {
    position: static;
    margin-top: 1rem;
  }
}


/* ============================================================
 * Mobile foundation — utility class definitions (2026-05-27)
 *
 * Phase 1 of the mobile-responsive overhaul. Defines utility
 * classes referenced in templates but previously absent from any
 * CSS file (audit identified 50 such classes). Templates render
 * today because surrounding layout absorbs the missing rules, but
 * media-query work on the responsive sweep needs these classes
 * to exist or rules targeting them silently no-op.
 *
 * Grouped by purpose. Each rule's value matches its template
 * callsite intent. Class names follow the existing vf-* convention.
 * ============================================================ */

/* ── Page containers ──
 * Naming: .vf-page-* is the standalone centered-page container
 * (margin-x: auto, capped max-width). .vf-centered-* adds vertical
 * margin to frame the page below the header. They compose:
 *
 *   <article class="vf-page-md vf-centered-3rem">
 *
 * yields a 48rem-wide article framed with 3rem of vertical margin.
 * Existing .vf-page-narrow (32rem) defined at line 1333 — leave alone. */
.vf-page-very-narrow { max-width: 24rem; margin-left: auto; margin-right: auto; }
.vf-page-md-narrow   { max-width: 40rem; margin-left: auto; margin-right: auto; }
.vf-page-md          { max-width: 48rem; margin-left: auto; margin-right: auto; }
.vf-page-md-wide     { max-width: 56rem; margin-left: auto; margin-right: auto; }
.vf-page-wide        { max-width: 64rem; margin-left: auto; margin-right: auto; }
.vf-centered-3rem    { margin-top: 3rem; margin-bottom: 3rem; }
.vf-centered-4rem    { margin-top: 4rem; margin-bottom: 4rem; }
.vf-centered-6rem    { margin-top: 6rem; margin-bottom: 6rem; }

/* ── Display / visibility ── */
.vf-block            { display: block; }
.vf-hidden           { display: none !important; }
.vf-cursor-pointer   { cursor: pointer; }

/* ── Flex layout helpers ── */
.vf-flex-1           { flex: 1; }
.vf-flex-col         { display: flex; flex-direction: column; }
.vf-flex-wrap        { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.vf-flex-wrap-center { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.vf-flex-center-tight { display: flex; align-items: center; gap: 0.25rem; }
.vf-flex-row-md      { display: flex; flex-direction: column; gap: 0.75rem; }
@media (min-width: 768px) {
  .vf-flex-row-md    { flex-direction: row; align-items: center; }
}
.vf-self-end         { align-self: end; }

/* ── Margin / padding ── */
.vf-pad-xs           { padding: 0.25rem 0.55rem; }
.vf-pad-sm           { padding: 0.5rem 0.85rem; }
.vf-py-md            { padding-top: 1rem; padding-bottom: 1rem; }
.vf-mb-xs            { margin-bottom: 0.25rem; }
.vf-mb-md            { margin-bottom: 1rem; }
.vf-my-sm            { margin-top: 0.5rem; margin-bottom: 0.5rem; }
.vf-my-md            { margin-top: 1rem; margin-bottom: 1rem; }

/* ── Width / height ── */
.vf-w-3rem           { width: 3rem; }
.vf-min-w-md         { min-width: 4rem; }
.vf-max-w-narrow     { max-width: 18rem; }
.vf-max-h-tall       { max-height: 24rem; overflow-y: auto; }

/* ── Overflow ──
 * vf-x-scroll and vf-table-scroll are aliases (identical behavior, two
 * names because templates picked different names for the same intent).
 * Both produce a horizontal-scroll container with iOS momentum scroll. */
.vf-x-scroll         { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.vf-table-scroll     { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.vf-table-wrap       { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── Text ── */
.vf-text-md          { font-size: var(--vf-text-md); }
.vf-text-left        { text-align: left; }
.vf-text-warning     { color: var(--vf-warning); }
.vf-text-hero        { font-size: 3rem; line-height: 1; }
.vf-nowrap           { white-space: nowrap; }
.vf-prewrap          { white-space: pre-wrap; word-break: break-word; }
.vf-mono-codeblock {
  font-family: var(--pico-font-family-monospace);
  font-size: 0.85rem;
  background: var(--pico-card-sectioning-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  padding: 0.75rem 1rem;
  white-space: pre-wrap;
  word-break: break-all;
}

/* DEEP-172: minimal word-break utility for narrow viewports. The
   admin user-detail page renders `bot.bot_home_path` inside <code>
   inside <dd>; without break-all, long paths
   (/srv/volatilityfarmer/users/<id> + future tenant scoping) overflow
   the dl/dd grid horizontally on mobile. Applied via class so the
   one-off doesn't grow into another margin/padding/background scope. */
.vf-break-all {
  word-break: break-all;
}

/* ── Trend indicator (companion to existing .vf-trend--up/down/flat) ── */
.vf-trend            { font-variant-numeric: tabular-nums; }

/* ── Callouts (light-tinted boxes with side stripe).
 *  vf-callout-danger-thin is the compact variant (less padding, 3px
 *  border) used inside dense forms like the per-pair remove section. */
.vf-callout-danger {
  background: var(--vf-danger-bg);
  border-left: 4px solid var(--vf-danger);
  border-radius: var(--pico-border-radius);
  padding: 1rem;
  margin: 0 0 1rem;
}
.vf-callout-success {
  background: var(--vf-success-bg);
  border-left: 4px solid var(--vf-success);
  border-radius: var(--pico-border-radius);
  padding: 1rem;
  margin: 0 0 1rem;
}
.vf-callout-danger-thin {
  background: var(--vf-danger-bg);
  border-left: 3px solid var(--vf-danger);
  border-radius: var(--pico-border-radius);
  padding: 0.6rem 0.85rem;
}

/* ── Grid variant ── */
.vf-grid-tight-no-mt { display: grid; gap: 0.75rem; }

/* ── DOM markers (no visual style — used as JS or filter hooks).
 *  Defined explicitly (rather than left undefined) so the audit
 *  test in web/tests/test_vf_utilities_defined.py treats them as
 *  intentional zero-effect classes, not omissions. */
.vf-confirm-typed    { }
.vf-select-one       { }
.vf-pair-card        { }

/* ── TOS / register box ── */
.vf-tos-box {
  background: var(--pico-card-sectioning-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  padding: 1rem;
  max-height: 16rem;
  overflow-y: auto;
}
.vf-blockquote-fallback {
  font-size: 0.9rem;
  line-height: 1.5;
}

/* ── TOTP setup page ── */
.vf-totp-qr-frame {
  display: inline-block;
  padding: 0.5rem;
  background: white;
  border-radius: var(--pico-border-radius);
}
.vf-totp-secret-box {
  display: inline-block;
  padding: 0.4rem 0.7rem;
  background: var(--pico-card-sectioning-background-color);
  border: 1px solid var(--pico-card-border-color);
  border-radius: var(--pico-border-radius);
  font-size: 1.1rem;
  letter-spacing: 0.15em;
  word-break: break-all;
}


/* ============================================================
 * Phase 3a — page-specific mobile responsive (2026-05-27)
 *
 * Per-page tweaks for the most-used non-dashboard pages: /me,
 * /me/bot/config, /leaderboard. The dashboard, /me/bot, billing,
 * and unauthed pages either already work after Phase 1 + 2 or
 * needed only template-level adjustments (see commit body).
 *
 * Convention: scope rules to a parent class or id where possible
 * so a change here can't accidentally bleed into other pages.
 * ============================================================ */

/* ── me/config.html ──
 *
 * Pico's .grid uses `grid-template-columns: repeat(auto-fit,
 * minmax(0, 1fr))` — children NEVER drop to a new row because the
 * minmax floor is 0. The 3-up field rows (Trade size + Bank rate +
 * Top-up reserve, Sell target + Buy dip + Reference adjust) stay
 * 3-up at every viewport, which compresses inputs to ~80px each
 * on phones — unusable. Force single-column at ≤640px.
 *
 * The DCA-level grid uses .grid + .vf-dca-level — kept 2-up at all
 * widths because drop% + size-multiplier read naturally side-by-side
 * even at 320px (both are short numeric inputs). The :not()
 * exclusion preserves that. */
@media (max-width: 640px) {
  .pair-fields-scope .grid:not(.vf-dca-level) {
    grid-template-columns: 1fr;
  }
  /* me.html email form: same Pico .grid issue — New email + Current
   * password share a row at all widths today. Stack at ≤640px. */
  #security .grid {
    grid-template-columns: 1fr;
  }
}

/* Sticky save bar at the foot of /me/bot/config drops out of
 * sticky positioning at ≤480px: phone viewports are short (~600px
 * usable after URL bar), and a 60px sticky band at the bottom
 * occupies 10% of that — intrusive on an already-dense page. At
 * desktop and tablet the sticky behaviour is the right call; only
 * the smallest viewports get the static fallback. */
@media (max-width: 480px) {
  .vf-s-l11 {
    position: static;
    margin-top: 1.5rem;
  }
}

/* ── me.html ──
 *
 * Profile section: .vf-me-profile is a flex row with the xl avatar
 * (5rem ≈ 80px) + display name + actions. At <480px the avatar
 * alone is 17% of viewport width and competes with the name and
 * buttons. Stack vertically and left-align so each element gets
 * the full width it needs. */
@media (max-width: 480px) {
  .vf-me-profile {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ── public/leaderboard.html ──
 *
 * .vf-tab-bar is a flex row of period tabs ('Today', '7d', '30d',
 * 'All time') — at 320px these can pack but with awkward text
 * shrinkage. overflow-x:auto lets them scroll horizontally; the
 * border-bottom underline stays continuous because the parent
 * keeps `border-bottom: 1px solid …` and the scroll just clips
 * horizontally. .vf-tab gets flex-shrink:0 so tabs don't compress;
 * they keep their natural width and the bar scrolls if needed. */
.vf-tab-bar {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.vf-tab {
  flex-shrink: 0;
}


/* ============================================================
 * Phase 3b — admin pages mobile (2026-05-27)
 *
 * Admin templates already inherit the Phase 1 utility classes
 * (.vf-table-scroll, .vf-x-scroll, .vf-max-h-tall, .vf-max-w-narrow)
 * which now correctly render the overflow they always intended.
 * The remaining mobile-breaking surface is inline <code> tags with
 * long unbroken strings (filesystem paths on /admin/kill-switch,
 * error detail strings on the kill-switch result).
 * ============================================================ */

/* ── .vf-code-wrap ──
 *
 * Apply to <code> elements rendering arbitrary long strings that
 * would otherwise force the row (or page) width unbounded — a
 * 60-character BOT_HOME path pushes horizontal page overflow on a
 * 320px viewport. With overflow-wrap:anywhere + word-break:break-all,
 * the string wraps mid-character so the cell stays within its
 * column's width.
 *
 * overflow-wrap:anywhere is the modern spec (CSS Text Module Level 3);
 * word-break:break-all is the older fallback for browsers that ignore
 * it. The combination covers Safari + Chrome + Firefox at the version
 * floor the rest of the app targets. */
.vf-code-wrap {
  overflow-wrap: anywhere;
  word-break: break-all;
}