/* Typography — five admin-switchable pairings (see .pair-* classes below and the
   vv_type_class() helper that stamps the active one on <body>). All faces are
   loaded here in one place (views have no shared <head>); the browser only
   downloads the woff2 of the pairing actually in use, so unused faces cost
   nothing but a few @font-face lines. */
@import url('https://fonts.googleapis.com/css2?family=Chivo:wght@400;600;700&family=IBM+Plex+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&family=Instrument+Serif&family=JetBrains+Mono:wght@400;500;600;700&family=Libre+Franklin:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=Playfair+Display:wght@500;600;700&family=Public+Sans:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&family=Sora:wght@500;600;700&family=Space+Grotesk:wght@500;600;700&family=Work+Sans:wght@400;500;600;700&display=swap');

/* ==========================================================================
   style.css — Vertex global stylesheet
   Used by every view (viewhome, viewregister, viewlogin, viewdashboard).
   Single source of truth for colors, typography, buttons, forms, tables.
   ========================================================================== */

/* ── Design tokens — Vertex Venture palette (logo gold on near-black navy) ── */
/* Single source of truth: edit values here. Typography tokens stay separate. */
:root{
	/* Surfaces */
	--vv-bg:             #05080F;   /* page — near-black navy (darker than cards so they lift) */
	--vv-surface:        #0C1420;   /* cards, panels                */
	--vv-surface-raised: #101A2A;   /* modals, toasts, inner chips  */
	/* Lines — translucent slate */
	--vv-border:         rgba(148,163,184,0.16);  /* hairline edges */
	--vv-border-strong:  rgba(148,163,184,0.30);  /* hover / focus edge */
	/* Accent ramp — GOLD, matched to the logo (#ceac57). Ramp derived from it:
	   dark = gradient base / outlines, light = highlights & emphasised figures. */
	--vv-gold-dark:      #A8863C;   /* deeper gold: gradient base, outlines */
	--vv-gold:           #CEAC57;   /* primary accent, CTAs — the logo gold  */
	--vv-gold-light:     #E6CC8A;   /* highlights, emphasised figures        */
	/* ⚠ Gold is LIGHT — text on a gold fill must be DARK. White on #CEAC57 is
	   ~2.2:1 contrast (unreadable); this near-black brown is ~7.4:1. */
	--vv-on-gold:        #2B1F06;   /* text on solid accent fills */
	/* Text */
	--vv-text:           #F1F5F9;
	--vv-text-muted:     #94A3B8;
	/* For a PROMPT — a placeholder — which must not read as content the reader
	   has entered. ⚠ MEASURED, not eyeballed. Against --vv-surface: this is
	   4.84:1, --vv-text-muted is 7.21:1 and real typed text is 16.87:1. A clear
	   step down from every label on the platform, and still above the 4.5:1 AA
	   floor — the obvious next slate on the ramp (#64748B) came out at 3.88:1,
	   which is a placeholder some people cannot read. */
	--vv-text-faint:     #748498;
	/* Status */
	--vv-positive:       #4ADE80;
	--vv-negative:       #F87171;
	/* Glass — frosted surfaces over the constellation backdrop.
	   The fill is a top-lit gradient, not a flat wash: a single alpha reads as
	   dirty film, whereas a lighter top edge is what makes a pane look like a
	   pane. Blur carries a saturate() so the gold behind stays gold instead of
	   greying out. --vv-glass-solid is the no-backdrop-filter fallback and must
	   stay opaque enough to read against a bright node. */
	--vv-glass:        linear-gradient(160deg,rgba(170,198,242,0.15),rgba(126,158,210,0.075) 58%,rgba(100,130,184,0.055));
	--vv-glass-solid:  #0E1725;
	--vv-glass-blur:   blur(16px) saturate(135%);
	--vv-glass-line:   rgba(206,172,87,0.16);
	--vv-glass-shadow: 0 8px 32px rgba(0,0,0,0.42), inset 0 1px 0 rgba(255,255,255,0.06);
	/* Shape & rhythm */
	--vv-radius-sm:6px; --vv-radius:10px; --vv-radius-lg:16px;
	/* ⚠ THE TAB BAR'S HEIGHT, IN ONE PLACE. It is content-driven (8px padding +
	   a 40px disc + a 4px gap + an 11px label + a 1px border ≈ 74px), so anything
	   that has to sit above it was carrying its own hand-measured copy — the wrap
	   said 90px, the chat composer said 72px, and the composer therefore overlapped
	   the bar it was meant to clear. Both read this now. */
	--vv-tabbar-h:74px;
	/* Height of a single-line composer, shared by the field and the send button so
	   the two cannot drift apart. */
	--vv-compose-h:46px; --vv-compose-min:66px; --vv-composebar-h:118px;
	--vv-gap-sm:8px; --vv-gap:12px; --vv-gap-lg:18px;
	/* Type — default pairing = Clean/Inter (admin can switch; see .pair-* below) */
	--vv-font-display: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--vv-font:         "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--vv-font-mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

	/* Back-compat aliases → mapped onto the palette so every existing rule
	   follows the new tokens without being rewritten one by one. */
	--bg:      var(--vv-bg);
	--card:    var(--vv-surface);
	--line:    var(--vv-border);
	--txt:     var(--vv-text);
	--muted:   var(--vv-text-muted);
	--accent:  var(--vv-gold);
	/* "Positive" highlights are GOLD, not green — one accent across the whole UI.
	   Debits/errors stay red, so money in vs out is still distinguishable. */
	--green:   var(--vv-gold);
	--red:     var(--vv-negative);
	--dangerbg:rgba(248,113,113,0.12);
	--infobg:  rgba(206,172,87,0.10);
}

/* ── Typography pairings — admin-switchable (body gets a .pair-* class) ────── */
/* Each redefines the three role tokens; :root above defaults to protocol so an
   unclassed page still renders. Three are from vertex-venture-palette.html;
   editorial + signal are house additions. */
.pair-inter{
	--vv-font-display:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
.pair-protocol{
	--vv-font-display:"Space Grotesk",ui-sans-serif,system-ui,"Segoe UI",sans-serif;
	--vv-font:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
.pair-institutional{
	--vv-font-display:"Chivo",ui-sans-serif,system-ui,"Segoe UI",sans-serif;
	--vv-font:"Public Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"Roboto Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
.pair-capital{
	--vv-font-display:"Instrument Serif",ui-serif,Georgia,"Times New Roman",serif;
	--vv-font:"Manrope",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
.pair-editorial{
	--vv-font-display:"Playfair Display",ui-serif,Georgia,"Times New Roman",serif;
	--vv-font:"Libre Franklin",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
.pair-signal{
	--vv-font-display:"Sora",ui-sans-serif,system-ui,"Segoe UI",sans-serif;
	--vv-font:"Work Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
	--vv-font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}
/* Clean/Inter: bolder, tighter headings to match the uploaded SaaS look (Inter has
   no separate display face, so lean on weight + negative tracking instead). */
.pair-inter .brand,
.pair-inter .page-title,
.pair-inter .hero h1{font-weight:800;letter-spacing:-.4px}
.pair-inter .panel-title{font-weight:700;letter-spacing:-.1px}

/* ── Reset / base ───────────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box}
*{margin:0;padding:0}
body{
	background:var(--vv-bg);
	color:var(--vv-text);
	font-family:var(--vv-font);
	line-height:1.6;
	min-height:100vh;
	-webkit-font-smoothing:antialiased;
}

/* Page backdrop — near-black navy with a soft radial lift: a wide spotlight from
   above and a fainter one below. Deliberately NOT gold — it uses the same cool
   slate as the borders, so it reads as depth/vignette rather than a coloured
   wash competing with the gold accent.
   ⚠ Alphas are kept low ON PURPOSE: at its brightest the backdrop reaches only
   ~#0C0F17, still darker than the card surface (#0C1420), so panels never look
   sunken against a background that has crept brighter than they are.
   Fixed ::before (mobile-safe, unlike background-attachment:fixed). */
body::before{
	content:"";
	position:fixed;
	inset:0;
	z-index:-1;
	pointer-events:none;
	background-color:var(--vv-bg);
	background-image:
		/* soft cool spotlight, sitting INSIDE the viewport so it isn't hidden
		   behind the opaque top bar (that's why the old one looked absent) */
		radial-gradient(1000px 620px at 50% 18%, rgba(148,163,184,0.09), transparent 60%),
		/* ── Scrim over the constellation ────────────────────────────────
		   The mesh is drawn at full strength in the SVG and dimmed HERE, so its
		   weight is one number to tune rather than a regenerated asset. Raise
		   these alphas to push the constellation back; lower them to bring it
		   forward. It must stay well below the glass panels in contrast — the
		   backdrop is texture, never something competing with the figures. */
		linear-gradient(180deg, rgba(7,12,21,0.58) 0%, rgba(5,9,16,0.66) 55%, rgba(3,5,11,0.74) 100%),
		/* ── The constellation itself ────────────────────────────────────
		   Transparent SVG, so the ramp below supplies the colour and a palette
		   change cannot strand it on the wrong background. `cover` + the SVG's
		   own preserveAspectRatio="slice" means it fills any viewport without
		   distorting the node spacing. */
		url("../img/constellation.svg"),
		/* the light→dark body: lifted navy at the top fading to deeper than the
		   base at the bottom — a real, visible vertical ramp */
		linear-gradient(180deg, #0B111C 0%, #070C15 45%, #03050B 100%);
	background-repeat:no-repeat;
	background-position:center top, center, center, center;
	background-size:auto, auto, cover, auto;
}
/* The mesh is decorative. Anyone who has asked for less motion/detail, and any
   print, gets the plain ramp — nothing here carries meaning. */
@media (prefers-reduced-motion:reduce){
	body::before{background-image:linear-gradient(180deg,#0B111C 0%,#070C15 45%,#03050B 100%)}
}
a{color:var(--vv-gold)}
.muted{color:var(--muted);font-size:14px}
.mt-10{margin-top:10px}
.txt-strong{color:var(--txt);font-weight:600}

/* Page layout modes (set on <body>) */
body.page-center{display:flex;align-items:center;justify-content:center;padding:24px}
body.page-column{display:flex;flex-direction:column}

/* ── Top navigation bar ─────────────────────────────────────────────────── */
.nav{
	display:flex;justify-content:space-between;align-items:center;
	padding:14px 24px;border-bottom:1px solid var(--line);
	position:sticky;top:0;background:var(--bg);z-index:10;
}
/* Brand = the header logo image (assets/img/header-logo.png, 205×40).
   Height-constrained so it scales cleanly; width:auto keeps the aspect ratio. */
.brand{display:inline-flex;align-items:center;text-decoration:none;line-height:0}
.brand-logo{height:30px;width:auto;max-width:60vw;display:block}
.topbar .brand-logo{height:28px}
.auth-card .brand-logo{height:40px}
.brand-auth{display:flex;justify-content:center;margin-bottom:6px}
/* admin: keep the logo and its ADMIN badge together as ONE flex child, or
   .nav's space-between would push the badge into the middle of the bar */
.brand-wrap{display:flex;align-items:center;gap:8px;min-width:0}
.brand-wrap .badge{margin-left:0}
@media (max-width:560px){
	.brand-logo,.topbar .brand-logo{height:26px}
	.auth-card .brand-logo{height:34px}
}
.userbox{display:flex;align-items:center;gap:14px;font-size:14px}
.userbox .uname{color:var(--muted)}

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn{
	display:inline-block;padding:14px 26px;border-radius:14px;
	text-decoration:none;font-weight:700;font-size:15px;line-height:1.2;
	border:1px solid transparent;cursor:pointer;
}
.btn:hover{filter:brightness(1.08)}
.btn-primary{
	background:linear-gradient(180deg,var(--vv-gold),var(--vv-gold-dark));color:var(--vv-on-gold);
	border-color:rgba(230,204,138,0.55);box-shadow:0 8px 22px rgba(206,172,87,0.30);
}
.btn-outline{border-color:var(--vv-border-strong);color:var(--vv-text);background:transparent}
.btn-outline:hover{border-color:var(--vv-gold);color:var(--vv-gold);filter:none}
.btn:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
.btn-block{display:block;width:100%;margin-top:22px}
.btn-logout{
	padding:8px 18px;border-radius:8px;background:transparent;
	border:1px solid var(--red);color:var(--red);
	text-decoration:none;font-size:13px;font-weight:600;
}
.btn-logout:hover{background:var(--red);color:#fff}
.btn-copy{
	padding:10px 18px;border-radius:8px;border:1px solid var(--accent);
	background:transparent;color:var(--accent);cursor:pointer;
	font-size:13px;font-weight:600;
}
.btn-copy:hover{background:var(--accent);color:var(--vv-on-gold)}

/* ── Landing hero (viewhome) ────────────────────────────────────────────── */
.hero{
	flex:0 0 auto;display:flex;flex-direction:column;align-items:center;
	justify-content:center;text-align:center;padding:44px 20px 30px;
}
.hero h1{
	font-family:var(--vv-font-display);font-weight:800;letter-spacing:-.6px;
	font-size:clamp(30px,8vw,46px);line-height:1.12;margin-bottom:16px;
}
.hero p{color:var(--muted);max-width:560px;margin-bottom:26px;line-height:1.7}
.hero p strong{color:var(--vv-gold-light)}
/* .hero-cta owns the button spacing now (gap), so no sibling margin hack.
   margin-top:0 is deliberate: the ≤560px rule below adds margin-top to stacked
   hero buttons, which would stack ON TOP of the flex gap and space them twice.
   Higher specificity than `.hero .btn+.btn`, so it wins regardless of order. */
.hero .hero-cta .btn+.btn{margin-left:0;margin-top:0}

/* ── Landing page (viewhome) ─────────────────────────────────────────────── */
.hero-mark{
	width:150px;height:150px;border-radius:50%;object-fit:cover;
	margin:0 auto 18px;display:block;
	border:1px solid rgba(206,172,87,0.28);
	box-shadow:0 14px 44px rgba(0,0,0,0.45);
}
.eyebrow{
	font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
	color:var(--vv-gold);margin-bottom:12px;
}
.hero-cta{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:6px}
.hero-cta .btn{margin:0}

/* The compensation plan — laid out 3 + n so every row is symmetrical. Fixed
   column counts (not auto-fit) so cards can't reflow into an uneven mix, and
   every row collapses at the SAME breakpoint so the page never shows a 3-across
   row above a 2-across one.

   ⚠ .plan-1 IS NOT "one column" — a lone card in .plan-2 would sit half-width
   and hard left, which reads as a layout fault rather than a single item. It is
   one column of the 3-across grid above it (100% less the two 14px gaps, over
   three) and centred, so the card lands directly under the middle one and
   matches its width. Currently used by the Direct Sales Incentive tile, alone
   on row 2 since Group was shelved. */
.plan{display:grid;gap:14px;margin-bottom:14px;align-items:stretch}
.plan-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.plan-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.plan-1{grid-template-columns:minmax(0,1fr);max-width:calc((100% - 28px) / 3);margin-inline:auto}
@media (max-width:820px){
	.plan-3,.plan-2,.plan-1{grid-template-columns:1fr}
	.plan-1{max-width:none}
}
.plan-card{
	position:relative;display:flex;flex-direction:column;
	background:var(--vv-surface);
	border:1px solid var(--vv-border);border-radius:var(--vv-radius-lg);
	padding:22px 20px 20px;
}
.plan-no{
	position:absolute;top:14px;right:16px;
	width:26px;height:26px;border-radius:50%;
	display:flex;align-items:center;justify-content:center;
	font-size:12px;font-weight:700;
	color:var(--vv-gold);border:1px solid rgba(206,172,87,0.4);
}
/* Fixed figure block height so the three titles in row 1 sit on one line */
.plan-figure{
	font-family:var(--vv-font-mono);font-size:40px;font-weight:700;line-height:1;
	color:var(--vv-gold);margin-bottom:14px;min-height:42px;
	display:flex;align-items:baseline;gap:6px;
}
.plan-figure span{font-family:var(--vv-font);font-size:14px;font-weight:600;color:var(--vv-text-muted)}
.plan-title{font-size:15px;font-weight:700;color:var(--vv-text);margin-bottom:6px;min-height:22px}
.plan-sub{font-size:13px;line-height:1.6;color:var(--vv-text-muted)}
/* Lists fill the remaining height so cards 4 and 5 end level */
.plan-list{list-style:none;margin:12px 0 0;font-size:13px;color:var(--vv-text-muted);flex:1 1 auto}
.plan-list li{
	display:flex;justify-content:space-between;align-items:baseline;gap:12px;
	padding:9px 0;border-bottom:1px solid var(--vv-border);
}
.plan-list li:last-child{border-bottom:none}
.plan-list .tier{font-family:var(--vv-font-mono);font-weight:600;color:var(--vv-gold-light);white-space:nowrap}
.plan-list .reward{text-align:right;line-height:1.45}
.plan-list em{font-style:normal;display:block;opacity:.75;font-size:12px}

/* Trust strip */
.trust{
	display:flex;flex-wrap:wrap;justify-content:center;gap:10px;
	margin:26px 0 6px;
}
.trust-item{
	display:flex;align-items:center;gap:8px;
	font-size:12px;font-weight:600;color:var(--vv-text-muted);
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:999px;padding:8px 14px;
}
.trust-ico{font-size:14px;line-height:1}

/* Closing call to action */
.closing{text-align:center;padding:34px 0 10px}
.closing-title{
	font-family:var(--vv-font-display);font-size:clamp(20px,5.4vw,28px);
	font-weight:800;letter-spacing:-.3px;color:var(--vv-gold);margin-bottom:6px;
}
.closing .btn{margin-top:16px}

/* Auth cards: a one-line pitch so guests know what they're joining */
.auth-pitch{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;margin:10px 0 4px}
.auth-pitch span{
	font-size:11px;font-weight:600;color:var(--vv-gold);
	border:1px solid rgba(206,172,87,0.35);border-radius:999px;padding:4px 10px;
}
.auth-blurb{font-size:12.5px;line-height:1.6;color:var(--vv-text-muted);text-align:center;margin:12px 0 2px}

/* ── Auth card (viewlogin / viewregister) ───────────────────────────────── */
.auth-card{
	background:linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0)),var(--vv-surface);
	border:1px solid var(--vv-border);
	border-radius:var(--vv-radius-lg);width:100%;max-width:420px;padding:32px;
}
/* auth cards centre the logo (the old text-brand sizing no longer applies) */
.auth-card .brand{justify-content:center;margin-bottom:4px}
.auth-card .sub{text-align:center;color:var(--muted);font-size:14px;margin-bottom:22px}
.alt{text-align:center;margin-top:18px;font-size:13px;color:var(--muted)}
.alt a{text-decoration:none}

/* ── Forms ──────────────────────────────────────────────────────────────── */
label{display:block;font-size:13px;color:var(--muted);margin:14px 0 6px}
/* ⚠ `textarea` IS IN THIS LIST, and was missing until 1 Aug 2026. The rule
   named every input TYPE plus select, so a textarea matched nothing and fell
   back to the browser default — a white box with a grey border, in the middle
   of a near-black theme. It stayed invisible for months because the app had no
   textarea anywhere; the messaging composer was the first, and shipped looking
   like a hole punched in the page.
   ⚠ Type-listing inputs has the same trap for any future type (date, search,
   url…). Prefer adding to this list over writing a one-off rule beside the new
   control — a local fix would have left the inbox's compose box still white. */
input[type=text],input[type=password],input[type=tel],input[type=email],input[type=number],
input[type=url],input[type=search],input[type=date],select,textarea{
	width:100%;padding:11px 12px;border-radius:8px;
	border:1px solid var(--line);background:var(--bg);
	color:var(--txt);font-size:15px;
}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent)}
select{appearance:none;cursor:pointer}
/* ⚠ A textarea does NOT inherit the page font — browsers give it a monospace
   default — so it has to be said explicitly or a typed message renders in a
   different face from the one it appears in once sent. */
textarea{font-family:var(--vv-font);line-height:1.5;resize:vertical;min-height:88px;display:block}
/* ⚠ A PLACEHOLDER IS A PROMPT, NOT CONTENT, and it has to look it. This used
   --muted (#94A3B8, slate-400) — the same colour as every hint and label on the
   platform — which on a dark field reads as text somebody has already typed. In
   a two-row composer with a full sentence in it, that is genuinely ambiguous:
   the reader cannot tell an empty box from a filled one.

   One step further down the same slate ramp the palette already uses. Still
   legible — a placeholder nobody can read is its own bug — but unmistakably a
   prompt beside real input at --vv-text.

   ⚠ opacity STAYS 1. Firefox defaults placeholder opacity to 0.54, so that
   declaration is what makes the colour mean the same thing in every browser.
   Dimming by opacity instead of by colour would hand the shade back to the
   browser. */
input::placeholder,textarea::placeholder{color:var(--vv-text-faint);opacity:1}
.hint{font-size:11px;color:var(--muted);margin-top:4px}

/* ── Alerts ─────────────────────────────────────────────────────────────── */
.errors{
	background:var(--dangerbg);border:1px solid var(--red);
	border-radius:8px;padding:12px 14px;margin-bottom:6px;
}
.errors ul{margin-left:18px}
.errors li{color:var(--red);font-size:13px;line-height:1.6}
.notice{
	background:var(--infobg);border:1px solid var(--accent);
	border-radius:8px;padding:10px 14px;margin-bottom:6px;font-size:13px;
}

/* ── Dashboard layout ───────────────────────────────────────────────────── */
.wrap{max-width:1000px;margin:0 auto;padding:24px 16px}
/* Type hierarchy: page title (one per page) › section heading › panel title.
   Page title is the big bright heading; section headings are small uppercase
   muted dividers between blocks; panel titles label individual cards. */
.wrap h2{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);margin:28px 0 12px}
/* ⚠ AFTER `.wrap h2`, AND MORE SPECIFIC THAN IT — both on purpose.
   Every page title in this app is `<h2 class="page-title">` inside `.wrap`, so
   it matched BOTH rules. `.page-title` alone is one class (0,0,1,0) while
   `.wrap h2` is a class plus an element (0,0,1,1), which wins on specificity no
   matter which order they appear in. The result was that every page title on
   every screen rendered as a 13px uppercase muted SECTION heading — visually
   identical to `.section-label` right below it — instead of the 24px display
   title the type hierarchy calls for. Adding `.wrap .page-title` (0,0,2,0)
   settles it, and the bare selector stays for any title outside `.wrap`. */
.page-title,
.wrap .page-title{font-family:var(--vv-font-display);font-size:24px;font-weight:600;letter-spacing:-.2px;line-height:1.25;text-transform:none;color:var(--txt);margin:0 0 16px}
.page-title .badge{vertical-align:middle}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px}
/* ⚠ Two card rows in a row would otherwise TOUCH. .cards carries no bottom
   margin, which never showed because every row was followed by a
   .section-label or <h2>, and those bring their own top margin. The peso
   automation row on the admin dashboard is the first to sit directly under
   another, and it sat flush against it. Matched to the grid gap so the vertical
   rhythm is the same between rows as between cards. */
.cards + .cards{margin-top:16px}
/* Stacked sub-lines in a stat (e.g. "₱499.00 refunded" / "2 members paused")
   ran together. Only the SECOND onward is spaced, so single-line tiles — the
   overwhelming majority — are untouched. */
.stat .muted + .muted{margin-top:3px}
/* A link under the figures is an action, not another data line: give it a
   little more room so it reads as separate. */
.stat .stat-link{margin-top:7px}
.stat{
	background:var(--vv-glass);
	-webkit-backdrop-filter:var(--vv-glass-blur);
	backdrop-filter:var(--vv-glass-blur);
	border:1px solid var(--vv-glass-line);
	border-radius:var(--vv-radius-lg);
	box-shadow:var(--vv-glass-shadow);
	padding:18px;
}
.stat .lbl{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
.stat .val{font-family:var(--vv-font-mono);font-variant-numeric:tabular-nums slashed-zero;font-size:23px;font-weight:600;margin-top:6px}
.stat .val.green{color:var(--green)}
/* Failure counts (peso payouts failed). Only applied when the count is > 0, so
   a red figure on the dashboard always means "something needs looking at". */
.stat .val.red{color:var(--red)}
.stat .val.accent{color:var(--vv-gold-light)}
/* GLASS. Reinstated 28 Jul 2026 together with the constellation backdrop.
   The previous note here said glass had been removed because, over a FLAT page,
   a translucent panel composites to ~#070C14 — 5/255 above the background, i.e.
   invisible. That reasoning was correct and still is: glass only reads as glass
   when there is something behind it to blur. It works now BECAUSE the backdrop
   is textured. If the constellation is ever removed, revert these to solid
   --vv-surface or the cards will disappear again. */
.panel{
	background:var(--vv-glass);
	-webkit-backdrop-filter:var(--vv-glass-blur);
	backdrop-filter:var(--vv-glass-blur);
	border:1px solid var(--vv-glass-line);
	border-radius:var(--vv-radius-lg);
	box-shadow:var(--vv-glass-shadow);
	padding:20px;
}
/* ⚠ .panel carries NO margin of its own — exactly the same latent bug as
   .cards did. It stayed hidden because a panel was always followed by an <h2>
   or a .section-label, and those bring a 28px/22px top margin with them. The
   Peso Gateway filter panel is the first to sit DIRECTLY above another panel,
   and the two rendered flush against each other as one broken box.
   Also covers .livelist, which wraps a panel for live-swapped tables and would
   otherwise reintroduce the same collision. */
.panel + .panel,
.panel + .livelist,
.livelist + .panel{margin-top:16px}
/* The rest of the glass family, and the no-backdrop-filter fallback, are applied
   together at the END of this file — see "Glass surfaces". They have to be late
   so they win over .auth-card / .faq-item / .plan-card, which set their own
   background earlier without raising specificity. */
.reflink{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
/* Input dominates the shared line (grow:999) so the copy button keeps its natural
   width inline; but when the button wraps to its own line it grows to fill the row
   (no unbalanced empty space beside it). */
/* min-width:0 lets the input actually shrink on narrow phones — a hard min-width
   floor here overflows the panel (and the page) on ~360px screens. */
.reflink input{flex:999 1 220px;min-width:0;font-size:13px;padding:10px 12px}
.reflink .btn-copy{flex:1 1 auto}

/* ── Tables (also emitted by model render helpers) ──────────────────────── */
.table-wrap{overflow-x:auto}
.table{width:100%;border-collapse:collapse;font-size:13px;font-variant-numeric:tabular-nums}
.table th{color:var(--muted);text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);white-space:nowrap}
.table td{padding:8px 10px;border-bottom:1px solid var(--line);white-space:nowrap}
.amt-in{font-family:var(--vv-font-mono);font-variant-numeric:tabular-nums;color:var(--green);font-weight:500}
.amt-out{font-family:var(--vv-font-mono);font-variant-numeric:tabular-nums;color:var(--red);font-weight:500}

/* ── Footer ─────────────────────────────────────────────────────────────── */
.footer{
	text-align:center;color:var(--muted);font-size:12px;
	padding:18px;border-top:1px solid var(--line);margin-top:auto;
}
.footer.plain{border-top:none}

/* ── Deposit page (viewdeposit) ─────────────────────────────────────────── */
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:760px){.grid-2{grid-template-columns:1fr}}
.panel-title{font-family:var(--vv-font-display);font-size:16px;font-weight:700;letter-spacing:-.1px;color:var(--txt);margin-bottom:13px}
.qr-panel{text-align:center}
.qr-box{display:inline-block;background:#fff;border-radius:12px;padding:12px;line-height:0;max-width:100%}
/* The QR is emitted at a fixed 240px; let it scale down on narrow phones instead
   of overflowing the panel (240 + 24 padding > content width at ~320px). */
.qr-box img{display:block;max-width:100%;height:auto}
.badge{
	display:inline-block;font-size:11px;font-weight:600;letter-spacing:.5px;
	color:var(--accent);border:1px solid var(--accent);border-radius:20px;
	padding:3px 10px;vertical-align:middle;margin-left:8px;
}
.warnbox{
	margin-top:14px;text-align:left;font-size:12px;line-height:1.6;color:var(--muted);
	background:var(--infobg);border:1px solid var(--line);border-radius:8px;padding:10px 12px;
}
.steps{margin-left:18px;font-size:14px;line-height:1.9;color:var(--muted)}

/* Section heading with a trailing action ("see all"), per the reference layout */
.sec-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:28px 0 12px}
.sec-head h2{margin:0}
.see-all{
	flex:0 0 auto;font-size:12px;font-weight:600;color:var(--vv-text-muted);
	text-decoration:none;border:1px solid var(--vv-border);
	border-radius:999px;padding:6px 14px;transition:color .15s,border-color .15s;
}
.see-all:hover{color:var(--vv-gold);border-color:var(--vv-gold)}

/* ── Consent checkbox (register / deposit) ────────────────────────────────── */
.agree{
	display:flex;align-items:flex-start;gap:10px;
	margin:16px 0 4px;padding:12px 14px;
	background:var(--vv-surface-raised);
	border:1px solid var(--vv-border);border-radius:var(--vv-radius);
	font-size:12.5px;line-height:1.6;color:var(--vv-text-muted);
	cursor:pointer;
}
.agree input[type=checkbox]{
	flex:0 0 auto;width:17px;height:17px;margin-top:1px;
	accent-color:var(--vv-gold);cursor:pointer;
}
.agree span{flex:1 1 auto;min-width:0}
.agree strong{color:var(--vv-text)}
.agree a{text-decoration:underline}
.agree:has(input:focus-visible){border-color:var(--vv-gold)}

/* ── Legal pages (terms / privacy) ────────────────────────────────────────── */
.legal{max-width:820px}
.legal p{font-size:13.5px;line-height:1.75;color:var(--vv-text-muted);margin-bottom:10px}
.legal p strong{color:var(--vv-text)}
.legal .steps{font-size:13.5px;line-height:1.75;margin-bottom:12px}
.legal .steps li{margin-bottom:5px}
.legal .table{margin:6px 0 14px}
.legal .table td{white-space:normal;vertical-align:top;line-height:1.6}
.legal h2{margin-top:26px}

/* ── FAQ accordion — native <details>, no JS ──────────────────────────────────
   Three deliberate typographic levels so the eye can skim:
     SECTION  small gold uppercase + trailing rule  (a divider, not content)
     QUESTION 16px / 600 / near-white               (the scannable layer)
     ANSWER   13.5px / muted / airy line-height     (clearly subordinate)
   Each level differs in size, weight AND colour — size alone was too flat. */

/* Level 1 — section divider */
.faq-sec{
	display:flex;align-items:center;gap:12px;
	margin:34px 0 12px;
	font-family:var(--vv-font-display);
	font-size:11.5px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;
	color:var(--vv-gold);
}
.faq-sec::after{content:"";flex:1 1 auto;height:1px;background:var(--vv-border)}
.faq-sec:first-of-type{margin-top:22px}

.faq{display:flex;flex-direction:column;gap:8px}
.faq-item{
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:var(--vv-radius-lg);overflow:hidden;
	transition:border-color .15s;
}
.faq-item[open]{border-color:var(--vv-border-strong)}

/* Level 2 — the question */
.faq-item summary{
	list-style:none;cursor:pointer;position:relative;
	padding:16px 48px 16px 18px;
	font-size:16px;font-weight:600;letter-spacing:-.1px;
	color:var(--vv-text);line-height:1.4;
	-webkit-tap-highlight-color:transparent;
}
.faq-item summary::-webkit-details-marker{display:none}   /* Safari's default arrow */
.faq-item summary::marker{content:""}                     /* Firefox/Chrome triangle */
.faq-item summary:hover{color:var(--vv-gold)}
.faq-item summary:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:-2px}
/* Chevron drawn in CSS — no icon dependency */
.faq-item summary::after{
	content:"";position:absolute;right:20px;top:50%;
	width:8px;height:8px;margin-top:-6px;
	border-right:2px solid var(--vv-gold);border-bottom:2px solid var(--vv-gold);
	transform:rotate(45deg);transition:transform .18s ease;
}
.faq-item[open] summary{color:var(--vv-gold);padding-bottom:10px}
.faq-item[open] summary::after{transform:rotate(225deg);margin-top:-2px}

/* Level 3 — the answer */
.faq-body{
	padding:0 48px 18px 18px;
	font-size:13.5px;line-height:1.8;color:var(--vv-text-muted);
	max-width:70ch;
}
.faq-body p{margin-bottom:10px}
.faq-body p:last-child{margin-bottom:0}
.faq-body strong{color:var(--vv-text);font-weight:600}
/* Indent with padding-left, not margin-left. The global `*{margin:0;padding:0}`
   strips the UA list indent and `.steps` restores it via margin-left — which any
   `margin` shorthand here would silently wipe out again, dropping the markers
   into .faq-body's padding and up against the card border. */
.faq-body .steps,.faq-body ul,.faq-body ol{
	margin:8px 0 0;padding-left:18px;
	font-size:13.5px;line-height:1.8;
}
.faq-body .steps li,.faq-body ul li,.faq-body ol li{margin-bottom:4px}
.faq-body .steps:last-child,.faq-body ul:last-child,.faq-body ol:last-child{margin-bottom:0}
@media (max-width:560px){
	.faq-item summary{font-size:15px;padding:14px 42px 14px 15px}
	.faq-body{padding:0 18px 15px 15px}
}
@media (prefers-reduced-motion:reduce){
	.faq-item,.faq-item summary::after{transition:none}
}

/* ── Transaction list — card rows instead of a table ──────────────────────── */
.txn-list{display:flex;flex-direction:column;gap:10px}
.txn{
	display:flex;align-items:center;gap:12px;
	background:var(--vv-surface-raised);
	border:1px solid var(--vv-border);
	border-radius:14px;padding:12px 14px;
}
.txn-ico{
	flex:0 0 auto;width:38px;height:38px;border-radius:50%;
	display:flex;align-items:center;justify-content:center;
	background:rgba(255,255,255,0.05);
}
.txn-ico svg{width:18px;height:18px;display:block}
.txn-ico.in{color:var(--vv-gold)}
.txn-ico.out{color:var(--vv-negative)}
/* body flexes and can shrink; min-width:0 lets long labels ellipsis instead of
   pushing the amount off the row */
.txn-body{flex:1 1 auto;min-width:0}
.txn-title{
	font-size:14px;font-weight:600;color:var(--vv-text);
	overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.txn-sub{font-size:12px;color:var(--vv-text-muted);margin-top:2px}
.txn-right{flex:0 0 auto;text-align:right;margin-left:auto}
.txn-amt{
	font-family:var(--vv-font-mono);font-variant-numeric:tabular-nums;
	font-size:14px;font-weight:600;white-space:nowrap;
}
.txn-time{font-size:11px;color:var(--vv-text-muted);margin-top:2px;white-space:nowrap}
/* Pending rows (awaiting confirmation / approval) — set apart from settled
   history with a faint amber wash + accent edge so they don't read as done. */
.txn-pending{background:rgba(234,179,8,0.06);border-color:rgba(234,179,8,0.34)}
.txn-pending .txn-sub{margin-top:4px}
@media (max-width:400px){
	.txn{padding:11px 12px;gap:10px}
	.txn-ico{width:34px;height:34px}
	.txn-title{font-size:13px}
	.txn-amt{font-size:13px}
}

/* ── Real-time investment yields (dashboard hero) ─────────────────────────── */
/* Hero panel: a faint gold wash + warmer edge so it reads as the centrepiece
   without shouting over the rest of the dashboard. */
.yield-panel{
	text-align:center;margin-bottom:16px;
	background:
		radial-gradient(120% 130% at 50% 0%, rgba(206,172,87,0.07), transparent 62%),
		linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0)),
		var(--vv-surface);
	border-color:rgba(206,172,87,0.22);
}
.yield-head{display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.yield-head .lbl{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
/* The hero number — the one thing on the dashboard people watch tick. Bigger than
   every stat value, polished gold with a soft bloom. Fluid so long amounts still
   fit on a phone; tabular figures so digits don't jitter as it counts. */
/* ⚠ Two class selectors deep ON PURPOSE. The element is `.yield-ticker .mono`, and
   `.mono` (font-size:13px) is defined LATER in this file — at equal specificity the
   later rule wins, which silently shrank this to 13px. `.yield-panel .yield-ticker`
   outranks it, and the resets below undo .mono's wrapping too. */
.yield-panel .yield-ticker{
	font-family:var(--vv-font-mono);
	font-size:clamp(34px,9.5vw,54px);
	font-weight:700;line-height:1.08;letter-spacing:-.5px;
	font-variant-numeric:tabular-nums slashed-zero;
	margin:4px 0 8px;
	white-space:nowrap;overflow-wrap:normal;word-break:normal;
	color:var(--vv-gold-light);                    /* fallback if clip unsupported */
	background:linear-gradient(180deg,#F6E3B0 0%,var(--vv-gold) 52%,var(--vv-gold-dark) 100%);
	-webkit-background-clip:text;background-clip:text;
	-webkit-text-fill-color:transparent;
	filter:drop-shadow(0 3px 16px rgba(206,172,87,0.30));
}
.yield-sub{font-size:13px;margin-bottom:14px}
.yield-panel .btn-block{margin-top:0}
.yield-panel .hint{margin-top:8px}
/* no mobile font-size override — clamp() above already scales it fluidly */

/* ── Rail selector (member) + tab panels (admin) — one segmented look ─────── */
.railtabs,.vxtabs{
	display:flex;gap:4px;width:100%;margin:0 0 18px;padding:4px;
	background:var(--vv-surface);border:1px solid var(--vv-border);border-radius:12px;
}
.railtab,.vxtab{
	flex:1;text-align:center;padding:9px 12px;border-radius:9px;line-height:1.2;
	font-size:13px;font-weight:600;color:var(--muted);text-decoration:none;
	border:0;background:transparent;cursor:pointer;font-family:inherit;
	transition:background .15s,color .15s;
}
.railtab small{display:block;font-size:10px;font-weight:500;opacity:.75;margin-top:2px}
.railtab.active,.vxtab.active{background:var(--vv-gold);color:var(--vv-on-gold)}
.railtab:not(.active):hover,.vxtab:not(.active):hover{color:var(--txt)}
.vxtab-panel[hidden]{display:none}

/* Live refresh: a brief tint so a list that changed under the reader is
   NOTICED rather than silently different. Deliberately short and quiet —
   this fires whenever anybody posts, and anything louder would be a
   distraction on a busy board. */
.is-fresh{animation:vxfresh .9s ease-out}
@keyframes vxfresh{from{background:rgba(206,172,87,0.10)}to{background:transparent}}
@media (prefers-reduced-motion:reduce){.is-fresh{animation:none}}

/* Visually hidden, still read aloud. For a label whose field is self-evident on
   screen (a lone composer box) but nameless to a screen reader without it. */
.sr-only{
	position:absolute;width:1px;height:1px;margin:-1px;padding:0;
	overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* ── Public board (viewmessages, tab=public) ──────────────────────────────
   ⚠ An admin post must be distinguishable at a glance from a member's — the
   board exists so an announcement can be made quickly, and an announcement
   nobody can tell apart from chatter is not one. */
.wallpost{
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:var(--vv-radius);padding:14px 16px;margin-bottom:10px;
}
.wallpost.is-admin{border-color:var(--vv-gold);background:rgba(206,172,87,0.06)}
.wallpost.is-pinned{box-shadow:inset 3px 0 0 var(--vv-gold)}
.wallpost-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.wallpost-who{display:flex;align-items:center;gap:7px;flex-wrap:wrap;min-width:0}
.wallpost-name{font-weight:600;font-size:14px;color:var(--txt)}
.wallpost-badge,.wallpost-pin{
	padding:2px 8px;border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.5px;
}
.wallpost-badge{background:var(--vv-gold);color:var(--vv-on-gold)}
.wallpost-pin{background:var(--vv-surface-raised);color:var(--muted);border:1px solid var(--vv-border)}
.wallpost-when{margin-left:auto;font-size:11px;color:var(--muted);white-space:nowrap;flex:0 0 auto}
/* ⚠ pre-line, so the line breaks somebody typed survive WITHOUT nl2br — the body
   is another member's text and never goes near innerHTML. overflow-wrap because
   one 300-character "word" would otherwise widen the whole page. */
.wallpost-body{
	font-size:14px;line-height:1.6;color:var(--txt);
	white-space:pre-line;overflow-wrap:anywhere;
}
.wallpost-acts{display:flex;gap:6px;margin-top:10px}
.wallpost-acts .inline-form{margin:0}

/* ── The board, laid out as a group chat ───────────────────────────────────
   Oldest at the top, newest at the bottom, composer stuck above the tab bar.
   ⚠ THE PAGE SCROLLS, not an inner box with its own scrollbar. A fixed-height
   scroller inside a page that also scrolls gives two bars on a phone and traps
   the wrong one under a thumb — viewchat.php settled this already, and the board
   follows it rather than inventing a second answer.

   ⚠ IT REUSES .msg-line / .msg-bubble / .msg-meta from the private chat above.
   The first version drew a bordered CARD per post — header row, body row, a row
   of buttons — and three posts filled a phone. A group chat is a column of
   bubbles; there was never a reason for this to be its own visual language. */
.wall-feed{display:flex;flex-direction:column;gap:3px;padding-bottom:4px}
.wall-feed .pager{margin:0 0 12px}

/* ⚠ A GROUP chat line: avatar beside the bubble, and the NAME AND TIME INSIDE
   it. They used to be a row above and a row below — three rows of chrome around
   one sentence. A modifier on .msg-line, not a second component: the private
   chat is 1:1 and legitimately needs neither an avatar column nor a name, so it
   keeps the plain .msg-line. */
/* ⚠⚠ COMPOUND SELECTORS, NOT SOURCE ORDER. `.msg-grouped` alone is 0,1,0 — the
   same weight as `.msg-line`, which is defined further down this file and
   therefore WON: flex-direction went back to column and the avatar landed on its
   own line above every bubble, a wasted row per message. `.msg-line.msg-grouped`
   is 0,2,0 and cannot be undone by moving either block. This is the second time
   this exact failure has bitten here — the first was .page-title losing to
   .wrap h2 on every page of the app. */
.msg-line.msg-grouped{flex-direction:row;align-items:flex-start;gap:6px;max-width:86%}
/* ⚠ 0,3,0 — it has to out-weigh `.msg-line.is-mine`, which is also 0,2,0 and
   also later in the file. */
.msg-line.msg-grouped.is-mine{flex-direction:row-reverse;align-items:flex-start}
/* ⚠ Big enough to be a TAP TARGET, not just a decoration: it opens a private
   conversation with whoever posted, so it has to take a thumb. 34px inside a
   6px gap still reads as a chat avatar. */
.msg-grouped .msg-avatar{
	flex:0 0 auto;width:34px;height:34px;font-size:14px;margin-top:2px;
	text-decoration:none;transition:filter .15s;
}
a.msg-avatar:hover{filter:brightness(1.12);text-decoration:none}
a.msg-avatar:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
/* ⚠ Hidden, not absent, on a continuation — an omitted avatar would slide every
   bubble below the first one left out of the column. */
.msg-grouped.is-run .msg-avatar{visibility:hidden}
/* ⚠ The shared .msg-bubble carries white-space:pre-line for the private chat,
   whose body sits directly inside it. Here the bubble holds siblings, so that
   rule would turn the newlines in the MARKUP between them into blank lines.
   The body carries its own pre-line via .msg-text. */
.msg-grouped .msg-bubble{min-width:0;white-space:normal;padding:6px 10px 4px;border-radius:12px}
/* The speaker's name, first line inside the bubble. */
.msg-from{
	display:block;font-size:11.5px;font-weight:700;color:var(--vv-gold);
	line-height:1.3;margin-bottom:1px;overflow-wrap:anywhere;
}
.msg-from .wallpost-badge{margin-left:6px;vertical-align:1px}
/* ⚠ pre-line lives HERE, not on the bubble. The bubble now holds siblings, and
   pre-line on it would render the whitespace BETWEEN them as blank lines. */
.msg-text{white-space:pre-line;overflow-wrap:anywhere}
/* ⚠ Floated so the text wraps around it and short messages keep the stamp on
   their own last line — a whole row saved per message, which is the point. */
.msg-time{
	float:right;display:inline-flex;align-items:center;gap:5px;
	margin:1px 0 0 8px;font-size:9.5px;color:var(--muted);line-height:1.5;
}
.msg-line.is-mine .msg-time{color:rgba(43,31,6,0.62)}
.msg-line.is-mine .msg-acts summary{color:rgba(43,31,6,0.62)}
/* A continuation sits tight under the line before it. */
/* ⚠ A NEW SPEAKER GETS AIR, a continuation does not. With one gap for both,
   a busy board reads as a single undifferentiated column and you cannot see
   where one person stops and the next starts. 0,3,0 so it cannot lose to
   anything later in the file — see the note on .msg-line.msg-grouped. */
.msg-line.msg-grouped:not(.is-run){margin-top:11px}
.msg-grouped.is-run{margin-top:0}
/* An announcement is bordered in gold — the same signal the cards carried, at a
   fraction of the height. */
.msg-bubble.is-admin{border-color:var(--vv-gold);background:rgba(206,172,87,0.07)}
.msg-bubble.is-pinned{box-shadow:inset 3px 0 0 var(--vv-gold)}
.msg-line.is-mine .msg-bubble.is-admin{background:var(--accent);border-color:var(--accent)}

/* ⚠ THE CONTROLS ARE FOLDED AWAY behind a "⋯". Two buttons under every bubble
   is what made the board unreadable. <details>, not a JS menu — the same no-JS
   disclosure the admin nav uses, so it works with scripting off. */
/* The private chat still puts its stamp on a line under the bubble — it has
   read receipts to show there, which a public board does not have. */
.msg-meta{display:flex;align-items:center;gap:6px}
.msg-line.is-mine .msg-meta{justify-content:flex-end}
.msg-pinned{color:var(--vv-gold);font-weight:600}
.msg-acts{position:relative;display:inline-block}
.msg-acts summary{
	list-style:none;cursor:pointer;padding:0 5px;border-radius:6px;
	color:var(--muted);font-size:13px;line-height:1.2;
}
.msg-acts summary::-webkit-details-marker{display:none}
.msg-acts summary:hover{color:var(--txt);background:var(--vv-surface-raised)}
.msg-acts-menu{
	position:absolute;z-index:20;bottom:100%;left:0;margin-bottom:4px;min-width:130px;
	background:var(--vv-surface-raised);border:1px solid var(--vv-border-strong);
	border-radius:10px;padding:4px;box-shadow:0 10px 28px rgba(0,0,0,0.45);
}
/* Near the right edge the menu would run off screen. */
.msg-line.is-mine .msg-acts-menu{left:auto;right:0}
.msg-acts-menu form{margin:0}
.msg-acts-menu button{
	display:block;width:100%;text-align:left;padding:8px 10px;border:0;border-radius:7px;
	background:transparent;color:var(--txt);font:inherit;font-size:13px;cursor:pointer;
}
.msg-acts-menu button:hover{background:var(--vv-surface)}
.msg-acts-menu button.is-danger{color:var(--red)}

/* The pinned strip: above the feed, visually detached from it. */
.wall-pinned{
	border-bottom:1px solid var(--vv-border);
	margin-bottom:14px;padding-bottom:4px;
}
.wall-pinned .section-label{margin-bottom:8px}

/* The board's composer is the shared .composebar — see that rule. Nothing extra
   is needed here beyond the hint line below. */
/* ⚠ ONE LINE, with the pin toggle on it. This was three rows — a checkbox row,
   the field, and a hint — above a sticky bar already eating the bottom of a
   phone screen. */
.wall-hint{
	display:flex;align-items:center;gap:8px;flex-wrap:wrap;
	margin-top:6px;font-size:11px;line-height:1.4;
}

/* ── Incentive leaderboard (viewadmin_incentives) ─────────────────────────── */
.lb-rank{
	display:inline-flex;align-items:center;justify-content:center;
	min-width:26px;height:26px;padding:0 6px;border-radius:999px;
	background:var(--vv-surface-raised);border:1px solid var(--vv-border);
	font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--muted);
}
.lb-rank-top{background:var(--vv-gold);border-color:var(--vv-gold);color:var(--vv-on-gold)}
.lb-meter{height:5px;border-radius:4px;background:var(--line);overflow:hidden;margin:4px 0 3px;max-width:150px}
.lb-meter>span{display:block;height:100%;border-radius:4px;background:var(--vv-gold)}

/* ── My Incentives (viewincentives) — the prize ladder ────────────────────
   A card per milestone: the prize photograph as a hero with the name and tier
   over it, then what it takes and how far the member has got.

   ⚠ THE PHOTOGRAPH IS THE ADMIN'S UPLOAD, at whatever size and shape they had.
   A fixed-height hero with object-fit:cover is what stops one portrait snap of a
   Fortuner from making a card three screens tall — the alternative, letting the
   image set the height, means the layout changes every time somebody re-uploads. */
.inc-window{margin-bottom:14px}
.inc-window-head{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
.inc-window-days{
	font-family:var(--vv-font-display);font-size:40px;font-weight:700;line-height:1;
	color:var(--vv-gold);font-variant-numeric:tabular-nums;
}
.inc-window-meta{display:flex;flex-direction:column;gap:3px;font-size:13px;margin-left:auto;text-align:right}
.inc-window-meta .muted{margin-right:6px}

.inc-card{
	border:1px solid var(--vv-border);border-radius:var(--vv-radius-lg);
	background:var(--vv-surface);overflow:hidden;margin-bottom:14px;
}
.inc-card.is-reached{border-color:var(--vv-gold)}

.inc-hero{position:relative;height:190px;background:var(--vv-surface-raised);overflow:hidden}
.inc-hero-img{width:100%;height:100%;object-fit:cover;display:block}
/* Without something behind it, white text over a white Fortuner is unreadable —
   and which car it is, is the admin's choice, not ours. */
.inc-hero::after{
	content:"";position:absolute;inset:0;
	background:linear-gradient(90deg,rgba(5,8,15,0.85) 0%,rgba(5,8,15,0.35) 45%,transparent 70%);
}
.inc-hero-blank{display:flex;align-items:center;justify-content:center}
.inc-hero-text{position:absolute;left:18px;bottom:16px;z-index:1}
.inc-hero-title{
	font-family:var(--vv-font-display);font-size:26px;font-weight:700;line-height:1.1;
	color:#fff;text-transform:uppercase;letter-spacing:.5px;
	text-shadow:0 2px 12px rgba(0,0,0,0.6);
}
.inc-hero-tier{
	margin-top:4px;font-size:12px;font-weight:700;letter-spacing:1px;
	color:var(--vv-gold-light);text-shadow:0 2px 10px rgba(0,0,0,0.6);
}
.inc-flag{
	position:absolute;top:12px;right:12px;z-index:1;
	padding:4px 12px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.6px;
}
.inc-flag-won{background:var(--vv-gold);color:var(--vv-on-gold)}
.inc-flag-wait{background:rgba(234,179,8,0.9);color:#2B1F06}
.inc-flag-done{background:var(--vv-positive);color:#052E16}

.inc-body{padding:16px 18px 18px}
.inc-req{font-size:15px;line-height:1.5;margin:0 0 4px}
.inc-choice{font-size:13px;color:var(--muted);margin:0 0 10px}
.inc-meter-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-top:10px;font-size:13px}
.inc-pct{color:var(--vv-gold);font-size:15px;font-variant-numeric:tabular-nums}
.inc-meter{height:8px;border-radius:6px;background:var(--line);overflow:hidden;margin-top:7px}
.inc-meter>span{display:block;height:100%;border-radius:6px;background:var(--vv-gold);transition:width .3s}
.inc-note{font-size:13px;color:var(--muted);line-height:1.55;margin:12px 0 0}
.inc-claim{margin-top:14px}
.inc-claim select{margin-bottom:10px}
/* .btn-block carries a 22px top margin for form footers; inside the card the
   select above it already provides the gap. */
.inc-claim .btn-block{margin-top:0}

@media (max-width:560px){
	.inc-hero{height:150px}
	.inc-hero-title{font-size:21px}
	.inc-window-days{font-size:34px}
	.inc-window-meta{margin-left:0;text-align:left;width:100%}
}

/* ── Link tabs — switching by NAVIGATION, not by JS ────────────────────────
   ⚠ DELIBERATELY NOT .vxtabs. That bar is the JS panel switcher above: it
   expects <button data-tab> and toggles sibling .vxtab-panel elements. Used for
   links it does two wrong things — nothing ever gets the .active class, so no
   tab looks selected, and a click runs activate(null), stripping .active off
   every real tab on the page for the instant before the browser navigates.

   ⚠ THE SELECTED STATE IS [aria-current="page"], NOT A CLASS. The first version
   of this wrote class="on" while the stylesheet only knew .active, so both tabs
   rendered identically and you could not tell which page you were on. Styling
   the semantic attribute makes that class of bug impossible: the thing that
   tells a screen reader which tab is current is the same thing that colours it.

   Sized to its content — a two-item bar stretched across a desktop table reads
   as two large buttons rather than as tabs. */
.linktabs{
	display:inline-flex;flex-wrap:wrap;gap:4px;max-width:100%;
	margin:0 0 18px;padding:4px;
	background:var(--vv-surface);border:1px solid var(--vv-border);border-radius:12px;
}
/* ⚠ A FLEX ROW, so a count sits BESIDE the label rather than under it.
   .msg-count is `display:flex` — right in the conversation row it was written
   for, wrong here: dropped into a block-level tab it became a full-width flex
   container, rendering the badge as a gold slab across the whole tab and pushing
   it to double height. */
.linktab{
	display:inline-flex;align-items:center;justify-content:center;gap:7px;
	padding:8px 18px;border-radius:9px;line-height:1.2;white-space:nowrap;
	font-size:13px;font-weight:600;color:var(--muted);text-decoration:none;
	transition:background .15s,color .15s;
}
.linktab:hover{color:var(--txt);text-decoration:none}
.linktab[aria-current="page"]{background:var(--vv-gold);color:var(--vv-on-gold)}
.linktab[aria-current="page"]:hover{color:var(--vv-on-gold)}
.linktab:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
.linktab .msg-count{
	min-width:18px;height:18px;padding:0 5px;border-radius:9px;font-size:10px;
}
/* ⚠ On the SELECTED tab the badge would be gold on gold — invisible. Invert it
   there: the tab's own text colour as the chip, the tab's background as the
   digits. */
.linktab[aria-current="page"] .msg-count{
	background:var(--vv-on-gold);color:var(--vv-gold);
}
/* On a narrow screen the bar goes full width and the tabs share the row, which
   is the one place stretching them is right. */
@media (max-width:560px){
	.linktabs{display:flex;width:100%}
	.linktab{flex:1;text-align:center;padding:8px 10px}
}
.steps strong,.steps em{color:var(--txt)}
/* Wallet addresses / tx hashes are single 42–66 char unbreakable "words" — without
   this they blow past the panel and give the whole PAGE horizontal overflow, which
   on mobile widens the layout viewport and displaces the fixed bottom nav. */
.mono{font-family:var(--vv-font-mono);font-variant-numeric:tabular-nums slashed-zero;font-size:13px;overflow-wrap:anywhere;word-break:break-word}
.checkrow{display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.scan-result{margin-top:14px;border-radius:8px;padding:11px 14px;font-size:14px}
.scan-result.ok{background:rgba(206,172,87,0.12);border:1px solid var(--green);color:var(--green)}
.scan-result.idle{background:var(--infobg);border:1px solid var(--line);color:var(--muted)}
.scan-result.err{background:var(--dangerbg);border:1px solid var(--red);color:var(--red)}
.btn-nav{
	padding:8px 18px;border-radius:8px;border:1px solid var(--line);
	color:var(--txt);text-decoration:none;font-size:13px;font-weight:600;
}
.btn-nav:hover{border-color:var(--accent);color:var(--accent)}
a.btn-copy{text-decoration:none}
.btn[disabled]{opacity:.45;cursor:not-allowed}

/* ── Payout page (viewpayout) ───────────────────────────────────────────── */
.balance-line{font-size:14px;color:var(--muted);padding:8px 0 2px}
.calcbox{margin-top:16px;border:1px solid var(--line);border-radius:8px;padding:12px 14px;font-size:13px}
.calcbox>div{display:flex;justify-content:space-between;padding:4px 0;color:var(--muted)}
.calcbox .calc-total{border-top:1px solid var(--line);margin-top:6px;padding-top:8px;color:var(--txt)}

/* ── Admin payout monitor (viewadmin_payouts) ───────────────────────────── */
.inline-form{display:inline-block;margin:0 4px 4px 0}
.btn-mini{
	padding:5px 12px;border-radius:6px;border:1px solid var(--line);
	background:transparent;color:var(--txt);cursor:pointer;font-size:12px;font-weight:600;
}
.btn-mini:hover{border-color:var(--accent);color:var(--accent)}
.btn-mini-green{border-color:var(--green);color:var(--green)}
.btn-mini-green:hover{background:var(--green);color:var(--vv-on-gold)}
.btn-mini-red{border-color:var(--red);color:var(--red)}
.btn-mini-red:hover{background:var(--red);color:#fff}
.rec-found{color:var(--green);font-weight:600;font-size:12px}
.rec-missing{color:var(--red);font-weight:600;font-size:12px}
.manual{margin-top:6px}
.manual summary{cursor:pointer;color:var(--muted);font-size:12px}
.manual form{margin-top:6px}
.manual input{width:auto;display:inline-block;margin-right:6px;padding:5px 8px;font-size:12px}
.pill{display:inline-block;padding:3px 11px;border-radius:999px;font-size:11px;font-weight:600}
.pill-0{background:var(--infobg);color:var(--accent);border:1px solid var(--accent)}
/* ⚠ pill-1 DELIBERATELY BREAKS FROM --green, which is aliased to the gold so the
   UI has one accent. That works for figures and toggles, but not for a STATUS
   badge: it made SUCCESS, WAITING and SENT render as three gold pills that were
   indistinguishable at a glance down a list of orders — the exact confusion
   that had payouts looking unresolved when they had completed.
   A status pill is the one place the meaning has to beat the palette:
     gold  (pill-0) = waiting / informational
     GREEN (pill-1) = done, good — SUCCESS · SENT · ACTIVE · CREDITED · EARNING
     red   (pill-2) = failed / suspended
     amber (pill-3) = needs attention — HELD · IN PROGRESS
   Uses --vv-positive, the real green already in the token set. */
.pill-1{background:rgba(74,222,128,0.12);color:var(--vv-positive);border:1px solid var(--vv-positive)}
.pill-2{background:var(--dangerbg);color:var(--red);border:1px solid var(--red)}
.pill-3{background:rgba(234,179,8,0.14);color:#FCD34D;border:1px solid rgba(234,179,8,0.55)}

/* ── Admin dashboard (viewadmin_dashboard) ──────────────────────────────── */
.section-label{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin:22px 0 10px}
.solvency{border-radius:12px;padding:16px 18px;margin-bottom:6px;border:1px solid var(--line)}
.solvency.ok{background:rgba(206,172,87,0.12);border-color:var(--green)}
.solvency.warn{background:var(--dangerbg);border-color:var(--red)}
.solvency.unknown{background:var(--infobg);border-color:var(--line)}
.solvency .big{font-family:var(--vv-font-display);font-size:18px;font-weight:600}
.kv{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px solid var(--line);font-size:14px}
.kv:last-child{border-bottom:none}
.kv .k{color:var(--muted)}
.kv .v{font-weight:600;white-space:nowrap}
.switchrow{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:1px solid var(--line)}
.switchrow:last-of-type{border-bottom:none}
.switchrow .lbl{font-size:14px}
.switchrow .sub{font-size:12px;color:var(--muted);margin-top:2px}
.switch{position:relative;display:inline-block;width:48px;height:26px;flex:0 0 auto}
.switch input{opacity:0;width:0;height:0}
.slider{position:absolute;inset:0;background:var(--line);border-radius:26px;transition:.2s;cursor:pointer}
.slider:before{content:"";position:absolute;height:20px;width:20px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s}
.switch input:checked+.slider{background:var(--green)}
.switch input:checked+.slider:before{transform:translateX(22px)}
.meter{height:8px;border-radius:6px;background:var(--line);overflow:hidden;margin-top:8px}
.meter>span{display:block;height:100%;background:var(--accent)}

/* ── Dashboard header + auto-refresh control ────────────────────────────── */
.dash-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:0}
/* Dashboards open with heavy content (stat-card grid / dash-head row) instead of a
   lightweight page-title, so their first block sits tight under the header. Give it
   the breathing room a title would — matching the rhythm of every other page. */
.wrap > .cards:first-child,
.wrap > .dash-head:first-child{margin-top:16px}
.autorefresh{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--txt);margin:0}
.autorefresh input{width:auto;margin:0}
.autorefresh .muted{font-size:12px}

/* ── Pagination (pager_helper) ──────────────────────────────────────────── */
.pager{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:14px}
.pg{display:inline-block;min-width:38px;text-align:center;padding:8px 10px;border-radius:8px;border:1px solid var(--line);color:var(--txt);text-decoration:none;font-size:13px;font-weight:600}
a.pg:hover{border-color:var(--accent);color:var(--accent)}
.pg.active{background:var(--accent);border-color:var(--accent);color:var(--vv-on-gold)}
.pg.disabled{opacity:.4;pointer-events:none}
.pg.dots{border:none;min-width:auto;padding:8px 4px;color:var(--muted);font-weight:400}
.pager-info{margin-left:auto;color:var(--muted);font-size:12px}

/* ── Responsive nav hamburger (injected by app.js) ──────────────────────── */
.nav-toggle{display:none;flex-direction:column;justify-content:center;gap:4px;
	width:42px;height:38px;border:1px solid var(--line);border-radius:8px;background:transparent;cursor:pointer;padding:0}
.nav-toggle span{display:block;width:18px;height:2px;background:var(--txt);margin:0 auto;border-radius:2px;transition:.2s}
.nav.open .nav-toggle span:nth-child(1){transform:translateY(6px) rotate(45deg)}
.nav.open .nav-toggle span:nth-child(2){opacity:0}
.nav.open .nav-toggle span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}

/* ── Phones: collapse nav into a dropdown ───────────────────────────────── */
@media (max-width:640px){
	.nav{flex-wrap:wrap;padding:12px 16px}
	.nav.has-toggle .nav-toggle{display:flex}
	.nav.has-toggle .userbox{display:none;order:3;width:100%;flex-direction:column;align-items:stretch;gap:8px;margin-top:12px}
	.nav.has-toggle.open .userbox{display:flex}
	.userbox .uname{order:-1;text-align:center;padding-bottom:6px;border-bottom:1px solid var(--line)}
	.userbox .btn-nav,.userbox .btn-logout{text-align:center;padding:11px 18px;font-size:14px}
}

/* ── Phones: general layout tweaks ──────────────────────────────────────── */
@media (max-width:560px){
	.wrap{padding:18px 12px}
	.wrap h2{font-size:12px;margin:22px 0 10px}
	/* Same specificity fight as the desktop pair above — see the note there. */
	.page-title,
	.wrap .page-title{font-size:20px;margin:0 0 12px}
	.cards{grid-template-columns:1fr 1fr;gap:10px}
	.stat{padding:14px}
	.stat .val{font-size:19px}
	.hero h1{font-size:26px}
	.hero .btn{display:block;width:100%}
	.hero .btn+.btn{margin-left:0;margin-top:10px}
	.auth-card{padding:24px}
	.table th,.table td{padding:7px 8px}
	.table-wrap{-webkit-overflow-scrolling:touch}
	.checkrow{gap:10px}
	.pager-info{width:100%;margin:8px 0 0;text-align:center}
	.switchrow{gap:12px}
	/* Account: stack + centre the profile-picture preview on phones */
	.avatar-edit{flex-direction:column;align-items:center;text-align:center}
	.avatar-form{width:100%}
}
/* Keep a minimum of 2 stat columns even on the narrowest phones (down to 320px);
   tighten the gap/padding so two cards still fit comfortably. */
@media (max-width:380px){
	.cards{grid-template-columns:1fr 1fr;gap:8px}
	.stat{padding:12px}
	.stat .val{font-size:18px}
}

/* ═══ APP CHROME — slim top bar + fixed bottom tab nav ════════════════════ */
.topbar{
	display:flex;align-items:center;justify-content:space-between;
	padding:13px 18px;border-bottom:1px solid var(--line);
	position:sticky;top:0;background:var(--bg);z-index:20;
}
.topbar .brand{font-size:18px}
.topbar-user{font-size:13px;color:var(--muted);font-weight:600;max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* content clears the fixed bar */
/* ⚠ Derived from the shared tab-bar height, not measured by hand. It read 90px
   while the chat composer assumed 72px for the same bar; one of them had to be
   wrong, and both were guesses. 74 + 16 is the same 90px it always was. */
body.has-tabbar .wrap{padding-bottom:calc(var(--vv-tabbar-h) + 16px)}
/* ⚠ A FIXED composer is out of flow, so the page has to keep its height back
   or the newest message hides behind it. --vv-composebar-h is a sensible
   DEFAULT; vxComposer measures the real bar and overwrites it, which it has to
   because the field grows as you type. Correct without JS, exact with it. */
body.has-composebar .wrap{
	padding-bottom:calc(var(--vv-tabbar-h) + var(--vv-composebar-h) + 16px);
}

.tabbar{
	position:fixed;left:0;right:0;bottom:0;z-index:60;
	background:var(--card);border-top:1px solid var(--line);
	padding-bottom:env(safe-area-inset-bottom,0);
}
.tabbar-inner{display:flex;max-width:620px;margin:0 auto}
.tab{
	flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
	padding:8px 2px;color:var(--muted);text-decoration:none;
	font-size:11px;font-weight:600;letter-spacing:.2px;
	-webkit-tap-highlight-color:transparent;transition:color .15s;
}
/* Every tab is a "punched-in" disc: a 40px circle with a 3px bar-coloured ring so it
   reads as inset into the bar, glyph centred so all icons align on one axis. Neutral
   by default; the ACTIVE (current-page) tab fills with the accent + glow + white icon. */
.ico-wrap{
	display:flex;align-items:center;justify-content:center;
	width:40px;height:40px;border-radius:50%;
	background:var(--vv-surface-raised);color:var(--muted);
	border:3px solid var(--card);
	transition:background .15s,color .15s,box-shadow .15s;
}
.tab svg{width:23px;height:23px;display:block}
.tab.active{color:var(--accent)}
.tab.active .ico-wrap{
	background:var(--accent);color:var(--vv-on-gold);
	box-shadow:0 4px 12px rgba(206,172,87,0.42);
}
.tab:not(.active):active .ico-wrap{filter:brightness(1.18)}
.tab.primary span{font-weight:700}

/* Account list-rows (Admin link, Log Out, etc.) */
.row-link{
	display:flex;align-items:center;gap:12px;padding:15px 4px;
	border-bottom:1px solid var(--line);color:var(--txt);
	text-decoration:none;font-size:15px;font-weight:600;
	-webkit-tap-highlight-color:transparent;
}
.row-link:last-child{border-bottom:none}
.row-link svg{width:20px;height:20px;color:var(--muted);flex:0 0 auto}
.row-link .chev{margin-left:auto;color:var(--muted);font-size:20px;line-height:1}
.row-link:active{opacity:.55}
.row-link.danger{color:var(--red)}
.row-link.danger svg{color:var(--red)}

/* ── Avatars / profile picture ──────────────────────────────────────────── */
.avatar{border-radius:50%;object-fit:cover;flex:0 0 auto;display:inline-flex;vertical-align:middle;background:var(--card)}
.avatar-sm{width:30px;height:30px}
.avatar-lg{width:88px;height:88px}
.avatar-ph{align-items:center;justify-content:center;background:var(--vv-gold);color:var(--vv-on-gold);font-weight:700;line-height:1}
.avatar-sm.avatar-ph{font-size:14px}
.avatar-lg.avatar-ph{font-size:38px}
/* topbar: avatar only (no username) — a link to the account page, sized to match
   the header logo's height so the two sit on one visual line. */
.topbar-user{display:inline-flex;align-items:center;flex:0 0 auto;max-width:none;
	text-decoration:none;border-radius:50%;line-height:0}
.topbar-user .avatar{width:30px;height:30px;font-size:14px;
	border:1px solid var(--vv-border-strong);transition:border-color .15s}
.topbar-user:hover .avatar,.topbar-user:focus-visible .avatar{border-color:var(--vv-gold)}
.topbar-user:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
@media (max-width:560px){.topbar-user .avatar{width:28px;height:28px;font-size:13px}}
/* account: upload panel */
.avatar-edit{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.avatar-form{flex:1;min-width:200px;display:flex;flex-direction:column;gap:8px;align-items:stretch}
.avatar-pick{cursor:pointer;text-align:center}

/* avatar preview box + background-upload spinner */
.avatar-box{position:relative;display:inline-flex;flex:0 0 auto}
.avatar[hidden]{display:none}
.avatar-spinner{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;background:rgba(6,10,16,0.55);border-radius:50%}
.avatar-spinner[hidden]{display:none}
.spinner{width:26px;height:26px;border-radius:50%;border:3px solid rgba(255,255,255,0.28);border-top-color:#fff;animation:vxspin .7s linear infinite}
@keyframes vxspin{to{transform:rotate(360deg)}}

/* ── Confirm dialog (vxConfirm) ──────────────────────────────────────────── */
.vx-backdrop{
	position:fixed;inset:0;z-index:200;
	display:flex;align-items:center;justify-content:center;padding:20px;
	background:rgba(2,6,14,0.66);
	-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
	opacity:0;transition:opacity .18s ease;
}
.vx-backdrop.show{opacity:1}
.vx-modal{
	width:100%;max-width:390px;
	background:var(--vv-surface-raised);
	border:1px solid var(--vv-border-strong);
	border-radius:var(--vv-radius-lg);
	box-shadow:0 24px 60px rgba(0,0,0,0.55);
	padding:22px;text-align:left;
	transform:translateY(10px) scale(.985);
	transition:transform .18s ease;
}
.vx-backdrop.show .vx-modal{transform:none}
.vx-modal h3{
	font-family:var(--vv-font-display);font-size:17px;font-weight:700;
	letter-spacing:-.1px;color:var(--vv-text);margin-bottom:8px;
}
.vx-modal p{font-size:14px;line-height:1.6;color:var(--vv-text-muted);white-space:pre-line;word-break:break-word}
/* Optional free-text note inside the confirm dialog (data-confirm-prompt). */
.vx-modal-prompt{
	display:block;margin-top:16px;
	font-size:12px;font-weight:600;color:var(--muted);
	text-transform:uppercase;letter-spacing:.4px;
}
.vx-modal-input{
	display:block;width:100%;margin-top:7px;
	padding:11px 13px;border-radius:10px;
	background:var(--vv-surface);border:1px solid var(--vv-border-strong);
	color:var(--vv-text);font:inherit;font-size:14px;font-weight:400;
	text-transform:none;letter-spacing:normal;
}
.vx-modal-input:focus{outline:2px solid var(--vv-gold-light);outline-offset:1px;border-color:var(--vv-gold)}
.vx-modal-actions{display:flex;gap:10px;margin-top:20px}
.vx-modal-actions .btn{flex:1;padding:12px 16px;font-size:14px;text-align:center}
.vx-modal .btn-danger{
	background:var(--vv-negative);color:#fff;border-color:var(--vv-negative);
	box-shadow:0 8px 22px rgba(248,113,113,0.26);
}
.vx-modal .btn-danger:hover{filter:brightness(1.08)}
@media (max-width:420px){
	.vx-modal-actions{flex-direction:column-reverse}
	.vx-modal-actions .btn{width:100%}
}
@media (prefers-reduced-motion:reduce){
	.vx-backdrop,.vx-modal{transition:none}
}

/* ── Toast notifications (vxToast) — fixed, just below the header ────────── */
.toasts{
	position:fixed;top:58px;left:50%;transform:translateX(-50%);
	z-index:100;width:calc(100% - 24px);max-width:440px;
	display:flex;flex-direction:column;gap:8px;pointer-events:none;
}
.toast{
	pointer-events:auto;cursor:pointer;
	padding:12px 16px;border-radius:10px;
	font-size:14px;font-weight:600;line-height:1.4;color:var(--txt);
	background:var(--vv-surface-raised);
	border:1px solid var(--vv-border-strong);border-left:4px solid var(--muted);
	box-shadow:0 10px 28px rgba(0,0,0,0.45);
	opacity:0;transform:translateY(-10px);
	transition:opacity .25s ease,transform .25s ease;
}
.toast.show{opacity:1;transform:translateY(0)}
.toast.hide{opacity:0;transform:translateY(-10px)}
.toast-success{border-left-color:var(--green)}
.toast-error{border-left-color:var(--red)}
.toast-info{border-left-color:var(--accent)}

/* ── Admin member directory (/admin/members, /admin/member/<id>) ─────────── */

/* The roster carries a dozen money columns; 1000px forces a scrollbar on every
   desktop. Opt-in wider shell — .wrap's own padding and mobile rules still apply. */
.wrap-wide{max-width:1440px}

/* Filter row: search grows, the selects keep their natural width, actions sit
   at the end. Wraps to stacked fields on a phone. */
.filterbar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px}
/* ⚠ A FIXED BASIS, NOT `auto`. With flex:0 0 auto each field sized to its own
   content — and the content of a <select> is its LONGEST OPTION. One label like
   "Held — awaiting your approval" was enough to stretch that field ~230px, blow
   past the row, and push Apply/Reset onto a line of their own with dead space
   above them. A 150px basis lets the fields share the row evenly and shrink
   when they must, so the option wording no longer decides the layout. */
.filterbar .fb-field{flex:1 1 150px;min-width:130px}
.filterbar .fb-grow{flex:2 1 240px;min-width:190px}
/* The select must not re-inflate the field it sits in. */
.filterbar select,.filterbar input{width:100%;min-width:0}
.filterbar label{margin:0 0 6px}
.filterbar .fb-actions{display:flex;align-items:center;gap:8px;margin-left:auto}
/* ⚠ APPLY AND RESET MUST BE THE SAME HEIGHT. .btn resolves to 42px here while
   .btn-mini is 26px, so a big gold pill sat next to a small chip with 16px of
   nothing under it, and neither lined up with the 42px inputs beside them. An
   explicit min-height pins all three to one baseline; Reset keeps its smaller,
   quieter type so it still reads as the secondary action. */
.filterbar .fb-actions .btn,
.filterbar .fb-actions .btn-mini{
	margin:0;min-height:42px;
	display:inline-flex;align-items:center;justify-content:center;
	line-height:1.2;
}
.filterbar .fb-actions .btn{padding:0 20px}
.filterbar .fb-actions .btn-mini{padding:0 16px;border-radius:10px;font-size:13px}
@media(max-width:640px){
	.filterbar .fb-field,.filterbar .fb-grow{flex:1 1 100%;min-width:0}
	.filterbar .fb-actions{margin-left:0;width:100%}
	.filterbar .fb-actions .btn{flex:1}
}

/* Member cell in the roster: avatar + name, the whole thing a link. */
.member-cell{display:flex;align-items:center;gap:9px;text-decoration:none;color:var(--txt)}
.member-cell .member-name{font-weight:600}
.member-cell:hover .member-name{color:var(--accent)}

/* Detail-page header: avatar, identity, action. */
.member-head{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-bottom:18px}
.member-head-body{flex:1 1 auto;min-width:0}
.member-head-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}
.member-head-actions{flex:0 0 auto;margin-left:auto}
.member-head-actions .inline-form{margin:0}
@media(max-width:640px){
	.member-head{gap:14px}
	.member-head-actions{margin-left:0;width:100%}
}

/* Post-dated ledger rows — booked, but not spendable until their date. */
.row-future td{background:rgba(234,179,8,0.06)}

/* ── Support / Telegram button pair (views/_supportlinks.php, panel style) ──
   `flex:1 1 200px` is what makes this work: while the row is wide enough for
   two, the buttons share it equally; the moment they wrap to a single column
   each one stretches to the FULL width instead of sitting at its natural size
   against dead space. .checkrow (the old container) never stretched them. */
.support-cta{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}
.support-cta .btn{
	flex:1 1 200px;margin:0;
	text-align:center;padding:13px 20px;
	white-space:nowrap;
}
/* Below the wrap point they are already full width — tighten the gap a little
   so a stacked pair doesn't read as two unrelated blocks. */
@media (max-width:420px){
	.support-cta{gap:8px}
	.support-cta .btn{flex-basis:100%}
}

/* ── Admin header: one pull-down menu (views/_adminnav.php) ──────────────── */
/* A <details>/<summary> disclosure, so it works with JS off. app.js only adds
   close-on-outside-click and Escape. */

.adminnav{gap:12px}
.vxmenu{position:relative;flex:0 0 auto}

.vxmenu-btn{
	display:inline-flex;align-items:center;gap:9px;
	padding:8px 12px;border-radius:10px;cursor:pointer;
	border:1px solid var(--vv-border-strong);background:var(--vv-surface);
	color:var(--txt);font-size:14px;font-weight:600;line-height:1;
	list-style:none;user-select:none;
}
/* Kill the native disclosure triangle (both engines). */
.vxmenu-btn::-webkit-details-marker{display:none}
.vxmenu-btn::marker{content:''}
.vxmenu-btn:hover{border-color:var(--accent);color:var(--accent)}
.vxmenu-btn:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
.vxmenu-btn-ico{display:inline-flex;flex:0 0 auto}
.vxmenu-btn-ico svg{width:17px;height:17px;display:block}
.vxmenu-btn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:44vw}
.vxmenu-caret{display:inline-flex;flex:0 0 auto;color:var(--muted);transition:transform .18s ease}
.vxmenu-caret svg{width:15px;height:15px;display:block}
.vxmenu[open] .vxmenu-btn{border-color:var(--accent);color:var(--accent)}
.vxmenu[open] .vxmenu-caret{transform:rotate(180deg)}

.vxmenu-panel{
	position:absolute;top:calc(100% + 8px);right:0;z-index:60;
	width:320px;max-width:calc(100vw - 32px);
	padding:8px;
	background:var(--vv-surface-raised);
	border:1px solid var(--vv-border-strong);
	border-radius:var(--vv-radius-lg);
	box-shadow:0 18px 44px rgba(0,0,0,0.5);
	max-height:calc(100vh - 90px);overflow-y:auto;
}
.vxmenu-head{
	padding:8px 10px 10px;margin-bottom:4px;
	border-bottom:1px solid var(--line);font-size:13px;
	display:flex;flex-direction:column;gap:2px;
}
.vxmenu-head .muted{font-size:11px}
.vxmenu-group{
	padding:10px 10px 5px;font-size:11px;font-weight:600;
	color:var(--muted);text-transform:uppercase;letter-spacing:.6px;
}
.vxmenu-item{
	display:flex;align-items:flex-start;gap:11px;
	padding:9px 10px;border-radius:9px;
	text-decoration:none;color:var(--txt);
	-webkit-tap-highlight-color:transparent;
}
.vxmenu-item:hover{background:var(--vv-surface)}
.vxmenu-item:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:-2px}
.vxmenu-item-ico{display:inline-flex;flex:0 0 auto;color:var(--muted);margin-top:1px}
.vxmenu-item-ico svg{width:19px;height:19px;display:block}
.vxmenu-item-body{display:flex;flex-direction:column;gap:2px;min-width:0}
.vxmenu-item-label{font-size:14px;font-weight:600;line-height:1.25}
.vxmenu-item-sub{font-size:11px;color:var(--muted);line-height:1.35}
.vxmenu-item.active{background:var(--vv-surface)}
.vxmenu-item.active .vxmenu-item-label,
.vxmenu-item.active .vxmenu-item-ico{color:var(--accent)}
.vxmenu-item.danger .vxmenu-item-label,
.vxmenu-item.danger .vxmenu-item-ico{color:var(--red)}
.vxmenu-item.danger:hover{background:var(--dangerbg)}
.vxmenu-sep{height:1px;background:var(--line);margin:8px 4px}
/* Pending-work count — on the closed button AND its menu row, so a queue
   members are waiting on can't sit unnoticed behind a shut menu. */
.vxmenu-count{
	flex:0 0 auto;margin-left:auto;align-self:center;
	min-width:20px;padding:2px 7px;border-radius:999px;
	background:var(--red);color:#fff;
	font-size:11px;font-weight:700;line-height:1.5;text-align:center;
	font-variant-numeric:tabular-nums;
}
.vxmenu-btn .vxmenu-count{margin-left:0}

/* ── Admin header on phones ───────────────────────────────────────────────
   The bar wraps below 640px, which puts the menu button on the LEFT. A panel
   anchored to the button's right edge then extends leftwards off-screen and
   gets clipped. So on phones anchor the panel to the NAV BAR instead: .vxmenu
   goes position:static, which hands the panel's containing block to .nav —
   .nav is position:sticky, i.e. a positioned element, so absolute children
   resolve against it. left/right then pin the panel inside the viewport. */
@media (max-width:640px){
	/* ⚠ SCOPED TO .adminnav ON PURPOSE. This block used to target .vxmenu
	   globally, which broke the header avatar menu once that became a second
	   .vxmenu: its panel inherited `left:12px` while also setting `right:0` and
	   a width, and a box with left + right + width is over-constrained — CSS
	   discards `right`, so the panel anchored from the LEFT and ran off the
	   screen. Keep admin-only positioning admin-only. */
	.adminnav .vxmenu{position:static}
	.adminnav .vxmenu-panel{
		left:12px;right:12px;width:auto;max-width:none;
		top:calc(100% + 6px);
		max-height:calc(100vh - 120px);
	}
}

@media (max-width:560px){
	/* Keep brand + menu button on ONE line: shrink the logo, let the button
	   shrink, and ellipsis the label rather than wrapping the bar. */
	.adminnav{gap:8px}
	.adminnav .brand-logo{max-width:32vw}
	.adminnav .vxmenu{flex:0 1 auto;min-width:0}
	.vxmenu-btn{max-width:100%;padding:8px 10px}
	.vxmenu-btn-label{max-width:30vw}
	/* Long peso figures must never widen a stat card past its column. */
	.stat .val{overflow-wrap:anywhere}
}

@media (max-width:420px){
	/* The ADMIN chip is decorative — the menu button and panel header both say
	   where you are. Drop it before the bar is forced to wrap. */
	.adminnav .brand-wrap .badge{display:none}
	.vxmenu-btn-label{max-width:38vw}
}

/* ── Two-option mode picker (radio cards) — e.g. withdrawal release mode ─── */
.modechoice{
	display:flex;align-items:flex-start;gap:12px;
	padding:14px;margin:0 0 10px;border-radius:12px;cursor:pointer;
	border:1px solid var(--vv-border);background:var(--vv-bg);
}
.modechoice:last-of-type{margin-bottom:0}
/* ⚠ `.modechoice` carries no TOP margin — it relies on the previous choice's
   bottom margin, which works only between two of them. On the Withdrawals panel
   the first one follows a `.warnbox`, and `.warnbox` has no bottom margin, so
   the notice and the first radio card rendered flush against each other as one
   box. Same latent shape as `.cards` and `.panel` before it: spacing that
   depends on what happens to come next. */
.warnbox + .modechoice{margin-top:14px}
/* And the mirror of it — a notice placed under the last choice. */
.modechoice + .warnbox{margin-top:14px}
.modechoice:hover{border-color:var(--vv-border-strong)}
.modechoice.on{border-color:var(--accent);background:var(--infobg)}
.modechoice input[type=radio]{width:18px;height:18px;flex:0 0 auto;margin:2px 0 0;accent-color:var(--accent);cursor:pointer}
.modechoice-body{display:flex;flex-direction:column;gap:3px;min-width:0}
.modechoice-title{font-size:14px;font-weight:600;color:var(--txt)}
.modechoice-sub{font-size:12px;line-height:1.6;color:var(--muted)}
.modechoice:focus-within{outline:2px solid var(--vv-gold-light);outline-offset:2px}

/* ── Sign-up: referrer preview ────────────────────────────────────────────
   Confirms WHO the visitor is signing up under before they create the
   account. Sits directly under the referral-code field.                    */
.is-hidden{display:none !important}
.ref-card{
	display:flex;align-items:center;gap:10px;
	margin:8px 0 4px;padding:9px 12px;
	border:1px solid var(--vv-gold-dark);border-radius:10px;
	background:rgba(206,172,87,0.07);
}
.ref-card-text{display:flex;flex-direction:column;line-height:1.3;min-width:0}
.ref-card-lbl{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.ref-card strong{
	font-size:14px;color:var(--vv-gold-light);
	overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.ref-miss{
	margin:8px 0 4px;padding:9px 12px;
	border:1px solid var(--vv-negative);border-radius:10px;
	background:rgba(248,113,113,0.07);
	font-size:12px;line-height:1.5;color:var(--muted);
}

/* ── Header avatar pull-down ──────────────────────────────────────────────
   .topbar-user used to be a circular <a> straight to /account. It is now the
   <summary> of a .vxmenu disclosure holding Profile / Admin / Log Out, so it
   needs the native marker killed and room for the caret. Declared AFTER the
   original rules so these win without raising specificity.               */
.topbar-menu{position:relative;flex:0 0 auto}
summary.topbar-user{
	display:inline-flex;align-items:center;gap:5px;
	border-radius:999px;padding:3px 4px 3px 3px;
	cursor:pointer;list-style:none;user-select:none;
	border:1px solid transparent;transition:border-color .15s;
}
summary.topbar-user::-webkit-details-marker{display:none}
summary.topbar-user::marker{content:''}
summary.topbar-user .vxmenu-caret svg{width:14px;height:14px}
.topbar-menu[open] summary.topbar-user{border-color:var(--vv-border-strong)}
.topbar-menu[open] .topbar-user .avatar{border-color:var(--vv-gold)}
/* The panel hangs off the RIGHT edge of the bar at every screen size.
   `left:auto` is load-bearing, not decoration: any inherited `left` would
   combine with `right` + `width` into an over-constrained box, and CSS resolves
   that by ignoring `right` — which silently flips the panel to left-anchored and
   pushes it off the screen. Stating left:auto here makes the anchor explicit and
   immune to whatever a future .vxmenu rule sets. */
.topbar-menu .vxmenu-panel{
	left:auto;
	right:0;
	width:284px;
	max-width:calc(100vw - 24px);
}

/* ── Glass surfaces ───────────────────────────────────────────────────────
   The card-like surfaces that sit DIRECTLY on the constellation backdrop, all
   frosted the same way. Declared here, at the end, deliberately: .auth-card,
   .faq-item and .plan-card each set their own background earlier at the same
   specificity, so an earlier rule would lose to them.

   NOT in this list, on purpose:
   · .warnbox      — already a translucent gold tint; it is meant to sit ON a
                     panel and tint it, not to be another pane of glass.
   · .vxmenu-panel — a dropdown floating over content. Blurring page text behind
                     a menu makes the menu hard to read, not elegant.
   · .topbar / .tabbar — kept opaque so the constellation cannot scroll visibly
                     behind the chrome (matches the reference mock-up).           */
.auth-card,.faq-item,.plan-card{
	background:var(--vv-glass);
	-webkit-backdrop-filter:var(--vv-glass-blur);
	backdrop-filter:var(--vv-glass-blur);
	border-color:var(--vv-glass-line);
	box-shadow:var(--vv-glass-shadow);
}
/* An open accordion row would otherwise stack a second glass fill over the
   first and read markedly lighter than its neighbours. */
.faq-item[open]{background:var(--vv-glass)}

/* Fallback: Firefox before 103 and older Safari have no backdrop-filter. Without
   it these panels would be a thin translucent wash with the constellation
   showing straight through the text. Give them an opaque surface instead —
   less pretty, still readable, which is the right trade. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
	.panel,.stat,.auth-card,.faq-item,.plan-card{
		background:var(--vv-glass-solid);
	}
}

/* ── Notifications: header bell ───────────────────────────────────────────
   The bell and the avatar menu share the right-hand end of the header, so they
   need a flex parent of their own — .topbar is space-between and would push
   them apart to opposite ends of the bar. */
.topbar-right{display:flex;align-items:center;gap:8px;flex:0 0 auto}

.topbar-bell{
	position:relative;flex:0 0 auto;
	display:inline-flex;align-items:center;justify-content:center;
	width:34px;height:34px;border-radius:50%;
	color:var(--muted);text-decoration:none;
	border:1px solid transparent;transition:color .15s,border-color .15s;
}
.topbar-bell svg{width:20px;height:20px;display:block}
.topbar-bell:hover,.topbar-bell:focus-visible{color:var(--vv-text);border-color:var(--vv-border-strong)}
.topbar-bell:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}
/* Unread: the bell itself goes gold as well as carrying the count, so it still
   reads as "something is waiting" on a screen where the small red pill is easy
   to miss — and for anyone who cannot distinguish the pill's colour. */
.topbar-bell.has-unread{color:var(--vv-gold)}

.bell-count{
	position:absolute;top:-2px;right:-3px;
	min-width:17px;padding:1px 4px;border-radius:999px;
	background:var(--red);color:#fff;
	font-size:10px;font-weight:700;line-height:1.5;text-align:center;
	font-variant-numeric:tabular-nums;
	border:2px solid var(--bg);   /* punches the pill out of the bar behind it */
}
@media (max-width:560px){
	.topbar-bell{width:32px;height:32px}
	.topbar-bell svg{width:19px;height:19px}
}

/* ── Notifications: the list ──────────────────────────────────────────────
   Rows are <button>s, not links: opening one shows a modal rather than
   navigating, so a link would be lying about what happens. */
.notif-list{display:flex;flex-direction:column;gap:10px}
.notif-item{
	display:flex;align-items:flex-start;gap:12px;width:100%;
	padding:14px 15px;text-align:left;cursor:pointer;
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:var(--vv-radius);color:var(--vv-text);
	font:inherit;transition:border-color .15s,background .15s;
}
.notif-item:hover{border-color:var(--vv-border-strong)}
.notif-item:focus-visible{outline:2px solid var(--vv-gold-light);outline-offset:2px}

/* Default is NEUTRAL. Red is reserved for the one message a member has to act
   on — if routine receipts shared it, that message would stop standing out. */
.notif-ico{
	flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
	width:34px;height:34px;border-radius:50%;
	background:rgba(148,163,184,0.12);color:var(--muted);
}
.notif-ico svg{width:18px;height:18px}
.notif-ico.bad{background:rgba(248,113,113,0.12);color:var(--vv-negative)}
.notif-ico.warn{background:rgba(206,172,87,0.14);color:var(--vv-gold)}
.notif-ico.good{background:rgba(74,222,128,0.12);color:var(--vv-positive)}

.notif-body{flex:1;min-width:0}
.notif-title{font-size:14px;font-weight:700;line-height:1.35;color:var(--vv-text);word-break:break-word}
.notif-snip{
	margin-top:3px;font-size:13px;line-height:1.45;color:var(--muted);
	display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
	overflow:hidden;word-break:break-word;
}
.notif-when{margin-top:6px;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* Unread rows carry a gold left edge AND a dot — never colour alone. */
.notif-item.unread{border-left:3px solid var(--vv-gold);background:var(--vv-surface-raised)}
.notif-dot{
	flex:0 0 auto;align-self:center;
	width:8px;height:8px;border-radius:50%;background:var(--vv-gold);
}
.notif-item.read .notif-title{font-weight:600;color:var(--muted)}

.notif-empty{padding:34px 18px;text-align:center;color:var(--muted);font-size:14px}
.notif-actions{display:flex;justify-content:flex-end;margin-bottom:12px}
/* .btn is a full-size CTA (14px 26px); this bar is a secondary control sitting
   above a list, so size it down here rather than inventing a global modifier. */
.notif-actions .btn{padding:8px 16px;font-size:13px}

/* ── Notifications: the read modal ────────────────────────────────────────
   Reuses .vx-backdrop / .vx-modal, but these messages are a screenful of
   explanation rather than a one-line confirm, so the box is wider and the text
   scrolls INSIDE it — a tall modal that overflows a phone screen would put the
   Close button somewhere unreachable. */
.vx-modal.vx-modal-read{max-width:520px}
.vx-modal-read .vx-modal-when{
	font-size:11.5px;color:var(--muted);margin:-4px 0 10px;
	font-variant-numeric:tabular-nums;
}
.vx-modal-read .vx-modal-text{
	max-height:min(52vh,420px);overflow-y:auto;
	font-size:14px;line-height:1.65;color:var(--vv-text-muted);
	white-space:pre-line;word-break:break-word;
	padding-right:4px;
}

/* ── Admin member list: per-row actions ───────────────────────────────────
   View + Pause/Resume sit in the last cell. white-space:nowrap keeps the pair
   on one line so the button never wraps under the link and doubles the row
   height across a 25-row table. */
.row-actions{white-space:nowrap}
.row-actions .btn-mini{vertical-align:middle}
.row-actions .inline-form{margin:0 0 0 4px;vertical-align:middle}

/* ── Page-size selector (views/_perpage.php) ──────────────────────────────
   Sits beside the pager under every list. Deliberately quiet: it is a control
   you reach for occasionally, not a call to action. */
.perpage{display:inline-flex;align-items:center;gap:8px;margin:0}
.perpage label{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px}
.perpage select{
	width:auto;min-width:68px;padding:6px 26px 6px 10px;
	font-size:13px;border-radius:8px;margin:0;
}
/* Pager and size selector on one line, pager left, size right. Wraps to two
   rows on a phone rather than squeezing either. */
.listfoot{
	display:flex;align-items:center;justify-content:space-between;
	gap:12px;flex-wrap:wrap;margin-top:14px;
}
.listfoot .pager{margin-top:0}

/* ── Live lists (data-live-list, see vxLiveList in app.js) ────────────────
   While a page is being fetched the old rows stay put, dimmed and inert. They
   are NOT blanked: a table that empties and refills on every click flashes far
   worse than the full reload this replaces, and the row positions jumping is
   what made paging feel slow in the first place. */
.livelist{position:relative;transition:opacity .12s ease}
.livelist.is-loading{opacity:.45;pointer-events:none}
/* Thin progress bar across the top of the list — enough to say "working"
   without moving anything. */
.livelist.is-loading::before{
	content:"";position:absolute;left:0;right:0;top:0;height:2px;z-index:2;
	background:linear-gradient(90deg,transparent,var(--vv-gold),transparent);
	background-size:40% 100%;background-repeat:no-repeat;
	animation:vxlive 1s linear infinite;
	border-radius:2px;
}
@keyframes vxlive{
	0%{background-position:-40% 0}
	100%{background-position:140% 0}
}
@media (prefers-reduced-motion:reduce){
	.livelist{transition:none}
	.livelist.is-loading::before{animation:none;background:var(--vv-gold);background-size:100% 100%;opacity:.5}
}

/* ── Messaging ──────────────────────────────────────────────────────────────
   Inbox rows, then the chat thread. The header icon reuses .topbar-bell and
   .bell-count so the two badges are visually identical — neither notifications
   nor messages should look more urgent than the other by styling alone. */

/* One conversation in the inbox. A row, not a card: an inbox is a list you scan
   down, and cards put a gap between every line of it. */
.msg-row{
	display:flex;align-items:center;gap:12px;padding:14px 16px;
	border-bottom:1px solid var(--line);color:var(--txt);text-decoration:none;
	transition:background .12s;
}
.msg-row:last-child{border-bottom:0}
.msg-row:hover{background:var(--vv-glass)}
/* Unread reads as weight and an accent edge, never colour alone — colour alone
   is invisible to a good number of people. */
.msg-row.is-unread{background:rgba(206,172,87,0.05);box-shadow:inset 3px 0 0 var(--accent)}
.msg-row.is-unread .msg-name,
.msg-row.is-unread .msg-snip{font-weight:600;color:var(--txt)}

.msg-avatar{
	flex:0 0 auto;width:38px;height:38px;border-radius:50%;
	display:flex;align-items:center;justify-content:center;
	background:var(--accent);color:var(--vv-on-gold);font-weight:700;font-size:15px;
	overflow:hidden;text-decoration:none;
}
/* ⚠ The uploaded picture FILLS the circle. object-fit:cover, because a portrait
   snapshot letterboxed into a round frame reads as a bug; and the element keeps
   its background, so a slow or missing image is a gold disc rather than a hole. */
.msg-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
/* ⚠ min-width:0 is what lets the snippet ellipsis work — without it the flex
   item refuses to shrink below its content and the row runs off the screen. */
.msg-main{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1 1 auto}
.msg-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.msg-name{font-size:14px;font-weight:500}
.msg-when{flex:0 0 auto;font-size:11px;color:var(--muted)}
.msg-snip{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.msg-count{
	flex:0 0 auto;min-width:20px;height:20px;padding:0 6px;border-radius:10px;
	background:var(--accent);color:var(--vv-on-gold);
	font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;
}

/* ── Contacts: the sponsor and the people you invited ─────────────────────
   A wrapping row of chips rather than a list, because it is a shortcut sitting
   above the real content and should not push the conversations off the screen
   on a phone. */
.contact-row{display:flex;flex-wrap:wrap;gap:8px}
.contact{
	display:flex;align-items:center;gap:8px;padding:8px 12px 8px 8px;
	border:1px solid var(--vv-border);border-radius:999px;
	background:var(--vv-bg);color:var(--txt);text-decoration:none;
	transition:border-color .12s;
}
.contact:hover{border-color:var(--accent)}
/* ⚠ Weight and a ring, never colour alone — the same rule the unread inbox row
   follows, for the same reason. */
.contact.has-unread{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
.contact.has-unread .contact-name{font-weight:700}
.contact .msg-avatar{width:28px;height:28px;font-size:12px}
.contact-name{font-size:13px;font-weight:500;line-height:1.1}
/* The relationship, so "invited you" and "you invited" are never guessed at. */
.contact-rel{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px}
.contact .msg-count{min-width:18px;height:18px;font-size:10px}

@media (max-width:560px){
	/* On a phone the relation label is what goes first — the chips have to stay
	   one line tall or a member with a dozen referrals scrolls past nothing else. */
	.contact-rel{display:none}
	.contact{padding:6px 12px 6px 6px}
}

/* Quick "message this member" icon on the admin lists. */
.msg-quick{
	display:inline-flex;align-items:center;justify-content:center;
	width:24px;height:24px;border-radius:6px;vertical-align:middle;margin-right:6px;
	color:var(--muted);border:1px solid var(--line);
}
.msg-quick svg{width:14px;height:14px}
.msg-quick:hover{color:var(--accent);border-color:var(--accent)}

/* ── The chat page ───────────────────────────────────────────────────────── */
.chat-head{display:flex;align-items:center;gap:10px;margin-bottom:14px}
.chat-back{display:flex;align-items:center;color:var(--muted)}
.chat-back svg{width:22px;height:22px}
.chat-who{display:flex;flex-direction:column;min-width:0}
.chat-name{font-size:15px;font-weight:600;color:var(--txt)}
.chat-sub{font-size:11px}

.chat-thread{display:flex;flex-direction:column;gap:8px;padding-bottom:8px}
/* A date separator, so a thread spanning weeks is readable. */
.chat-day{display:flex;align-items:center;gap:10px;margin:10px 0 4px;color:var(--muted);font-size:11px}
.chat-day::before,.chat-day::after{content:"";flex:1 1 auto;height:1px;background:var(--line)}

.msg-line{display:flex;flex-direction:column;align-items:flex-start;max-width:78%}
.msg-line.is-mine{align-self:flex-end;align-items:flex-end}
/* ⚠ white-space:pre-line IS WHAT MAKES ESCAPING POSSIBLE HERE. The body is
   written by another member and printed with html_escape; the line breaks
   survive because of this rule. Do not "fix" the formatting with nl2br and raw
   output — that would turn every message into an injection point. */
.msg-bubble{
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:14px 14px 14px 4px;padding:9px 13px;
	font-size:14px;line-height:1.55;color:var(--txt);
	white-space:pre-line;overflow-wrap:anywhere;
}
.msg-line.is-mine .msg-bubble{
	background:var(--accent);border-color:var(--accent);color:var(--vv-on-gold);
	border-radius:14px 14px 4px 14px;
}
.msg-meta{font-size:10px;color:var(--muted);margin:2px 4px 0}

/* ── The composer bar ──────────────────────────────────────────────────────
   ONE bar, used by the private chat and the public board. It was two
   near-identical rule sets; they had already started to differ.

   ⚠ FIXED, NOT STICKY. Sticky pins it only once the content is long enough to
   scroll — on a board with three messages it sat wherever the content ended,
   floating in the middle of the screen with dead space beneath. Fixed puts it
   where a chat keeps it: immediately above the tab bar, always.

   ⚠ The offset is the SHARED tab-bar height, never a hand-measured number —
   that mistake already cost two attempts here. */
.composebar{
	position:fixed;left:0;right:0;z-index:15;margin:0;
	bottom:calc(var(--vv-tabbar-h) + env(safe-area-inset-bottom,0px));
	background:var(--bg);border-top:1px solid var(--line);
	padding:9px 0 11px;
}
/* Full-width background so nothing shows through behind it; the CONTENT lines up
   with .wrap, whose geometry these two numbers mirror. */
.composebar-inner{max-width:1000px;margin:0 auto;padding:0 16px}
.compose-row{display:flex;align-items:flex-end;gap:8px}
/* The base textarea rule above supplies the surface, border and font; this only
   overrides what a composer needs differently — it must not restate the colours,
   or the two would drift the next time the theme changed.
   ⚠ resize:none because the box grows itself (vxComposer); a drag handle on a
   fixed bar fights the auto-grow and can push the send button off screen. */
/* ⚠ The line-height and padding are chosen so TWO lines fit inside
   --vv-compose-min exactly. A pixel over and the field shows a scrollbar track
   down its right edge while empty, which reads as a stray border. */
.compose-row textarea{
	flex:1 1 auto;margin:0;resize:none;overflow-y:auto;
	height:var(--vv-compose-min);min-height:var(--vv-compose-min);max-height:160px;
	padding:10px 14px;line-height:1.45;border-radius:12px;
}
/* ⚠ A SQUARE, sized explicitly rather than left to .btn's padding. It was 42px
   against a 70px field and read as hanging below it — .btn-primary carries a
   glow, which exaggerated the few pixels of difference. It stays one row tall
   against a two-row field and aligns to its bottom edge, which is where a send
   button belongs. */
.compose-row .btn{
	flex:0 0 auto;padding:0;margin:0;border-radius:12px;
	width:var(--vv-compose-h);height:var(--vv-compose-h);
	display:flex;align-items:center;justify-content:center;
	/* ⚠ CENTRED against the field, not sat on its bottom edge. Beside a two-row
	   box the bottom-aligned square read as hanging low; centred it stays level
	   with the field whatever height the field grows to. align-self, because the
	   row itself aligns to flex-end for everything else in it. */
	align-self:center;
}
/* ⚠⚠ NO GLOW ON THIS ONE. .btn-primary carries `box-shadow:0 8px 22px` — a halo
   offset EIGHT PIXELS DOWNWARD. Against a full-width call-to-action that reads
   as depth; against a 46px square sitting beside a 66px field it reads as the
   button hanging below the field, which is exactly how it was reported. The
   button was aligned correctly the whole time; the shadow was not. */
.compose-row .btn-primary{box-shadow:none}
.compose-row .btn svg{width:18px;height:18px;display:block}
/* ⚠ The lock has to be VISIBLE, or a member whose message is in flight just
   sees a button that stopped working. Not `cursor:not-allowed` — this is a
   second, not a refusal. */
.compose-row .btn.is-sending{opacity:.5;filter:none;cursor:progress}
.compose-row .btn[disabled]{pointer-events:none}

@media (max-width:560px){
	.msg-line{max-width:88%}
	.msg-row{padding:12px 12px;gap:10px}
}

/* ── Admin tools grid (settings page) ───────────────────────────────────────
   The five standalone tools below the settings form — password reset, manual
   activation, payout unbind, Telegram link, manual send — two per row on a
   desktop. .grid-2 already gives one column under 760px, so phones are
   unchanged. */
.admin-tools{align-items:start;margin-top:4px}
/* ⚠ Grid items default to stretch, which would pull every panel to the height
   of the tallest in its row and leave a slab of empty card under the short one.
   align-items:start above lets each keep its own height; min-width:0 stops a
   long unbroken value (an account number, a URL) forcing the column wider than
   its share and pushing the second one off screen. */
.admin-tools > div{min-width:0}
/* Each tool owns its heading, so the first one must not carry the big top
   margin an <h2> uses when it follows body copy. */
.admin-tools > div > h2{margin-top:0}
.admin-tools .panel{max-width:none}
/* Long account numbers and URLs inside a narrowed column. */
.admin-tools .kv .v{overflow-wrap:anywhere}

/* ── Daily activity table (dashboard) ───────────────────────────────────────
   The window selector sits on its own row above the table rather than beside
   the heading, so it does not compete with the section label and still has
   somewhere to go on a phone. */
.dailybar{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:12px}
.dailybar .perpage{margin:0}

/* Figures are read down the column, so they get the tabular face and align
   right — the same treatment every other money column in the app has. */
.table.daily td{font-variant-numeric:tabular-nums}

/* Today is the row an administrator looks at first and the only one still
   filling. A faint wash, not a border, so it does not read as an alert. */
.table.daily tr.row-today td{background:rgba(206,172,87,0.07)}

/* The totals row must not scroll away with the body on a narrow screen, and
   must read as a summary rather than one more day. */
.table.daily tfoot .daily-total td{
	border-top:2px solid var(--line);
	background:var(--vv-raised,var(--card));
	position:sticky;bottom:0;
}

/* ── Solvency & runway (viewadmin_forecast) ────────────────────────────────
   ⚠ CSS bars, not a charting library. This page has to render from the
   stylesheet alone — an analysis screen that needs a CDN is one that shows
   nothing on the day the CDN is blocked, which is exactly the sort of day
   somebody opens it. */
.fc-curve{margin:14px 0 18px}
.fc-curve-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
	font-size:13px;font-weight:600;margin-bottom:6px}
.fc-bars{
	display:flex;align-items:flex-end;gap:1px;height:110px;
	background:var(--vv-surface);border:1px solid var(--vv-border);
	border-radius:8px;padding:6px;overflow:hidden;
}
.fc-bar{
	flex:1 1 0;min-width:2px;border-radius:1px 1px 0 0;
	background:var(--vv-gold);transition:filter .12s;
}
/* ⚠ A day the platform could not pay is the whole point of the chart — it does
   not share the gold, and it is not a subtle shade of it. */
.fc-bar.is-short{background:var(--vv-negative)}
.fc-bar:hover{filter:brightness(1.3)}
.fc-axis{display:flex;justify-content:space-between;gap:10px;margin-top:4px;
	font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}

.fc-signal{
	border:1px solid var(--vv-border);border-left:3px solid var(--vv-border);
	border-radius:var(--vv-radius);background:var(--vv-surface);
	padding:12px 14px;margin-bottom:8px;
}
.fc-signal.is-critical{border-left-color:var(--vv-negative)}
.fc-signal.is-warn{border-left-color:#EAB308}
.fc-signal.is-ok{border-left-color:var(--vv-positive)}
.fc-signal-head{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:14px}
