:root {
  --bg: #1e1f24;
  --bg-elevated: #26272e;
  --bg-elevated-2: #2f3038;
  --bg-elevated-3: #34353e;
  --border: #3a3b44;
  --text: #e7e8ea;
  --text-dim: #9a9ba3;
  --accent: #5865f2;
  --accent-hover: #4752c4;
  --good: #57f287;
  --bad: #ed4245;
  --warn: #fee75c;
  /* Same two extra gradient stops home.css defines locally for the landing page - promoted here
     (not just left landing-page-only) because the dashboard's command bar (see the "Command bar"
     section below) reuses the exact teal/fuchsia pairing to visually tell its two panes apart and
     to match the plan-plus badge's existing gradient, so the dashboard now needs them too. */
  --accent-2: #5eeadf;
  --accent-3: #eb459e;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

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

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 24px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}
.topbar .brand { font-weight: 700; font-size: 18px; color: var(--text); }
.topbar .brand span { color: var(--accent); }
.topbar .brand-logo { width: 24px; height: 24px; border-radius: 50%; vertical-align: middle; margin-right: 8px; object-fit: cover; }
.topbar .brand-mark { width: 22px; height: 22px; vertical-align: middle; margin-right: 8px; object-fit: contain; }
.topbar .brand-sub { font-size: 11px; font-weight: 400; color: var(--text-dim); margin-top: 2px; }

/* Language switcher (verify.ejs/verify-interstitial.ejs only, for now - see i18n.ts) - sits at the
   far right of the topbar via the existing `justify-content: space-between` above, so it needs no
   extra positioning of its own. Styled to match the other <select>s on the page (same border/bg
   tokens) but sized down since it only ever holds short language names, not a form field. */
.lang-switcher select {
  width: auto;
  padding: 6px 10px;
  font-size: 13px;
  background: var(--bg-elevated-2);
  border: 1px solid #3a3b44;
  color: var(--text);
  border-radius: 6px;
}
/* Visually hidden but still reachable by screen readers - the <select>'s own visible options
   already convey what it's for sighted users, so a redundant visible label would just be clutter
   next to a compact topbar control. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.container {
  max-width: 880px;
  margin: 0 auto;
  padding: 32px 20px 80px;
}

/* Dashboard settings page only (dashboard-settings.ejs) - wider than the shared .container above
   (used by verify.ejs/verify-interstitial.ejs, which stay at 880px) so the command-bar and
   2-column settings-grid actually have room to breathe on a real monitor instead of leaving big
   empty margins either side, per Kornel: "poszerzyć, bo aktualnie nawet szerokości całej strony
   nie zajmuje". */
.container.dashboard-container {
  max-width: 1320px;
}

/* Dashboard-only ambient background glow, echoing home.css's .hero-glow so the settings page reads
   as the same product as the marketing site instead of a plain gray admin panel. Fixed (not
   absolute) and behind everything (z-index below .topbar/.container's own stacking), so it sits
   once behind the whole scrollable page rather than repeating per-section. Doesn't intercept clicks. */
body.dashboard-body {
  position: relative;
}
body.dashboard-body::before {
  content: "";
  position: fixed;
  inset: -10% -10% auto -10%;
  height: 640px;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(520px 400px at 15% 10%, rgba(88, 101, 242, 0.16), transparent 70%),
    radial-gradient(460px 360px at 85% 0%, rgba(94, 234, 212, 0.1), transparent 70%),
    radial-gradient(420px 340px at 50% 30%, rgba(235, 69, 158, 0.07), transparent 70%);
  filter: blur(10px);
}
@media (prefers-reduced-motion: no-preference) {
  body.dashboard-body::before { animation: heroDriftDash 26s ease-in-out infinite alternate; }
}
@keyframes heroDriftDash {
  0% { transform: translate3d(0, 0, 0) scale(1); }
  100% { transform: translate3d(2%, 2%, 0) scale(1.06); }
}
body.dashboard-body .topbar,
body.dashboard-body .container { position: relative; z-index: 1; }

/* Wraps the main .container plus the two optional ad rails (verify.ejs only) so all three sit
   side by side. justify-content:center keeps everything centered as one unit whether or not the
   rails are present, so .container doesn't need its own auto margins in here (overridden below). */
.page-body {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 20px;
}
.page-body > .container { margin: 0; }

/* Stacked skyscraper-style ad slots down the left/right margins. Hidden below the width where
   a 300px rail on each side would start squeezing the actual verification content — narrower
   visitors get the top .ad-banner-top slot instead (see below), not these. */
.ad-rail {
  display: none;
  flex-direction: column;
  gap: 20px;
  width: 300px;
  flex-shrink: 0;
  position: sticky;
  top: 20px;
}
@media (min-width: 1360px) {
  .ad-rail { display: flex; }
}

.ad-slot {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 16px;
  overflow: hidden;
  margin-bottom: 20px;
  min-height: 0;
}
.ad-slot:empty { display: none; }
.ad-rail .ad-slot { margin-bottom: 0; }

/* Every ad slot is a sandboxed iframe (see src/lib/adSandbox.ts) so a misbehaving ad network
   can't hijack clicks anywhere else on the page - this just makes the iframe itself fill and
   blend into its slot. Real height comes from public/js/verify-ads.js once the ad reports its
   own size; min-height keeps the slot from collapsing to nothing before that first message. */
.ad-iframe {
  width: 100%;
  min-height: 50px;
  border: 0;
  display: block;
}

/* The top banner is the fallback for visitors who won't see the side rails (anyone below the
   1360px breakpoint above) — shown only there, hidden once the rails take over. */
@media (min-width: 1360px) {
  .ad-banner-top { display: none; }
}

/* Sandwiches the Verified/Not verified card (see verify.ejs) - unlike .ad-banner-top above,
   these are not a responsive fallback for the rails, so they show at every screen size. */
.ad-slot-result { margin: 0 0 20px; }

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 20px;
  transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
/* Dashboard-only hover lift, echoing home.css's .feature-card treatment - not applied on
   verify.ejs/verify-interstitial.ejs (plain .card there, untouched) since a visitor filling out a
   verification form has no reason for its cards to feel "clickable". */
body.dashboard-body .card:hover {
  transform: translateY(-2px);
  border-color: rgba(88, 101, 242, 0.35);
  box-shadow: 0 16px 32px -20px rgba(0, 0, 0, 0.55);
}

/* Collapsible cards (see dashboard-settings.ejs) - every settings section below the guild header
   is a <details class="card collapsible"> instead of a plain <div class="card">, closed by
   default so the whole panel reads as a scannable list of section names instead of one long wall
   of every field from every section at once. A section renders with the `open` attribute
   server-side instead when it has something that actually needs the admin's attention right now
   (an active "warn" badge - role hierarchy, a missing API key, captcha not configured - or a
   pending manual review), so nothing important gets missed behind a closed section. Deliberately
   NOT persisted anywhere (no localStorage) - every page load starts from this same server-decided
   state, so a fixed warning's card goes back to closed on its own next reload, no stale "still
   open" section left behind once the underlying problem is gone. */
.card.collapsible { padding: 0; overflow: hidden; }
.card.collapsible > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 24px;
  cursor: pointer;
  list-style: none;
  transition: background 0.15s ease;
}
.card.collapsible > summary:hover { background: rgba(255, 255, 255, 0.02); }
.card.collapsible > summary::-webkit-details-marker { display: none; }
.card.collapsible > summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-dim);
  transition: transform 0.2s ease;
  flex-shrink: 0;
}
.card.collapsible[open] > summary::after { transform: rotate(180deg); }
.card.collapsible > summary h1,
.card.collapsible > summary h2 { margin: 0; }

/* Smooth expand/collapse animation for <details>, per Kornel: "kafelki miały animacje
   rozwijania" - a plain <details> snaps open/closed instantly with no way to transition its
   height (auto <-> 0 isn't animatable). The fix is a CSS-grid trick: .card-body's *rows*
   (grid-template-rows) animate between 0fr and 1fr, which IS transitionable, and the fr unit
   naturally settles at the content's real height with no JS measuring the pixel height itself.
   min-height:0 on the inner wrapper is required for the 0fr collapsed state to actually reach
   zero (grid items default to min-height:auto, which would otherwise floor it at content height).
   Falls back to the old instant show/hide under prefers-reduced-motion. */
.card.collapsible > .card-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s ease;
}
.card.collapsible[open] > .card-body {
  grid-template-rows: 1fr;
}
.card.collapsible > .card-body > .card-body-inner {
  min-height: 0;
  overflow: hidden;
  padding: 4px 24px 24px;
  border-top: 1px solid var(--border);
}
@media (prefers-reduced-motion: reduce) {
  .card.collapsible > .card-body { transition: none; }
}

/* The two sub-sections inside the combined "Verification rules" card (Available checks +
   Verification requirements) - a lighter divider than a full nested .card, since that card is
   already nested one level inside the outer collapsible card. */
.rule-builder-subsection h3 { font-size: 15px; margin: 0 0 10px; }
.rule-builder-subsection + .rule-builder-subsection {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

h1 { font-size: 26px; margin: 0 0 8px; }
h2 { font-size: 18px; margin: 0 0 14px; }
p.muted, .muted { color: var(--text-dim); }

.btn {
  display: inline-block;
  background: var(--accent);
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.btn:hover { background: var(--accent-hover); text-decoration: none; }
/* Secondary buttons (e.g. "Sign in with Steam/Twitch") pick up the accent color as an outline
   instead of a flat dark background, so a server's custom branding color actually shows up on
   the verification page's main call-to-action buttons, not just the topbar logo. */
.btn.secondary { background: var(--bg-elevated-2); border: 1px solid var(--accent); color: var(--accent); }
.btn.secondary:hover { background: var(--accent); color: white; text-decoration: none; }
.btn.danger { background: var(--bad); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* .btn's own `display: inline-block` (an author rule) otherwise beats the browser's built-in
   `[hidden] { display: none }` default (a lower-priority user-agent rule) - without this, a
   <button hidden class="btn ..."> (e.g. altMapGraph.js's "Show more" button while there's nothing
   more to show) stays visibly rendered as an empty pill instead of actually hiding. */
.btn[hidden] { display: none; }

.badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  font-style: normal; /* never italicized even inside .field-hint's italic text (e.g. the "Coming soon" badge under a hint line) */
}
.badge.ok { background: rgba(87,242,135,0.15); color: var(--good); }
.badge.bad { background: rgba(237,66,69,0.15); color: var(--bad); }
.badge.warn { background: rgba(254,231,92,0.15); color: var(--warn); }

/* Plan tier badges: free is neutral, plus gets a bit of premium sparkle to make the
   upgrade-locked state visually distinct from an ordinary warning. */
.badge.plan-free { background: rgba(255,255,255,0.06); color: var(--text-dim); }
.badge.plan-plus { background: linear-gradient(135deg, rgba(94,234,212,0.18), rgba(88,101,242,0.18)); color: #5eeadf; border: 1px solid rgba(94,234,212,0.35); }

.step {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.step:last-child { border-bottom: none; }

/* Speech-bubble style callout under a .step (e.g. the Steam privacy hint on verify.ejs) - an
   outlined card with a small triangular "tail" pointing back up at the row it's commenting on,
   instead of a plain line of muted text that visually blends into the paragraph above it. Only
   the border/tail are tinted (warn-colored here); the fill stays the normal card background so it
   reads as "a note attached to this step", not an error/warning state of the step itself. */
.step-callout {
  position: relative;
  margin: -4px 0 14px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--bg-elevated-2);
  border: 1.5px solid rgba(254, 231, 92, 0.45);
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-dim);
  display: flex;
  gap: 9px;
  align-items: flex-start;
}
.step-callout::before {
  content: "";
  position: absolute;
  top: -7px;
  right: 28px;
  width: 12px;
  height: 12px;
  background: var(--bg-elevated-2);
  border-left: 1.5px solid rgba(254, 231, 92, 0.45);
  border-top: 1.5px solid rgba(254, 231, 92, 0.45);
  transform: rotate(45deg);
}
.step-callout .step-callout-icon { flex-shrink: 0; font-size: 15px; line-height: 1.4; }

/* The "what do I actually need to do here" requirements list on the verify page (verify.ejs),
   built from describeRuleTreeStructured() in ruleEngine.ts. Nested <ul>s mirror the rule tree's
   own grouping (an "All of" / "At least N of" group's sub-items are visually indented under it),
   the same idea as the dashboard rule builder's tree lines, just in plain read-only list form.
   Markup shape per level: <li>text</li> for a plain requirement, or <li>header text</li><li><ul>
   ...children...</ul></li> for a group - the nested <ul> sits inside its OWN <li> (a sibling of
   the header's <li>, not a child of it), which is why the header-detection rule below has to
   reach for ":has(+ li > ul)" rather than the simpler ":has(+ ul)". */
.requirements-list {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
}
.requirements-list.requirements-list-depth-0 { margin-bottom: 20px; }
.requirements-list li > ul.requirements-list {
  margin: 4px 0 8px 10px;
  padding-left: 16px;
  border-left: 2px solid var(--border);
}
.requirements-list li {
  list-style: none;
  padding: 5px 0 5px 20px;
  position: relative;
  color: var(--text);
  font-size: 14px;
}
/* Custom bullet instead of the browser default marker, since list-style:none on the <li> itself
   (rather than just the parent <ul>) is what's needed to reliably suppress it across browsers. */
.requirements-list li::before {
  content: "\2022";
  position: absolute;
  left: 4px;
  color: var(--text-dim);
}
/* A <li> that's immediately followed by a sibling <li> whose only content is a nested <ul> is a
   group header ("All of:" / "At least 3 of:") rather than a plain requirement - style it as a
   label instead of a leaf item, and swap its bullet for a small caret to read as a group opener. */
.requirements-list li:has(+ li > ul) {
  font-weight: 600;
  color: var(--accent);
}
.requirements-list li:has(+ li > ul)::before {
  content: "\25B8";
  color: var(--accent);
  font-size: 12px;
  top: 6px;
}
/* The <li> that only wraps a nested <ul> (no text of its own) shouldn't get a bullet or padding -
   it's pure structure, not a requirement in its own right. */
.requirements-list > li:has(> ul) {
  padding: 0;
}
.requirements-list > li:has(> ul)::before {
  content: none;
}
/* Group header lines get their own color per logic type ("All of" purple, "At least N of" yellow,
   "Must NOT" red), matching the color language already used in the rule builder - overrides the
   generic ":has(+ li > ul)" accent-purple rule above (same specificity by class count, but that
   selector's ":has()" pseudo-class counts as an extra class, so these need the ".requirements-list
   li" prefix too to actually win instead of just losing quietly to source order). The ::before
   caret is recolored the same way via currentColor so it always matches its own header's line. */
.requirements-list li.requirements-group-and,
.requirements-list li.requirements-group-and::before { color: #b39ddb; }
.requirements-list li.requirements-group-or,
.requirements-list li.requirements-group-or::before,
.requirements-list li.requirements-group-at_least,
.requirements-list li.requirements-group-at_least::before { color: #e6c86e; }
.requirements-list li.requirements-group-not,
.requirements-list li.requirements-group-not::before { color: #e08585; }

/* A connector line sits BETWEEN two sibling requirements at the same depth ("OR"/"AND"/"N OF") -
   it isn't a requirement of its own, so it renders as a small centered pill instead of a bulleted
   list item, mirroring the connector pills already shown between sibling nodes in the rule
   builder's tree view (see CONNECTOR_LABEL in rulebuilder.js). */
.requirements-list li.requirements-connector {
  padding: 2px 0 2px 20px;
  list-style: none;
}
.requirements-list li.requirements-connector::before {
  content: none;
}
.requirements-connector-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(255,255,255,0.08);
}
.requirements-connector-or .requirements-connector-badge {
  color: #e6c86e;
  background: rgba(230,200,110,0.14);
}
.requirements-connector-and .requirements-connector-badge {
  color: #b39ddb;
  background: rgba(179,157,219,0.14);
}
.requirements-connector-at-least .requirements-connector-badge {
  color: #e6c86e;
  background: rgba(230,200,110,0.14);
}

/* Live ✅/❌/○ state on a plain leaf requirement line, shown as soon as the visitor's current
   connections make it knowable - see RequirementLine.passed's own doc comment in ruleEngine.ts for
   the three-state reasoning (why "platform not connected yet" is its own neutral state, not lumped
   in with an actual failure) and for why this is safe to reveal before submitting (same facts/
   evaluator as the real pass/fail decision, so it can never disagree with what submit then does).
   Only ever applied to a leaf line - a group header keeps its own AND/OR/NOT coloring untouched. */
.requirements-list li.requirement-unknown { color: var(--text-dim); }
.requirements-list li.requirement-met { color: var(--good); }
.requirements-list li.requirement-unmet { color: var(--bad); }
.requirement-mark {
  display: inline-block;
  width: 1em;
  text-align: center;
  font-weight: 700;
}
.requirement-unknown .requirement-mark { color: var(--text-dim); }
.requirement-met .requirement-mark { color: var(--good); }
.requirement-unmet .requirement-mark { color: var(--bad); }
/* The same per-check privacy-ambiguity hint evalCheck() attaches to a failed Steam count/playtime
   check post-submit (e.g. "...or your Game details privacy setting may be hiding it") - shown
   right under the live requirement line too now, so a visitor sees the actionable "go check your
   Steam privacy settings" hint immediately instead of only after wasting a submit on it. */
.requirement-fail-detail {
  margin: 2px 0 2px 1.4em;
  font-size: 12.5px;
  color: var(--text-dim);
}

label { display: block; font-size: 13px; color: var(--text-dim); margin-bottom: 6px; margin-top: 16px; }
/* Explanatory text placed AFTER a field (e.g. "Added alongside Unverified whenever someone fails
   verification." under the Failed-verification role picker) - was plain <p class="muted"> before,
   which has no font-size of its own and so renders at the page's default (larger than label's
   13px above it) - visually backwards, since the field's own name should read as more prominent
   than a secondary explanation of it, and the size mismatch made it unclear which line was the
   field's label and which was commentary on it (see the 2026-09 UI feedback/screenshot). Sized
   smaller than label and italicized so a hint reads unmistakably as "additional detail", never as
   a second label for the field below it. */
.field-hint { color: var(--text-dim); font-size: 12px; font-style: italic; margin: 6px 0 0; }
input[type=text], input[type=number], select, textarea {
  width: 100%;
  background: var(--bg-elevated-2);
  border: 1px solid #3a3b44;
  color: var(--text);
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input[type=text]:hover, input[type=number]:hover, select:hover, textarea:hover {
  border-color: #4a4c58;
}
input[type=text]:focus, input[type=number]:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(88,101,242,0.15);
}
textarea { min-height: 70px; font-family: inherit; }
input[type=checkbox] { width: 16px; height: 16px; vertical-align: middle; margin-right: 8px; }

/* Small "?" affordance that expands a longer explanation on hover/focus, so a setting's caption
   can stay a single short sentence instead of a crowded paragraph (see the muted captions in
   dashboard-settings.ejs). Pure CSS (no JS) via the title-less attr(data-tooltip) trick, so it
   also works for keyboard users tabbing onto it (:focus), not just mouse hover. */
.help-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 700;
  font-style: normal; /* never italicized even inside .field-hint's italic text */
  line-height: 1;
  cursor: help;
  position: relative;
  flex-shrink: 0;
}
.help-icon:hover, .help-icon:focus {
  background: rgba(88,101,242,0.3);
  color: var(--text);
  outline: none;
}
.help-icon::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 280px;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.45;
  padding: 8px 10px;
  border-radius: 8px;
  box-shadow: 0 8px 20px rgba(0,0,0,0.4);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
  z-index: 30;
  pointer-events: none;
}
.help-icon:hover::after, .help-icon:focus::after {
  opacity: 1;
  visibility: visible;
}

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); }
th { color: var(--text-dim); font-weight: 600; }

/* Collapsible per-platform sections on the docs page (Discord/Steam/Twitch checks lists). */
.checks-platform {
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 4px 16px;
  margin-bottom: 12px;
}
.checks-platform summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0;
  cursor: pointer;
  font-weight: 600;
  list-style: none; /* hide the default marker so only our own caret shows */
}
.checks-platform summary::-webkit-details-marker { display: none; }
.checks-platform summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-dim);
  transition: transform 0.15s ease;
}
.checks-platform[open] summary::after { transform: rotate(180deg); }
.checks-platform table { margin: 4px 0 14px; }
.checks-platform > p:last-child { margin-bottom: 14px; }

.guild-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.guild-row:last-child { border-bottom: none; }

/* --- Rule builder --- */
/* Visual hierarchy: the root group (the whole tree's outer boundary) is the one proper "box" -
   a solid border like any other card element. Every group nested inside it is NOT its own
   separate box anymore (that used to render as an identical dashed rectangle at every depth,
   stacking visually with the .indent line right next to it into what read as duplicate/
   overlapping frames) - instead it's marked by a colored left accent border "hanging" off the
   tree line, plus a faint background tint. Because each subgroup's own translucent white tint
   sits on top of its parent's already-tinted background, nesting depth naturally reads as
   "gets a little lighter with each level" without tracking a depth number in JS at all. */
.rule-group {
  border-radius: 10px;
  padding: 14px;
  margin: 10px 0;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.rule-group-root {
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.012);
}
.rule-group-nested {
  border: none;
  border-left: 4px solid var(--accent);
  background: rgba(255,255,255,0.035);
  border-radius: 4px 10px 10px 4px;
}
.rule-group-header { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
/* The group header phrased as a sentence ("Match ALL of the following rules") instead of a static
   label + separate dropdown - see OP_WORDS/OP_TAIL in rulebuilder.js. The <select> and the
   AT_LEAST count <input> are styled to blend into the sentence's flow (transparent background,
   bold accent-colored text, no visible box until hovered/focused) rather than reading as a
   distinct form control sitting next to unrelated text. */
.rule-group-sentence {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-weight: 600;
}
.rule-op-select-inline {
  width: auto;
  display: inline-block;
  background: transparent;
  border: 1px solid transparent;
  color: var(--accent);
  font-weight: 700;
  font-size: 14px;
  padding: 2px 6px;
  border-radius: 6px;
  cursor: pointer;
}
.rule-op-select-inline:hover { background: rgba(88,101,242,0.12); }
.rule-op-select-inline:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(88,101,242,0.15); }
.rule-at-least-input {
  width: 56px !important;
  min-width: 0 !important;
  display: inline-block;
  text-align: center;
  padding: 4px 6px;
  font-weight: 700;
  color: var(--accent);
}
/* One AND/OR/AT-LEAST-N pill between two consecutive children of the same group, so the logic
   reads inline (condition, AND, condition, OR, group) instead of only being stated once up in the
   group's own header - easy to lose track of once you've scrolled past it. Pulled left with a
   negative margin so the pill visually sits "on" the .indent tree line rather than floating in
   the middle of the row. */
.rule-connector { display: flex; margin: 2px 0 2px -21px; }
.rule-connector-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.rule-connector-and .rule-connector-badge { background: rgba(129,102,255,0.22); color: #c3b3ff; border: 1px solid rgba(129,102,255,0.4); }
.rule-connector-or .rule-connector-badge { background: rgba(255,159,67,0.18); color: #ffb066; border: 1px solid rgba(255,159,67,0.4); }
.rule-connector-at_least .rule-connector-badge { background: rgba(94,234,212,0.16); color: #5eeadf; border: 1px solid rgba(94,234,212,0.35); }

.rule-check {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  margin: 6px 0;
}
.rule-check select, .rule-check input { width: auto; min-width: 120px; }
.rule-check-label { font-weight: 600; white-space: nowrap; }
/* The icon+label span that opens the "swap to a different check type" picker (rulebuilder.js,
   data-action="start-pick-check-type") on click - a separate element from the drag-handle span
   next to it, so clicking here to swap the check's type never starts a drag, and dragging the row
   via its handle never opens the picker. Underline-on-hover is the only affordance (no button
   chrome) since this sits inline with the row's other text and shouldn't visually compete with the
   actual buttons (Remove) beside it. */
.rule-check-swappable {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  border-radius: 4px;
  padding: 2px 4px;
  margin: -2px -4px;
}
.rule-check-swappable:hover {
  background: rgba(88,101,242,0.12);
}
.rule-check-swappable:hover .rule-check-label {
  text-decoration: underline;
  text-decoration-color: var(--accent);
}
.check-type-picker {
  font-weight: 600;
  min-width: 220px !important;
}
/* The check's own fields sit right after its label on the same row (see renderNodeCheckFields in
   rulebuilder.js - each field is now a single placeholder-labeled <input>, no separate caption
   line above it), only wrapping onto their own line if the row is genuinely too narrow. */
.rule-check-fields { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rule-check-fields input { min-width: 100px; }
.rule-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; align-items: center; }
.small-btn {
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12px;
  cursor: pointer;
}
.small-btn:hover { border-color: var(--accent); }

/* Remove / Remove group: an icon-only pill (trash icon supplied by rulebuilder.js) on a soft red
   tint, pushed to the far right of its row via margin-left:auto, rather than plain red text
   sitting flush against the row's edge with no visual weight of its own. */
.remove-btn {
  color: var(--bad);
  background: rgba(237,66,69,0.1);
  border: none;
  border-radius: 8px;
  width: 30px;
  height: 30px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
}
.remove-btn svg { display: block; }
.remove-btn:hover { background: rgba(237,66,69,0.22); border-color: transparent; }

/* Approve/Deny on the "Awaiting manual review" table (dashboard-settings.ejs) - reuses .small-btn's
   base sizing/font but adds a colored border+tint per action (green for approve, red for deny)
   instead of .small-btn's neutral border, so the two outcomes read as visually distinct at a
   glance rather than looking like two identical grey buttons. Both share review-decision-btn for
   sizing so they line up at equal width side by side (min-width, not a fixed width, so longer
   translated button labels in other locales still fit without wrapping oddly) - per Kornel's
   request (2026-09) that the two buttons "line up" rather than Deny (previously .remove-btn, an
   icon-sized 30px square meant for the rule-builder's trash icon, not text) being narrower than
   Approve. */
.review-decision-btn { min-width: 72px; text-align: center; }
.review-approve-btn { color: var(--good); background: rgba(87,242,135,0.1); border: 1px solid rgba(87,242,135,0.4); }
.review-approve-btn:hover { background: rgba(87,242,135,0.2); border-color: var(--good); }
.review-deny-btn { color: var(--bad); background: rgba(237,66,69,0.1); border: 1px solid rgba(237,66,69,0.4); }
.review-deny-btn:hover { background: rgba(237,66,69,0.2); border-color: var(--bad); }

/* Vertical "tree" line connecting a group's children, so nested AND/OR/NOT logic reads as a tree
   at a glance instead of same-looking indentation with no visible connection. Also the drop
   target for drag&drop (see .drag-over below) - the indent itself is what a check/group is
   dropped "into" (always appended as that group's last child). */
.indent {
  margin-left: 10px;
  padding-left: 16px;
  border-left: 2px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.indent.drag-over {
  background: rgba(88,101,242,0.1);
  border-left-color: var(--accent);
}
.empty-group-hint {
  color: var(--text-dim);
  font-size: 12px;
  font-style: italic;
  padding: 10px 0;
}

/* "+ Add rule" - the primary way to put a check into a group (see rulebuilder.js), so it's
   styled as a filled accent pill rather than the plainer dashed-outline treatment "+ Add nested
   group" gets below: adding a plain check is the far more common action, and this is the only
   button that does it now that the platform tiles above are reference-only. */
.add-condition-btn {
  border-radius: 8px !important;
  background: rgba(88,101,242,0.14);
  color: #b3bbff;
  font-weight: 600;
  padding: 6px 14px;
}
.add-condition-btn:hover { background: rgba(88,101,242,0.26); border-color: transparent; }

.add-group-btn {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--text-dim);
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 13px;
}
.add-group-btn:hover { border-color: var(--accent); color: var(--accent); }

.check-option-icon { display: inline-flex; width: 20px; height: 20px; flex-shrink: 0; }
.check-option-icon svg, .check-option-icon img { display: block; width: 100%; height: 100%; object-fit: contain; }

/* Drag handle + dragged-element feedback, shared by both draggable kinds (a single check, or a
   whole group header) - see rulebuilder.js's dragstart/dragend handlers. */
.drag-handle {
  display: inline-flex;
  color: var(--text-dim);
  cursor: grab;
  flex-shrink: 0;
}
.drag-handle:active { cursor: grabbing; }
.rule-check.dragging, .rule-group.dragging { opacity: 0.4; }
[draggable="true"] { -webkit-user-drag: element; }

/* --- Platform tiles ("Available checks") --- */
.platform-tile {
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-bottom: 12px;
  background: var(--bg-elevated-2);
  overflow: hidden;
}
.platform-tile summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  cursor: pointer;
  font-weight: 600;
  font-size: 15px;
  list-style: none;
}
.platform-tile summary::-webkit-details-marker { display: none; }
.platform-tile summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-dim);
  transition: transform 0.15s ease;
}
.platform-tile[open] summary::after { transform: rotate(180deg); }
.platform-tile-body { padding: 4px 16px 12px; border-top: 1px solid var(--border); }

.tile-check-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.tile-check-row:last-child { border-bottom: none; }
.tile-check-row.locked { opacity: 0.55; }
.tile-check-row-main { display: flex; align-items: center; gap: 8px; min-width: 200px; flex: 1; }
.tile-check-label { font-size: 14px; }

/* Analytics activity-over-time chart (analyticsChart.js) - the 7/30/90-day range toggle, color
   legend, and the SVG plot area it draws into. */
.chart-range-toggle { display: flex; gap: 6px; margin-bottom: 10px; }
.chart-range-btn {
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.chart-range-btn:hover { color: var(--text); }
.chart-range-btn.active { background: var(--accent); border-color: var(--accent); color: white; }
/* Chart.js renders its own legend/tooltip (see analyticsChart.js's options.plugins.legend) - this
   wrapper just gives the <canvas> a fixed height, since Chart.js with maintainAspectRatio:false
   sizes itself to fill its parent rather than picking a sensible height on its own. */
.chart-canvas-wrap { position: relative; height: 280px; margin-bottom: 6px; }
.chart-status { text-align: center; padding: 40px 0; }

/* Alt Account Map (altMapGraph.js) - one grid of small independent per-cluster graphs (see that
   file's own doc comment on why the map is split into clusters instead of one shared canvas) plus
   its summary/status/legend text and the "Show more clusters" pagination button. */
.alt-map-summary { margin-bottom: 10px; font-size: 13px; }
/* auto-fit (not auto-fill) is the deliberate choice here: auto-fill reserves column TRACKS for
   however many 280px-wide columns could fit even when there's only one actual cluster to show,
   which collapses that one cluster's box down to its 280px minimum and leaves the rest of the
   card empty instead of letting it stretch to fill the available width (visible as exactly that
   with a single cluster - see the 2026-09 bug report/screenshot). auto-fit collapses those empty
   tracks instead, so the real cluster(s) present take up the 1fr share on their own. With 2+
   clusters that would otherwise fill a row, the visual result is identical either way. */
.alt-map-clusters { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; }
.alt-map-cluster { border: 1px solid var(--border); border-radius: 8px; background: var(--bg-elevated-2); overflow: hidden; }
.alt-map-cluster-meta { padding: 8px 10px; font-size: 12px; color: var(--text-dim); border-bottom: 1px solid var(--border); }
.alt-map-canvas-wrap { position: relative; width: 100%; }
.alt-map-canvas-wrap canvas { width: 100%; height: 100%; display: block; cursor: default; }
.alt-map-status { text-align: center; padding: 40px 0; }
.alt-map-legend { margin-top: 8px; font-size: 12px; }
.alt-map-more { margin-top: 14px; }

/* =============================================================================================
   COMMAND BAR - approved redesign (see /home/claude/mockup/dashboard-redesign.html, "git! super
   to wygląda, implementuj"). Replaces the old collapsed Analytics/Alt Map <details> sections:
   Plus-plan intel an admin actually logs in to check now sits at the very top of the page,
   visible on load with no expanding required. Ported class-for-class from the approved mockup.
========================================================================================= */
.command-bar {
  display: grid;
  grid-template-columns: 1.1fr 1.4fr;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  margin-bottom: 34px;
  box-shadow: 0 24px 60px -32px rgba(0,0,0,0.6);
}
@media (max-width: 760px) {
  .command-bar { grid-template-columns: 1fr; }
}
.command-pane {
  background: var(--bg-elevated);
  padding: 22px 24px 24px;
  position: relative;
}
.command-pane::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 2px;
}
.pane-altmap::before { background: linear-gradient(90deg, var(--accent-3), transparent 70%); }
.pane-analytics::before { background: linear-gradient(90deg, var(--accent-2), transparent 70%); }

.pane-eyebrow {
  font-size: 11px; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
}
.pane-eyebrow.map-eyebrow { color: var(--accent-3); }
.pane-eyebrow.chart-eyebrow { color: var(--accent-2); }
.pane-title { font-size: 17px; font-weight: 700; margin: 0 0 14px; }

/* Alt Account Map pane: compact cluster list, not the full canvas graph (that stays reachable via
   the "View full map" link, which jumps to the existing full alt-map-graph section further down
   the page) - this command-bar view is the "glance and go" form. */
.cluster-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 10px;
  background: var(--bg-elevated-2);
  margin-bottom: 8px;
  border: 1px solid transparent;
  transition: border-color 0.15s ease;
}
.cluster-row:hover { border-color: var(--accent-3); }
.cluster-avatars { display: flex; flex-shrink: 0; }
.cluster-avatars .av {
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-3));
  border: 2px solid var(--bg-elevated-2);
  margin-left: -10px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #fff;
}
.cluster-avatars .av:first-child { margin-left: 0; }
.cluster-info { flex: 1; min-width: 0; }
.cluster-names { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cluster-meta { font-size: 12px; color: var(--text-dim); margin-top: 1px; }
.cluster-platforms { display: flex; gap: 4px; flex-shrink: 0; }
.plat-dot { width: 8px; height: 8px; border-radius: 50%; }
.plat-dot.steam { background: #66c0f4; }
.plat-dot.twitch { background: #9146ff; }
.plat-dot.riot { background: #d13639; }

.pane-footer-link {
  display: flex; align-items: center; gap: 6px;
  margin-top: 14px; font-size: 13px; font-weight: 600; color: var(--accent-3);
  background: none; border: none; cursor: pointer; padding: 0; font-family: inherit;
}
.pane-footer-link:hover { text-decoration: underline; }

/* Analytics pane */
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 18px; }
.kpi-tile { background: var(--bg-elevated-2); border-radius: 10px; padding: 10px 12px; }
.kpi-value { font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; }
.kpi-label { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.kpi-tile.good .kpi-value { color: var(--good); }
.kpi-tile.bad .kpi-value { color: var(--bad); }

.range-control {
  display: flex; align-items: center; gap: 12px;
  background: var(--bg-elevated-2);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 14px;
}
.range-control label { font-size: 12px; color: var(--text-dim); font-weight: 600; white-space: nowrap; margin: 0; }
.range-control input[type=range] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  outline: none;
}
.range-control input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent-2);
  border: 3px solid var(--bg-elevated);
  box-shadow: 0 0 0 1px var(--accent-2);
  cursor: pointer;
}
.range-control input[type=range]::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent-2);
  border: 3px solid var(--bg-elevated);
  box-shadow: 0 0 0 1px var(--accent-2);
  cursor: pointer;
}
.range-value {
  font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 700; color: var(--accent-2);
  background: rgba(94,234,212,0.12);
  padding: 3px 10px;
  border-radius: 999px;
  min-width: 58px;
  text-align: center;
}

.mini-chart { height: 120px; display: flex; align-items: flex-end; gap: 3px; }
.mini-chart .bar-group { flex: 1; display: flex; flex-direction: column-reverse; gap: 1px; height: 100%; }
.mini-chart .bar { border-radius: 2px 2px 0 0; }
.mini-chart .bar.entries { background: var(--accent); }
.mini-chart .bar.verified { background: var(--good); }
.mini-chart .bar.alt { background: var(--warn); }
.chart-legend { display: flex; gap: 16px; margin-top: 10px; font-size: 12px; color: var(--text-dim); flex-wrap: wrap; }
.chart-legend span { display: flex; align-items: center; gap: 6px; }
.chart-legend i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }

/* Small collapsed-by-default toggle for the Totals/failure-reasons tables inside the Analytics
   command-bar pane - keeps that pane's resting height in line with the Alt Map pane next to it
   (the chart above already shows the shape of activity; the raw numbers are a "look closer" detail
   someone can expand, not something that should always take up the same amount of vertical space
   as the chart itself). Deliberately lighter-weight than .card.collapsible's big accordion style,
   since this is a subsection inside one pane, not a page-level section of its own. */
.pane-subsection { margin-top: 14px; }
.pane-subsection > summary {
  cursor: pointer;
  list-style: none;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  padding: 8px 0;
  border-top: 1px solid var(--border);
}
.pane-subsection > summary::-webkit-details-marker { display: none; }
.pane-subsection > summary::before { content: "▸ "; }
.pane-subsection[open] > summary::before { content: "▾ "; }
.pane-subsection table { margin-top: 8px; }

/* Free-plan variant: same two-pane layout filled with realistic SAMPLE data (never zeros or an
   empty state), blurred, with a lock overlay on top - see the mockup's own comment for why this
   replaced an earlier text-only upsell banner. Nothing that reads through the blur is a real
   number for this guild. */
.locked-pane { position: relative; }
.locked-pane .pane-content-blur {
  filter: blur(6px);
  pointer-events: none;
  user-select: none;
}
.locked-overlay {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px;
  background: linear-gradient(180deg, rgba(30,31,36,0.35), rgba(30,31,36,0.72));
  text-align: center;
  padding: 20px;
}
.locked-icon {
  width: 44px; height: 44px; border-radius: 12px;
  background: linear-gradient(135deg, var(--accent), var(--accent-3));
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10px 24px -8px rgba(88,101,242,0.6);
}
.locked-title { font-size: 14px; font-weight: 700; margin: 0; }
.locked-sub { font-size: 12.5px; color: var(--text-dim); margin: 0; max-width: 220px; }
.locked-cta {
  margin-top: 4px;
  background: linear-gradient(135deg, var(--accent), #7c3aed);
  color: #fff; border: none; padding: 9px 18px; border-radius: 8px;
  font-size: 13px; font-weight: 700; cursor: pointer;
  box-shadow: 0 8px 20px -8px rgba(88,101,242,0.7);
  text-decoration: none;
  display: inline-block;
}
.locked-cta:hover { text-decoration: none; opacity: 0.92; }

/* =============================================================================================
   SETTINGS GRID - restyles the existing <details class="card collapsible"> sections (unchanged
   markup/behavior - every field, form, and script hook stays exactly as it was) into themed cards
   carrying their own icon + one-line purpose, closer to home.ejs's .feature-card treatment than a
   plain accordion stack.
========================================================================================= */
.section-label {
  font-size: 12px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-dim); margin: 28px 0 14px;
}
/* grid-auto-flow: dense backfills the gap a span-2 card (Rule Builder) would otherwise leave behind
   it when it doesn't fit the remaining column of its row - without it, a 1-column card placed
   right after a span-2 card gets pushed down instead of filling that empty cell. */
.settings-grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-auto-flow: dense; gap: 16px; margin-bottom: 16px; }
@media (max-width: 760px) {
  .settings-grid { grid-template-columns: 1fr; }
}
/* The settings <form> physically wraps most of the cards below (see dashboard-settings.ejs) so
   one submit can save every field across them - display:contents makes the form itself invisible
   to grid layout, so its <details> children still lay out as direct grid items instead of being
   pushed into a single full-width cell. */
.settings-grid > form { display: contents; }
/* The submit button sits directly in the form's (display:contents) children, so without this
   wrapper it becomes a grid item in its own right and stretches to fill an entire 1fr column/row
   cell - a full-width, full-height blue block instead of a normal button. span-2 + block-level
   wrapper keeps it full-width (matching the old layout) but sized to its own content height. */
.settings-save-row { grid-column: span 2; }
@media (max-width: 760px) {
  .settings-save-row { grid-column: span 1; }
}
/* .settings-card decorates the EXISTING <details class="card collapsible"> markup/behavior (see
   that rule's own doc comment above) - it never replaces the accordion mechanics, just adds the
   icon-tile + one-line purpose row that the plain text-only <summary><h2> used to lack. */
.settings-card.span-2 { grid-column: span 2; }
@media (max-width: 760px) {
  .settings-card.span-2 { grid-column: span 1; }
}
/* Thin top accent, matching .command-pane::before's treatment - each card picks the gradient up
   from whichever tint its own icon uses, so the settings grid reads as one family of cards with
   the command bar above it rather than two visually unrelated sections of the same page. */
.settings-card { position: relative; overflow: hidden; }
.settings-card::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--accent), transparent 70%);
}
.settings-card:has(.card-icon.tint-teal)::before { background: linear-gradient(90deg, var(--accent-2), transparent 70%); }
.settings-card:has(.card-icon.tint-fuchsia)::before { background: linear-gradient(90deg, var(--accent-3), transparent 70%); }
.settings-card:has(.card-icon.tint-warn)::before { background: linear-gradient(90deg, var(--warn), transparent 70%); }
.card-head { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
.card-head-text { min-width: 0; }
.card-head h2 { font-size: 15px; margin: 0; font-weight: 700; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.card-icon {
  width: 34px; height: 34px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.card-icon svg { stroke: currentColor; }
.card-icon.tint-blurple { background: rgba(88,101,242,0.16); color: var(--accent); }
.card-icon.tint-teal { background: rgba(94,234,212,0.16); color: var(--accent-2); }
.card-icon.tint-fuchsia { background: rgba(235,69,158,0.16); color: var(--accent-3); }
.card-icon.tint-warn { background: rgba(254,231,92,0.16); color: var(--warn); }
.card-sub { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 0; }

.plan-pill {
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.04em;
  padding: 2px 8px; border-radius: 999px; margin-left: 8px;
  background: linear-gradient(135deg, rgba(94,234,212,0.2), rgba(88,101,242,0.2));
  color: var(--accent-2); border: 1px solid rgba(94,234,212,0.35);
}
