/* Clutch Ping — brand system (fixed, see runbook). Dark only. */
@font-face{font-family:Vazirmatn;src:url(/static/fonts/vazirmatn-regular.woff2)format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:Vazirmatn;src:url(/static/fonts/vazirmatn-bold.woff2)format("woff2");font-weight:700;font-display:swap}
@font-face{font-family:JBMono;src:url(/static/fonts/jbmono-regular.woff2)format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:JBMono;src:url(/static/fonts/jbmono-bold.woff2)format("woff2");font-weight:700;font-display:swap}

:root{
  --cyan-300:#7FE3FA;--cyan-500:#22C9F2;--cyan-700:#0E9BC0;
  /* donator rank hue — one semantic family, light->bright by tier. Kept clear of
     gold (owner) and red (youtuber) so stacked emblems never read as each other. */
  --violet-300:#C4B5FD;--violet-500:#8B5CF6;--violet-700:#6D28D9;
  --navy-900:#0B1220;--navy-800:#121C2E;--navy-700:#1B2942;--navy-600:#2A3B57;
  --text-hi:#FFFFFF;--text-mid:#C7D2E0;--text-low:#8494AB;
  --ok:#3DDC84;--warn:#FFB020;--bad:#FF5A5F;
  /* Plus gold: the deeper amber of the Plus mark, NOT the founder crest's
     #FFD54A. It appears on the mark, the Plus panel/upsell edge, the tile
     outline and exactly ONE .btn-gold per page; never on the .row- classes
     or glowrank. (Never write a glob like ".row-STAR/" in a CSS comment: the
     star-slash closes the comment and the rest of the sentence swallowed the
     --gold-300 declaration below it, which left the gold CTA's hover
     background transparent and the Plus pill's selected text grey.)
     --gold-500 on --navy-800 and --gold-ink on --gold-500 both sit near 8.9:1. */
  --gold-300:#FDD977;--gold-500:#F5B335;--gold-700:#EFA21C;--gold-ink:#2A1A00;

  /* Admin design tokens (redesign-preserve). Cyan is the ONE accent and means
     "interactive or brand" — links, primary buttons, active nav/chip. Data stays
     neutral; state is ok/warn/bad. One radius scale, one spacing scale. */
  --link:var(--cyan-500);--link-hover:var(--cyan-300);
  --surface:var(--navy-800);--surface-alt:var(--navy-900);
  --border:var(--navy-700);--border-soft:var(--navy-600);
  --r-ctl:6px;--r-card:10px;--r-pill:999px;
  --sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:22px;--sp-6:32px;
  --fs-h1:1.4rem;--fs-h2:1rem;--fs-label:.7rem;--fs-body:.86rem;
}
*{box-sizing:border-box;margin:0}
html{scroll-behavior:smooth}
body{background:var(--navy-900);color:var(--text-mid);font-family:Vazirmatn,system-ui,sans-serif;line-height:1.8;font-size:16px}
.mono,code{font-family:JBMono,monospace}
h1,h2,h3{color:var(--text-hi);line-height:1.4}
a{color:var(--cyan-500);text-decoration:none}
main{max-width:1060px;margin:0 auto;padding:0 20px}
main.narrow{max-width:640px;padding-top:48px;padding-bottom:80px}
main.wide{max-width:1200px;padding-top:32px;padding-bottom:80px}
main.center{text-align:center}
::selection{background:rgba(34,201,242,.3);color:#fff}
:focus-visible{outline:2px solid var(--cyan-500);outline-offset:3px;border-radius:4px}

/* staging banner */
.staging-banner{background:var(--warn);color:#231a00;text-align:center;font-weight:700;font-size:.85rem;padding:6px 12px}

/* header */
/* The BAR is sticky and full width so the background covers the viewport; the
   row inside it stays capped. z-index 30 on the documented scale: 5 crest,
   20 admin rail, 40 account dropdown, 50 /terms progress bar, 60 tooltip. The
   account dropdown lives INSIDE the header, so it still paints above page
   content through the header's own stacking context, and the progress bar
   correctly stays on top of the bar. */
.head-bar{position:sticky;top:0;z-index:30;background:var(--navy-900);
  border-bottom:1px solid var(--navy-700)}
.site-head{display:flex;align-items:center;gap:20px;max-width:1060px;margin:0 auto;padding:16px 20px}
/* The links are inline at desktop; the <details> around them is only a wrapper
   until the mobile breakpoint turns it into a menu. */
.nav-menu{display:contents}
.nav-toggle{display:none}
/* The links sit inside a <details> ONLY so the same markup can become a menu on
   mobile. A closed <details> hides its content, so at desktop that hiding has to
   be undone explicitly: modern Chrome uses ::details-content with
   content-visibility, older engines put display:none on the children (already
   overridden by .head-links{display:flex}). Both are covered.
   Found by LOOKING, not measuring: the anchors reported a real bounding rect and
   still painted nothing, the same trap as the blank gift-card screens. */
@media(min-width:821px){
  /* display:contents as well: without it the ::details-content box becomes the
     flex item and .head-links{margin-inline:auto} centres the nav INSIDE that
     shrink-to-fit box, so the links stopped being centred in the header. */
  .nav-menu::details-content{content-visibility:visible;display:contents}
}
/* Anchor targets have to clear the sticky bar or /#how lands behind it. There
   was no scroll-margin anywhere in this file before the header stuck.
   The number budgets for the EXPANDED header (81px), not the condensed one:
   the bar shrinks by 16px as the page scrolls, and because it is sticky and in
   flow that pulls the content up by the same 16px AFTER the anchor jump has
   already placed it. Measured: with 72 here, #how landed 9px behind the bar. */
#how,#leaderboard,#plans{scroll-margin-top:96px}
.head-links{display:flex;align-items:center;gap:22px;margin-inline:auto}
.nav-i{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.head-id{display:flex;align-items:center;gap:12px}
.head-login{color:var(--text-mid);font-size:.95rem;white-space:nowrap}
.head-login:hover{color:var(--cyan-300)}

/* Signed-in chip. The face carries the recognition, so the name is allowed to
   drop away on narrow screens without the control losing its meaning. */
.acct-chip{display:flex;align-items:center;gap:9px;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:999px;padding:5px 14px 5px 6px;
  transition:border-color .2s,background .2s}
.acct-chip:hover{border-color:var(--cyan-700);background:var(--navy-700)}
/* 32px, not 30: /avatar serves a 96px face and 96/32 is exactly 3, so
   nearest-neighbour downscaling keeps the pixel art crisp. At 30px the 3.2
   ratio drops pixels unevenly and the face smears. */
.acct-face{width:32px;height:32px;border-radius:8px;image-rendering:pixelated;
  background:var(--navy-700);flex:none;border:1px solid var(--navy-600)}
.acct-name{font-size:.88rem;color:var(--text-hi);font-weight:700;max-width:11ch;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.brand{display:flex;align-items:center;gap:10px}
/* The v3 mark's tight viewBox is portrait (627x864). A square box would letterbox
   it, so height leads and width follows the aspect. The glyph variant is square
   and inherits the site accent through currentColor. */
.mark{height:48px;width:auto}
.mark-glyph{color:var(--cyan-500)}
/* Held at the flare's bright point instead of pulsing there. Animating this
   filter cost +11% CPU per open tab; see the runbook for 2026-08-20. */
.hub-art .mark{filter:drop-shadow(0 0 18px rgba(34,201,242,.6))}
.pod-1:not(.row-founder):not(.row-partner) .pod-rank{
  box-shadow:0 0 14px rgba(34,201,242,.5)}
.mark-glyph.mark{aspect-ratio:1}
/* The wordmark is Persian (2026-08-13). It CANNOT use JBMono, which it did while
   the mark was Latin: JetBrains Mono carries no Persian glyphs, so the browser
   would fall back for the letters and render the brand in a random system face.
   Vazirmatn at 1.05rem sits at the same optical weight as the old 1.15rem mono,
   because Persian letterforms carry more ink per em. */
.wordmark{font-family:Vazirmatn,system-ui,sans-serif;font-weight:700;font-size:1.05rem;
  color:var(--text-hi);white-space:nowrap;letter-spacing:-.01em}
.wordmark b{color:var(--cyan-500);font-weight:700}
.site-head nav a{color:var(--text-mid);font-size:.95rem;transition:color .2s;white-space:nowrap}
.site-head nav a:hover{color:var(--cyan-300)}
.site-head .cta{background:var(--cyan-500);color:#04222e;font-weight:700;padding:8px 18px;border-radius:12px}
.site-head .cta:hover{background:var(--cyan-300)}

/* hero */
/* ---------- hero ----------
   Brand-locked (navy/cyan, Vazirmatn, bolt mark). Motion is CSS-only and every
   animated part holds still under prefers-reduced-motion. Only transform/opacity
   animate, so the hero stays cheap for LCP. */
/* The hero lives OUTSIDE <main> (which is max-width 1060), so it is full body
   width naturally — no 100vw breakout, no horizontal-overflow trap. overflow
   hidden keeps the aura drift from ever spilling. The inner content is a tidy
   centred column with side padding for mobile. */
.hero{position:relative;text-align:center;padding:76px 0 68px;overflow:hidden}
.hero-inner{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:0 20px}

/* Lightning strike: a giant, single-cyan version of the brand bolt, centred so
   it echoes the small foreground logo — the mark's energy surging. Full-height,
   behind the content, faint at peak so the copy always wins. It flashes in and
   out on a loop (see .hero-strike keyframes); invisible by default so a no-motion
   render shows nothing. */
.hero-bolt-bg{position:absolute;inset:0;z-index:0;pointer-events:none;
  display:flex;align-items:center;justify-content:center;opacity:0}
.hero-bolt-bg .mark{height:100%;width:auto;filter:none}
/* Recolour every polygon to one brand cyan — the two-tone logo would read as a
   dim dark shape on navy. */
/* The background copy is flattened to one flat cyan: the gradient and any glow
   would fight the hero text sitting on top of it. Targets `path` because the v3
   mark is paths, not the old polygons. */
.hero-bolt-bg .mark path{fill:var(--cyan-500)}
/* On a narrow column the full-height bolt fills the frame and reads bolder than
   the intended "subtle". Shrink it on phones so the strike stays a whisper. */
@media(max-width:620px){.hero-bolt-bg .mark{height:60%}}

/* ⚠️ Static drop-shadow, held near what `breathe` used to peak at. Animating a
   filter re-renders it every frame: that one keyframe measured +27% CPU per
   open tab (2026-08-20). Glow yes, pulsing glow no. */
.hero-mark .mark{height:120px;width:auto;filter:drop-shadow(0 0 26px rgba(34,201,242,.42))}
.hero h1{font-size:clamp(2.1rem,6vw,3.4rem);margin:20px 0 14px}
/* Same-family emphasis: کلاچ stays cyan with a soft glow, no gradient text. */
.cy{color:var(--cyan-500);text-shadow:0 0 22px rgba(34,201,242,.35)}
.sub{max-width:540px;margin:0 auto;color:var(--text-mid)}

/* The address, as the hero's focal object. A clean two-part chip: the domain is
   the content (large cyan mono), the copy button is the action (icon + label,
   check + green on success). Restrained — a 1px border and a soft dark lift, no
   loud glow. Shape radius matches the site's r14. */
.addr-focus{margin-top:36px;display:flex;justify-content:center;max-width:100%}
.addr-pill{display:flex;align-items:stretch;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:14px;overflow:hidden;
  box-shadow:0 12px 34px -18px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.03)}
.addr-pill code{display:flex;align-items:center;font-size:1.4rem;font-weight:700;
  color:var(--cyan-300);padding:15px 28px;letter-spacing:.02em}
/* The action. Divider on the inline-start edge; hover tints it cyan so it reads
   as tappable; active gives a physical press. */
.addr-pill button{display:flex;align-items:center;gap:8px;font-family:Vazirmatn;
  font-size:.98rem;font-weight:700;background:var(--navy-700);color:var(--text-hi);
  border:0;border-inline-start:1px solid var(--navy-600);padding:0 22px;cursor:pointer;
  transition:background .18s,color .18s,transform .08s}
.addr-pill button:hover{background:var(--navy-600);color:var(--cyan-300)}
.addr-pill button:active{transform:scale(.97)}
.addr-pill button svg{width:18px;height:18px;flex:none}
.addr-pill .check-ico{display:none}
/* Success: green, and the copy icon becomes a check. */
.addr-pill button.done{color:var(--ok)}
.addr-pill button.done .copy-ico{display:none}
.addr-pill button.done .check-ico{display:block}

.hero-support{margin-top:22px;display:flex;flex-direction:column;align-items:center;gap:10px}
.live-line{display:flex;align-items:center;gap:8px;font-size:.9rem;color:var(--text-low)}
.dot{width:8px;height:8px;border-radius:50%;background:var(--ok);animation:pulse 1.6s ease-in-out infinite}
/* The dot must be able to say no. When no node has reported in five minutes
   it stops pulsing and goes grey, so the indicator stops claiming liveness
   the service cannot currently back. */
.dot-off{background:var(--text-low);animation:none}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.8)}}
.hero-hint{font-size:.9rem;color:var(--text-low)}

/* Motion layer — everything here is gated, so reduced-motion gets a clean,
   fully-composed static hero. */
@media (prefers-reduced-motion:no-preference){
  /* The strike: ~8s loop. Distinct arrival and exit. Only opacity/transform,
     kept faint (peak .14) so text over it stays readable.
       flash-crack in  — snap bright, dip, bright again (a real lightning crack)
       hold            — brief
       flicker-out drift — dim with a flicker while drifting up (slower, different)
       gap             — invisible for the rest of the cycle */
  .hero-bolt-bg{animation:hero-strike 8s ease-in-out infinite;will-change:opacity,transform}
  @keyframes hero-strike{
    0%{opacity:0;transform:translateY(0)}
    2%{opacity:.14}
    3.2%{opacity:.05}
    4.4%{opacity:.14}            /* flash-crack finished (~0.35s in) */
    12%{opacity:.14}             /* hold */
    16%{opacity:.06}
    19%{opacity:.10}
    23%{opacity:0;transform:translateY(-10px)}  /* flicker-out drift done (~1.8s) */
    100%{opacity:0;transform:translateY(0)}}     /* long invisible gap */

  /* The bolt breathes: the mark is the "clutch", the energy should live. */

  /* Load-in cascade for the above-the-fold hero: each part settles in turn.
     The .rv observer also reveals these immediately in-viewport; this adds the
     staggered polish on top and is self-contained (no JS needed). */
  .hero-inner>*{animation:hero-in .7s cubic-bezier(.16,1,.3,1) both}
  .hero-mark{animation-delay:.05s}
  .hero h1{animation-delay:.16s}
  .sub{animation-delay:.28s}
  .addr-focus{animation-delay:.42s}
  .hero-support{animation-delay:.56s}
  @keyframes hero-in{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
}

/* sections */
section{padding:56px 0}
section h2{font-size:clamp(1.5rem,4vw,2.1rem);margin-bottom:12px;text-align:center}
.section-lead{max-width:620px;margin:0 auto 34px;text-align:center;color:var(--text-mid)}

/* proof */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:28px}
.stat{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:26px 22px;text-align:center}
.stat .num{font-family:JBMono,monospace;font-weight:700;font-size:2.4rem;color:var(--text-hi)}
.stat .unit{font-size:1.1rem;color:var(--cyan-500);margin-inline-start:2px}
.stat .lbl{font-size:.88rem;color:var(--text-low);margin-top:8px;line-height:1.7}
.proof-note{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:26px 28px;max-width:760px;margin:0 auto}
.proof-note h3{font-size:1.1rem;margin-bottom:8px}
.proof-note p{font-size:.95rem}

/* how it works */
/* The route. RTL: player at the start (right), servers at the end (left). */
.route{display:flex;align-items:flex-start;justify-content:center;gap:0;
  margin:40px auto 10px;max-width:740px}
.node{display:flex;flex-direction:column;align-items:center;gap:10px;width:150px;flex:none}
.node-art{height:104px;display:grid;place-items:center}
.hub-art .mark{height:76px;width:auto}
/* The three destinations. Base state is a plain ROW: that is what shows with no
   motion, with reduced motion, and before any animation runs, so all three stay
   readable instead of piled on top of each other. The cross-fade below turns it
   into a stack. */
/* The NO-MOTION fallback (the animated single-logo version is in the
   prefers-reduced-motion:no-preference block far below). It has to lay every
   server out at once inside a 150px node.
   ⚠️ This used to be 72px marks capped at 232px, described as wrapping "3x2".
   It never did: .node is width:150px, so a 232px cap buys nothing and the marks
   wrapped ONE per row - six of them ran 400px+ down the page, straight over the
   node's own label and into the section below. Measured, not theorised.
   40px marks inside a 136px cap give 3 per row (3x40 + 2x8 = 136) for any count,
   and the auto height keeps the overflow contained. */
.srv-art{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:8px;max-width:136px;margin-inline:auto}
.srv-art img{width:40px;height:40px;object-fit:contain;display:block}
/* The fixed 104px band is what the other two nodes need; this one must be free
   to grow to its wrapped rows or it clips them. */
.node-srv .node-art{height:auto;min-height:104px}
/* The character. Sized to the same 104px band as the other two nodes so the
   three labels sit on one line. */
.node-you .mc3d{width:88px;height:104px}
.node-you .mc3d img{width:100%;height:100%;object-fit:contain}
.node-lbl{font-size:.95rem;font-weight:700;color:var(--text-hi);white-space:nowrap}
.node-sub{font-size:.8rem;color:var(--text-low);text-align:center;line-height:1.6;margin-top:-4px}
/* overflow:hidden matters: the wire is flex:1 so its width changes with the
   viewport, and --travel was never actually set anywhere (the keyframe had been
   falling back to a flat 100px since it was written). The dot now overshoots and
   is clipped at the edge, which looks right at any wire width. */
.wire{flex:1;height:2px;margin:52px 10px 0;overflow:hidden;
  background:linear-gradient(90deg,var(--navy-600),var(--cyan-700),var(--navy-600))}

@media(max-width:620px){
  /* Three logo nodes plus labels do not fit across a phone. Stack them and turn
     the wires vertical. Declared here rather than left to wrapping. */
  .route{flex-direction:column;align-items:center;gap:0;max-width:none;margin-top:28px}
  .node{width:auto}
  .wire{flex:none;width:2px;height:56px;margin:12px 0;
    background:linear-gradient(180deg,var(--navy-600),var(--cyan-700),var(--navy-600))}
}

/* plans */
/* The row holds however many plans are on sale — CP_PLANS is a launch switch —
   plus an optional Early Access card, so the count is 1, 3, 4 or 5 and the
   layout may not assume any of them.

   This used to be repeat(auto-fit,minmax(180px,240px)) inside max-width:900px,
   with a comment claiming that fits four cards at ~213px. It does not, and the
   fourth plan shipped orphaned on a row of its own. auto-fit counts repetitions
   against the track's MAX when that max is a definite length, not against its
   min: the browser laid out four hypothetical 240px tracks (4x240 + 3x16 =
   1008 > 900), settled on three, and pushed the year card onto an implicit
   second row where justify-content:center left it flush against the RTL start
   edge. 180px never entered the arithmetic.

   Flex, not grid, because grid cannot centre a partial last row. With an
   unknown item count the trailing card has to land in the middle rather than
   against an edge, and that is exactly what grid gets wrong here. flex-basis is
   the wrap threshold, max-width caps a lone card so a single-plan launch still
   looks like a card rather than a banner, and 1010px = 4x240 + 3x16 + 2 fits
   four full-size cards inside main's 1020px content box.

   --plan-row-w, not a literal, because /store reads the same number from a
   margin-inline break-out further down the sheet. That rule outranks these
   media queries on specificity, so a literal here would leave the landing page
   at 2x2 and /store still at a cramped 4-up on the same viewport. One token,
   both pages. */
/* Row gap is larger than column gap because .best-badge hangs 12px above its
   card, and once the row wraps that badge lands in the gap over the card
   above it. 16px would put it on top of that card's border. */
.plan-row{--plan-row-w:1010px;
  display:flex;flex-wrap:wrap;justify-content:center;align-items:stretch;
  gap:28px 16px;max-width:var(--plan-row-w);margin:0 auto 30px}
.plan-row > .plan-card{flex:1 1 200px;max-width:264px}
.plan-card{display:block;background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:26px 20px;text-align:center;color:var(--text-mid);transition:border-color .2s,transform .2s}
a.plan-card:hover{border-color:var(--cyan-700);transform:translateY(-3px)}
.plan-card.disabled{opacity:.6}
.plan-days{font-size:1.4rem;color:var(--text-hi);font-weight:700}
.plan-days .mono{font-size:1.9rem;color:var(--cyan-500)}
/* «نامحدود» stands in for the big cyan number on the lifetime card. Smaller than
   1.9rem because that size is tuned for 2-3 Latin digits, not a 7-letter Persian
   word — at 1.9rem it overflows the 180px card. */
.plan-days .plan-inf{font-size:1.5rem;color:var(--cyan-500)}
.plan-price{margin-top:10px;font-weight:700;color:var(--text-hi)}
.plan-price .cur{font-weight:400;font-size:.85rem;color:var(--text-low)}
.plan-price.soon{color:var(--text-low);font-weight:400}
.plans-preview{text-align:center}

/* faq */
/* The FAQ is its own page now (the landing page was getting long). .faq keeps
   its old rules so the section markup moved across unchanged. */
.faq-page{max-width:1060px;margin:0 auto;padding:48px 20px 90px}
.faq-head{max-width:680px;margin:0 auto 28px;text-align:center}
.faq-head h1{font-size:1.9rem;color:var(--text-hi)}
.faq-lead{margin-top:10px;font-size:.95rem;color:var(--text-low)}
@media(max-width:560px){
  .faq-page{padding:32px 18px 70px}
  .faq-head h1{font-size:1.5rem}
}
/* The global `section{padding:56px 0}` applies here too and left a big hole
   under the lead. Killed with a class-on-class selector (0,2,0) rather than a
   blanket `.faq-page section{padding:0}` (0,1,1), which is the shape that
   silently ate .tos-alert's padding on /terms. */
.faq-page .faq{padding:0}
.faq{max-width:680px;margin:0 auto}
/* Proof before doubts. Reuses the .stat treatment from the leaderboard so the
   figures read as the same kind of fact, not a second visual language. */
/* minmax(0,1fr), not 1fr: a bare 1fr is minmax(auto,1fr), so a column cannot
   shrink below its content's min-content width and the grid pushes past its
   container. That is what put 17px of horizontal overflow on the page at 390px
   the moment this strip went to three columns. */
.faq-proof{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-bottom:26px;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:22px 18px}
.faq-proof .stat{text-align:center}
.faq-proof .num{font-size:1.7rem}
.faq-proof .lbl{margin-top:6px;font-size:.8rem}
.faq-cta{margin-top:26px;text-align:center}
.faq-cta .btn-solid{display:inline-block;text-decoration:none;padding:13px 40px;font-size:1.02rem}
.faq-cta-sub{margin-top:12px;font-size:.86rem}
.faq-cta-sub a{color:var(--text-low)}
.faq-cta-sub a:hover{color:var(--cyan-300)}
@media(max-width:560px){
  /* Stays three across on a phone. Stacking made the strip a full screen of
     numbers before the first question, which buries the questions. */
  .faq-proof{gap:8px;padding:16px 10px}
  .faq-proof .num{font-size:1.12rem}
  .faq-proof .lbl{font-size:.66rem;margin-top:5px;line-height:1.5}
}
.faq details{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;margin-bottom:10px;padding:0 20px}
.faq summary{cursor:pointer;padding:16px 0;font-weight:700;color:var(--text-hi);list-style:none;position:relative}
.faq summary::after{content:"+";position:absolute;inset-inline-end:2px;top:50%;transform:translateY(-50%);color:var(--cyan-500);font-family:JBMono;font-size:1.2rem}
.faq details[open] summary::after{content:"–"}
.faq details[open]{border-color:var(--cyan-700)}
.faq summary:hover{color:var(--cyan-300)}
.faq details p{padding:0 0 18px;font-size:.95rem}

/* buttons + forms */
.btn-solid{display:inline-block;background:var(--cyan-500);color:#04222e;font-weight:700;border:0;border-radius:12px;padding:12px 30px;font-size:1rem;font-family:Vazirmatn;cursor:pointer;transition:background .2s}
.btn-solid:hover{background:var(--cyan-300)}
/* min-height 44px: the WCAG/mobile touch-target minimum. Every .btn-ghost was
   39px — the submit button on the email, password, phone and Discord settings
   forms, i.e. every action on the account page. .btn-quiet next door was
   already 44. Found while adding the Discord field; fixed for the class rather
   than for one button, since they are all the same control. */
.btn-ghost{background:none;border:1px solid var(--navy-600);color:var(--text-mid);border-radius:12px;padding:8px 18px;min-height:44px;font-family:Vazirmatn;cursor:pointer}
.card-form{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:28px;margin-top:26px;display:flex;flex-direction:column;gap:6px;text-align:start}
.card-form label{color:var(--text-hi);font-weight:700;font-size:.95rem;margin-top:14px}
.card-form input{background:var(--navy-900);border:1px solid var(--navy-600);border-radius:8px;color:var(--text-hi);padding:12px 14px;font-size:1rem;font-family:inherit}
.card-form input:focus{border-color:var(--cyan-700);outline:none}
.card-form .hint{font-size:.82rem;color:var(--text-low)}
.card-form button{margin-top:22px}
.card-form fieldset{border:0;padding:0;display:flex;gap:20px}
.card-form legend{color:var(--text-hi);font-weight:700;font-size:.95rem;margin-bottom:10px}
.radio{display:flex;align-items:center;gap:8px;color:var(--text-mid)}
.form-err{background:rgba(255,90,95,.12);border:1px solid rgba(255,90,95,.4);color:#ffb3b5;border-radius:8px;padding:10px 16px;margin-top:18px;font-size:.9rem}

/* pay page */
.pay-rails{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;margin-top:10px}
.rail-card{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:22px}
.rail-card h2{font-size:1.05rem;margin-bottom:12px}
.rail-big{font-size:1.3rem;font-weight:700;color:var(--cyan-300);letter-spacing:.06em;word-break:break-all}
.rail-big.usdt{font-size:.95rem}
.rail-holder{font-size:.85rem;color:var(--text-low);margin-top:8px}

/* status page */
.status-badge{width:64px;height:64px;border-radius:50%;margin:40px auto 20px;position:relative}
.status-badge.wait{border:3px solid var(--warn)}
.status-badge.ok{border:3px solid var(--ok)}
.status-badge.bad{border:3px solid var(--bad)}
.status-badge::after{content:"";position:absolute;inset:22px;border-radius:50%}
.status-badge.wait::after{background:var(--warn)}
.status-badge.ok::after{background:var(--ok)}
.status-badge.bad::after{background:var(--bad)}
.order-id{margin-top:36px;font-size:.82rem;color:var(--text-low)}

/* steps hint */

/* admin */
.admin-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}
.tabs{display:flex;gap:10px;margin-bottom:18px}
.tabs a{border:1px solid var(--navy-600);border-radius:12px;padding:7px 16px;color:var(--text-mid);font-size:.9rem}
.tabs a.on{background:var(--navy-700);color:var(--text-hi);border-color:var(--cyan-700)}
table.orders{width:100%;border-collapse:collapse;background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;overflow:hidden;font-size:.9rem}
.orders th,.orders td{padding:10px 12px;text-align:start;border-bottom:1px solid var(--navy-700)}
.orders th{color:var(--text-low);font-weight:400;font-size:.8rem}
.orders td.ref{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acts{display:flex;gap:8px}
.btn-ok,.btn-bad{border:0;border-radius:8px;padding:7px 16px;font-family:Vazirmatn;font-weight:700;cursor:pointer}
.btn-ok{background:var(--ok);color:#03301a}
.btn-bad{background:rgba(255,90,95,.15);color:var(--bad);border:1px solid rgba(255,90,95,.4)}

/* footer */
.site-foot{border-top:1px solid var(--navy-700);margin-top:56px;background:var(--navy-800)}
.foot-grid{max-width:1060px;margin:0 auto;padding:34px 20px 36px;display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:30px;align-items:start}
.foot-brand .brand{display:inline-flex;align-items:center;gap:10px}
.foot-tag{margin-top:12px;font-size:.88rem;color:var(--text-low);max-width:26ch;line-height:1.9}
.foot-col{display:flex;flex-direction:column;gap:10px}
.foot-head{font-size:.8rem;color:var(--text-low);margin-bottom:4px}
.foot-col a{color:var(--text-mid);font-size:.92rem;width:fit-content;transition:color .2s}
/* Same geometry as .soc in the next column over (inline-flex, 8px gap, 17px
   mark), so the outline icons and the filled brand marks line up as one system.
   The brand glyphs must be filled - they are logos - so matching SIZE is the
   most that can agree; a size mismatch on top of that would read as two
   unrelated columns. */
.foot-nav a{display:inline-flex;align-items:center;gap:8px}
.foot-nav a .ic{width:17px;height:17px;flex:none;color:var(--text-low);transition:color .2s}
.foot-nav a:hover .ic{color:var(--cyan-300)}
.foot-col a:hover{color:var(--cyan-300)}
/* Trust marks. The visible mark is OUR file; the official badge lives inside the
   <details> panel with its markup untouched. The old rule put both on white
   plates, which read like stickers on a dark footer, and the eNamad slot showed
   as a blank white rectangle to everyone outside Iran because
   trustseal.enamad.ir geo-blocks non-Iran IPs. */
/* A plain row of unboxed marks, per the owner's reference. Click opens the
   official badge in the panel below. */
.trust-row{display:flex;align-items:center;gap:24px;margin-top:12px;flex-wrap:wrap}
.trust{border-radius:12px}
/* Icons only, like the servers strip. The labels were clipped mid-word by the
   marquee window: a partly visible ICON at a fade edge reads as intentional, a
   partly visible WORD reads as broken. The name lives on in title= and the
   summary's aria-label. */
.trust summary{list-style:none;cursor:pointer;display:flex;align-items:center;
  justify-content:center;padding:0}
.trust summary::-webkit-details-marker{display:none}
.trust summary img{display:block;object-fit:contain}
.trust[open] summary img{filter:drop-shadow(0 0 8px rgba(34,201,242,.7))}
/* min-height matters: trustseal.enamad.ir geo-blocks non-Iran IPs, so from
   abroad its image has zero height and the white plate collapsed to a sliver
   that read as a rendering bug. With a floor it stays a sensible plate whether
   the badge loads or not. */
.trust-real{margin-top:10px;background:#fff;border-radius:10px;padding:8px;
  display:grid;place-items:center;min-width:64px;min-height:84px}
/* Zarinpal specify 80px for their badge; the inline <style> in their snippet
   would be blocked by style-src 'self', so the rule lives here. */
.trust-real img{display:block;max-width:80px;height:auto}
.foot-bar{border-top:1px solid var(--navy-700);padding:16px 20px}
.foot-bar{display:flex;justify-content:space-between;align-items:center;max-width:1060px;margin:0 auto;font-size:.82rem;color:var(--text-low)}
.foot-domain{color:var(--text-low)}
/* The copyright name was `mono` + dir=ltr while the brand was Latin. Persian in
   JBMono renders in a fallback face, so it takes the UI font like the wordmark. */
.foot-name{font-weight:700;color:var(--text-mid)}

/* Supported servers. A full-width band above the columns: the emblems are
   visual and need horizontal room, and a narrow fourth column would shrink them
   to nothing on tablet. Three very different artworks (one tall, two square)
   share one frame because each was composited onto a transparent 128x128 square
   at build time, so the tiles are identical whatever the art does. */
.foot-servers{max-width:1060px;margin:0 auto;padding:34px 20px 28px;
  border-bottom:1px solid var(--navy-700)}
/* No boxes (owner, 2026-08-13): the emblems sit directly on the footer. */
.srv{display:flex;align-items:center;justify-content:center}
.srv img{width:64px;height:64px;object-fit:contain;display:block}
/* Heading at one end, the "more coming" note at the other, so the band reads as
   a full-width strip instead of a cluster hugging the start edge. */
.srv-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;flex-wrap:wrap}
.srv-head .foot-head{margin-bottom:0}
.srv-more{font-size:.82rem;color:var(--text-low)}

/* Sliding strips. Same shape as .buyers-strip above and the SAME keyframes:
   the track is repeated and translates -50%, so each half must be at least as
   wide as its window or the loop shows a gap. direction:ltr keeps the flex
   order and the translate maths predictable on an RTL page, and the mask fades
   both edges so nothing hard-clips. */
/* padding-block is load-bearing: the hover glow is a drop-shadow and this box is
   overflow:hidden, so with the track sized exactly to the 64px artwork the blur
   was being sliced flat top and bottom. The padding gives the glow room INSIDE
   the clip box; .srv-mq drops its margin-top to keep the spacing it had. */
.foot-mq{direction:ltr;overflow:hidden;padding-block:18px;
  mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent)}
.foot-mq-track{list-style:none;padding:0;margin:0;display:flex;width:max-content;
  align-items:center;animation:buyers-slide 26s linear infinite}
.srv-mq{margin-top:-2px;margin-bottom:-10px}
/* 11 servers make the -50% travel ~2440px; scale the duration up from the
   7-server 26s (which moved ~1544px) so the scroll stays ~60px/s, not faster. */
.srv-mq .foot-mq-track{gap:48px;animation-duration:41s}
/* Pause when someone reaches for the strip, including via keyboard. */
.foot-mq:hover .foot-mq-track,
.foot-mq:focus-within .foot-mq-track{animation-play-state:paused}
@media(prefers-reduced-motion:reduce){
  .foot-mq-track{animation:none}
}

/* Social links: icon plus label. Glyphs are the official Simple Icons paths,
   vendored inline because the CSP allows no external origin but enamad. */
.soc{display:inline-flex;align-items:center;gap:8px}
.soc-i{width:17px;height:17px;flex:none;fill:currentColor}


@media(max-width:900px){
  .foot-grid{grid-template-columns:1fr 1fr;gap:26px}
}
@media(max-width:720px){
  .foot-grid{grid-template-columns:1fr;text-align:center;gap:26px}
  .foot-brand .brand{justify-content:center}
  .foot-tag{margin-inline:auto}
  .foot-col{align-items:center}
  .foot-col a{width:auto}
  .trust-row,.srv-row{justify-content:center}
  .foot-servers{text-align:center}
  .soc{justify-content:center}
  .foot-bar{flex-direction:column;gap:8px}
  /* The columns centre at this width, so the card centres with them. */
}
@media(max-width:640px){
  /* 14px of side padding instead of 20 buys the card 12px of width, which on a
     390px phone is what lets it reach its full 350px and lay the widget out
     without the widget growing its own horizontal scrollbar. The card is
     max-width:100% either way, so it is always the card that gives, never the
     page. */
}

/* mobile */
@media(max-width:640px){
  .hero{padding-top:44px}
  .stat .num{font-size:2rem}
  .acts{flex-direction:column}
  table.orders{display:block;overflow-x:auto}
}
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .dot{animation:none}
  *{transition:none!important}
}

/* zarinpal primary payment */
.zp-form{margin-top:26px;text-align:center}
.btn-big{font-size:1.15rem;padding:16px 44px;width:100%;max-width:420px}
.center-hint{font-size:.85rem;color:var(--text-low);margin-top:12px}
.rails-divider{text-align:center;color:var(--text-low);font-size:.9rem;margin:34px 0 6px;position:relative}

/* minecraft avatar preview */
/* ---------- /store/order ---------- */
/* Two columns at desktop: the form does the work, the summary answers "what am I
   buying and who is it for". Below 900px it stacks with the SUMMARY FIRST, so the
   character and price are the first thing seen on a phone rather than buried
   under three text fields. */
.order-page{max-width:1000px;margin:0 auto;padding:0 20px 80px}
.order-page h1{margin-top:34px;text-align:center;font-size:clamp(1.6rem,4.5vw,2.2rem)}
.order-page .section-lead{text-align:center;margin-bottom:26px}
.back-link{display:inline-block;margin-top:26px;color:var(--text-mid);font-size:.88rem;
  transition:color .18s ease-out}
.back-link::before{content:"‹";margin-inline-end:6px}
.back-link:hover{color:var(--cyan-300)}
.order-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,.85fr);
  gap:20px;align-items:start}
.order-grid .card-form{margin-top:0}
@media(max-width:900px){
  .order-grid{grid-template-columns:1fr}
  .order-side{order:-1}
}

.order-side{position:sticky;top:20px;display:flex;flex-direction:column;gap:14px}
@media(max-width:900px){.order-side{position:static}}

.side-plan{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;
  padding:20px;text-align:center}
.side-lbl{font-size:.78rem;color:var(--text-low)}
.side-plan-name{margin-top:4px;font-size:1.3rem;font-weight:700;color:var(--text-hi)}
.side-plan-name .mono{font-size:1.6rem;color:var(--cyan-500)}
.side-plan-name .plan-inf{font-size:1.35rem;color:var(--cyan-500)}
/* The tier under the plan length in the checkout summary: named, not only priced. */
.side-tier{display:block;margin-top:4px;font-size:.88rem;font-weight:600;color:var(--cyan-300)}
.side-price{margin-top:4px;display:flex;align-items:baseline;justify-content:center;gap:6px}
.side-price .mono{font-size:1.8rem;font-weight:700;color:var(--text-hi);letter-spacing:-.02em}
.side-price .cur{font-size:.78rem;color:var(--text-low)}

.side-char{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;
  padding:18px 16px 20px;text-align:center;display:flex;flex-direction:column;align-items:center}
.side-char .mc3d{width:130px;height:230px;margin:0}
.who-name{margin-top:10px;font-size:1rem;color:var(--text-hi);font-weight:700;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}
.who-note{margin-top:6px;font-size:.78rem;color:var(--text-low);line-height:1.8;
  min-height:2.6em} /* reserved so the card does not jump between states */

/* The preview must answer for every state, not just the happy one. */
/* Idle = a grey stand-in, not an empty box and not a live-looking account. It
   turns to full colour the moment a real name resolves, which is the whole
   payoff of the panel. */
.mc3d.is-idle{opacity:.5;filter:grayscale(1)}
.mc3d.is-loading{opacity:.5}
.mc3d.is-bad{opacity:.35;filter:grayscale(1)}
@media (prefers-reduced-motion:no-preference){
  .mc3d{transition:opacity .2s ease-out,filter .2s ease-out}
}

.field-err{color:#ffb3b5;font-size:.82rem;margin-top:2px}
.card-form input[aria-invalid="true"]{border-color:rgba(255,90,95,.55)}
.pay-note{margin-top:12px;font-size:.78rem;color:var(--text-low);text-align:center}
.avatar-box{width:64px;height:64px;flex:0 0 64px;background:var(--navy-900);border:1px solid var(--navy-600);border-radius:8px;overflow:hidden;display:flex;align-items:center;justify-content:center}
.avatar-box img{width:56px;height:56px;image-rendering:pixelated;opacity:0;transition:opacity .25s ease}
.avatar-box img.show{opacity:1}
.avatar-inline{width:28px;height:28px;image-rendering:pixelated;border-radius:4px;vertical-align:middle;margin-inline-end:6px}
.avatar-status{width:80px;height:80px;image-rendering:pixelated;border-radius:10px;border:1px solid var(--navy-600);margin:0 auto 14px;display:block}

/* pricing cards */
.plan-card{position:relative;display:flex;flex-direction:column;align-items:center;padding:30px 22px 24px}
.plan-card.best{border-color:var(--cyan-700);box-shadow:0 0 0 1px rgba(34,201,242,.15),0 12px 40px rgba(34,201,242,.06)}
.best-badge{position:absolute;top:-12px;right:50%;transform:translateX(50%);background:var(--cyan-500);color:#04222e;font-size:.72rem;font-weight:700;border-radius:999px;padding:3px 14px;white-space:nowrap}
.perday{font-size:.78rem;color:var(--text-low);margin-top:2px}
.plan-sep{width:44px;height:1px;background:var(--navy-600);margin:16px auto 14px}
.plan-price{margin-top:4px;display:flex;align-items:baseline;gap:6px;justify-content:center}
.price-num{font-family:JBMono,monospace;font-weight:700;font-size:1.65rem;color:var(--text-hi);letter-spacing:-.01em}
.plan-price .cur{font-size:.78rem;color:var(--text-low)}
.plan-cta{margin-top:18px;width:100%;text-align:center;padding:11px 0}
.btn-closed{margin-top:18px;width:100%;text-align:center;background:var(--navy-700);color:var(--text-low);border-radius:12px;padding:11px 0;font-size:.85rem}

/* --- the value ladder -----------------------------------------------------
   Four lengths at one ٪49 chip read as four prices and nothing else: the chip
   is identical on every card, so the only thing separating them was the total,
   and a bigger total looks worse, not better. What actually separates them is
   what each length costs against buying 30 days at a time — 108,000 / 355,000 /
   998,000 tomans — and the year's figure is very nearly four months of the
   monthly price. That is the argument for the longer commitment, so it is the
   number the cards carry. Computed in the template from the live 30-day price;
   see the "plancards" partial for why it is never typed in. */

/* Every card's button on the same line. The cards are flex columns stretched to
   the tallest, and the hero's saving block is two lines taller than the rest —
   without this its CTA sits below its neighbours and the row reads ragged. */
.plan-foot{margin-top:auto;padding-top:18px;width:100%}
.plan-foot > .plan-cta,.plan-foot > .btn-closed{margin-top:0}

/* 24ch, and the copy is "کمتر از ماهانه" rather than "کمتر از خرید ماهانه":
   at the 240px a card gets in a 4-up row the longer line wrapped and left
   "ماهانه" stranded on a line of its own under every card. */
.plan-save{margin-top:11px;font-size:.78rem;line-height:1.75;color:var(--text-low);
  text-align:center;max-width:24ch}
.plan-save .mono{font-family:JBMono,monospace;font-weight:700;color:var(--text-mid)}

/* «۱ ساله» had no rule at all, so the best-value card announced itself in
   1.4rem body white while "180 روز" beside it carried a 1.9rem cyan number —
   the card meant to win the row read as the weakest one in it. Sized like
   .plan-inf for the same reason: 1.9rem is tuned for 2-3 Latin digits and a
   Persian word set at it overflows the card. */
.plan-days .plan-year{font-size:1.62rem;color:var(--cyan-500)}

/* The hero card. Emphasis is added to it rather than taken away from the
   others: every card here is a real product with a real button, and the 30-day
   plan is where a first-time buyer starts, so it keeps its full-strength cyan
   CTA. What the year gets instead is its own surface, the badge, and the only
   filled block in the row. */
.plan-card.best{background:
  linear-gradient(180deg,rgba(34,201,242,.075),rgba(34,201,242,.012)),var(--navy-800)}
.plan-card.best .plan-cta{box-shadow:0 6px 20px rgba(34,201,242,.26)}
.plan-save.big{display:flex;flex-direction:column;gap:1px;width:100%;max-width:none;
  margin-top:13px;padding:9px 10px;border-radius:10px;
  background:rgba(34,201,242,.10);border:1px solid rgba(34,201,242,.30)}
/* The line a buyer feels, over the arithmetic that backs it up. */
.save-free{display:block;font-size:.95rem;font-weight:700;color:var(--cyan-300)}
.save-free .mono{font-size:1.06rem;color:var(--cyan-300)}
.plan-save.big .save-vs{color:var(--text-mid)}
.plan-save.big .save-vs .mono{color:var(--text-hi)}

/* Four across needs 4x200 + 3x16 = 848px of row. Below that the cards wrap 3+1
   and orphan the last one all over again, so switch to a deliberate 2x2 before
   it can happen. The break is at 1000px rather than at the 888px where the
   arithmetic actually fails, because between the two the cards are down to
   ~205px — the cramped size this row already had — and a 256px 2x2 is the
   better page there. 528px = 2x256 + 16, which is two tracks and cannot be
   three. */
@media(max-width:1000px){
  .plan-row:not(.compact){--plan-row-w:528px}
}
/* Two cards under 560px leave ~212px each and "1,990,000 تومان" needs about
   190px before padding, so it would break the price line. Stack, and let the
   cards go full-bleed: a 264px card floating in the middle of a 380px phone
   reads as a mistake. This audience is almost entirely on phones, so the stack
   also loses the desktop padding it does not need. */
@media(max-width:560px){
  .plan-row:not(.compact){--plan-row-w:100%;gap:26px 12px}
  .plan-row:not(.compact) > .plan-card{max-width:none;padding:24px 20px 20px}
  .plan-save{max-width:none}
  /* PHONE ONLY: float the best-value card to the top. Stacked, the year card was
     the FOURTH thing you reached - about 1.3 screens of scrolling - so the offer
     meant to win the row was the one a phone-first audience was least likely to
     see. On desktop the ladder still reads cheap -> best across the row, so this
     stays inside the stack breakpoint.
     ⚠️ `order` moves the card visually but NOT in the DOM, so keyboard and screen
     reader order still follow the price ladder. That mismatch is the accepted
     trade here: the badge names the card («بهترین ارزش») rather than relying on
     position, so nothing is lost to a non-visual reader.
     margin-top pays for .best-badge, which is absolutely positioned at top:-12px
     and would otherwise hang into the section subtitle once this card is first. */
  .plan-row:not(.compact) > .plan-card.best{order:-1;margin-top:14px}
}

/* segmented gift toggle */
.segmented{border:1px solid var(--navy-600);border-radius:12px;padding:4px;display:flex;gap:4px;margin-bottom:4px}
.seg{flex:1;text-align:center;padding:10px 8px;border-radius:9px;color:var(--text-mid);font-size:.92rem;cursor:pointer;transition:background .2s,color .2s;min-height:44px;display:flex;align-items:center;justify-content:center}
.seg input{position:absolute;opacity:0;pointer-events:none}
.seg.on{background:var(--cyan-500);color:#04222e;font-weight:700}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.opt{font-weight:400;font-size:.8rem;color:var(--text-low)}

/* profile */
.profile-head{text-align:center;padding:36px 0 8px}
.profile-avatar{width:96px;height:96px;image-rendering:pixelated;border-radius:14px;border:1px solid var(--navy-600)}
.profile-avatar.inactive{filter:grayscale(.9) brightness(.7)}
.profile-name{font-size:1.2rem;font-weight:700;color:var(--text-hi);margin-top:14px}
/* lifetime: replaces the 3.2rem day count. Persian word, so smaller than the
   Latin-digit size it stands in for. */
.until{font-size:.85rem;color:var(--text-low);margin-top:4px}
.profile section{padding:34px 0 0}
.profile h2{font-size:1.15rem;text-align:right;margin-bottom:6px}
.profile .section-lead{text-align:right;margin:0 0 18px;font-size:.9rem}
.history-list{list-style:none;padding:0}
.history-list li{display:flex;justify-content:space-between;gap:12px;padding:11px 2px;border-bottom:1px solid var(--navy-700);font-size:.9rem;color:var(--text-mid)}
.history-list li:last-child{border-bottom:0}
.rail-lbl{color:var(--text-low);font-size:.82rem}
.ref-sm{font-size:.78rem;color:var(--text-low)}
.card-form.slim{padding:22px;margin-top:8px}
.keep-link{margin:36px 0 20px;text-align:center;font-size:.8rem;color:var(--text-low)}
.claim-box{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:20px;margin-top:20px}
.claim-box p{font-size:.9rem;margin-bottom:14px}
.gift-tag{background:rgba(34,201,242,.12);color:var(--cyan-300);border-radius:6px;padding:1px 8px;font-size:.72rem}

/* weekly leaderboard */
.lb{max-width:720px;margin:0 auto}
.lb-table{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;overflow:hidden}
.lb-head,.lb-row{display:grid;grid-template-columns:1.5fr .6fr .6fr .7fr .6fr;align-items:center;gap:10px;padding:12px 18px}
.lb-head{font-size:.78rem;color:var(--text-low);border-bottom:1px solid var(--navy-700)}
.lb-row{border-bottom:1px solid var(--navy-700);font-size:.92rem;color:var(--text-mid)}
.lb-row:last-child{border-bottom:0}
.lb-row.top{background-color:rgba(34,201,242,.06)}
.lb-row.top .lb-rank{color:var(--cyan-300)}
.lb-player{display:flex;align-items:center;gap:10px;min-width:0}
.lb-rank{width:1.4em;color:var(--text-low);font-weight:700;flex:0 0 auto}
.lb-avatar{width:28px;height:28px;image-rendering:pixelated;border-radius:6px;flex:0 0 auto}
.lb-name{color:var(--text-hi);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lb-server{font-size:.85rem;color:var(--text-low)}
.lb-num{text-align:left;font-size:.9rem}
.lb-gain{color:var(--ok);font-weight:700}
.lb-note{margin-top:14px;text-align:center;font-size:.8rem;color:var(--text-low)}
@media(max-width:560px){
  .lb-head,.lb-row{grid-template-columns:1.8fr .9fr;padding:12px 14px}
  .lb-server,.lb-head .lb-num:nth-child(3),.lb-row .lb-num:not(.lb-gain){display:none}
}

/* recent buyers strip */
.buyers{padding-bottom:24px}
.buyers-strip{direction:ltr;overflow:hidden;max-width:720px;margin:0 auto;mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);-webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.buyers-track{display:flex;gap:14px;width:max-content;animation:buyers-slide 28s linear infinite}
.buyers-strip:hover .buyers-track{animation-play-state:paused}
.buyer-card{display:flex;align-items:center;gap:10px;background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:10px 16px;white-space:nowrap}
.buyer-card img{width:36px;height:36px;image-rendering:pixelated;border-radius:8px}
/* ⚠️ Scoped to the WRAPPER, never `.buyer-card span`. That bare selector is
   (0,1,1) and beats .hx-c-XXXXXX (0,1,0), so it silently repainted every rank
   colour to near-white and the names all came out identical. This is the exact
   collision that broke the donor toast's + colours (.donor-toast b), found the
   same day - a descendant element selector setting `color` will always outrank
   the Minecraft colour classes. Set the size here; let the colour class win. */
.buyer-card > .mono{font-size:.88rem;display:inline-flex;align-items:center;gap:6px}
.buyer-card .hx-name{font-size:.88rem}
@keyframes buyers-slide{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){.buyers-track{animation:none}}

/* posed renders */
.profile-body{width:140px;height:auto;max-height:280px;filter:drop-shadow(0 18px 22px rgba(0,0,0,.45))}
.profile-body.inactive{filter:grayscale(.9) brightness(.7) drop-shadow(0 18px 22px rgba(0,0,0,.45))}
.avatar-inline.bust{width:34px;height:34px;border-radius:6px;object-fit:cover;image-rendering:auto}

/* ---- weekly leaderboard (rebuilt on real relay data) ---- */
.lb{max-width:760px;margin:0 auto}
/* Freshness line. Sits under the lead, which already carries 34px of bottom
   margin, so it pulls up to belong to the heading block rather than float
   between two sections. Quiet on purpose: it is reassurance, not a stat. */
.lb-updated{margin:-26px auto 24px;text-align:center;font-size:.82rem;color:var(--text-low)}
.lb-proof{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:26px}
.proof-cell{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;padding:22px 14px;text-align:center}
.proof-cell.hero{border-color:rgba(34,201,242,.45);background:linear-gradient(180deg,rgba(34,201,242,.07),var(--navy-800))}
.proof-num{font-size:2.5rem;font-weight:700;color:var(--text-hi);line-height:1.1;letter-spacing:-.02em}
.proof-cell.hero .proof-num{color:var(--cyan-300);font-size:2.8rem}
.proof-lbl{margin-top:8px;font-size:.8rem;color:var(--text-low);line-height:1.6}
/* champion */
.lb-champion{position:relative;display:flex;align-items:center;gap:22px;background:var(--navy-800);border:1px solid rgba(34,201,242,.4);border-radius:12px;padding:22px 26px;margin-bottom:12px}
.champ-badge{position:absolute;top:-11px;inset-inline-start:26px;background:var(--cyan-500);color:#04222e;font-size:.72rem;font-weight:700;border-radius:999px;padding:2px 14px}
.champ-render{width:110px;height:auto;flex:0 0 110px;filter:drop-shadow(0 14px 18px rgba(0,0,0,.5))}
.champ-info{flex:1;min-width:0}
.champ-name{font-size:1.15rem;font-weight:700;color:var(--text-hi);
  display:flex;align-items:center;flex-wrap:wrap;gap:6px}
.champ-hours{margin-top:6px;color:var(--text-mid)}
.champ-hours .mono{font-size:2.2rem;font-weight:700;color:var(--text-hi);letter-spacing:-.02em}
.champ-sub{margin-top:8px;font-size:.9rem;color:var(--text-low);line-height:1.8}
/* rows 2-5 */
.lb-rest{list-style:none;padding:0;background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px}
.lb-rest .lb-row:first-child{border-top-left-radius:11px;border-top-right-radius:11px}
.lb-rest .lb-row:last-child{border-bottom-left-radius:11px;border-bottom-right-radius:11px}
.lb-rest .lb-row{display:grid;grid-template-columns:1.6em 28px minmax(120px,1fr) 110px auto;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid var(--navy-700);font-size:.92rem}
.lb-rest .lb-row:last-child{border-bottom:0}
.lb-bar{height:5px;background:var(--navy-700);border-radius:3px;overflow:hidden}
.lb-bar i{display:block;height:100%;background:var(--cyan-700);border-radius:3px}
.lb-hours{color:var(--text-low);font-size:.82rem;white-space:nowrap;text-align:left}
.lb-hours .mono{color:var(--text-hi);font-weight:700;font-size:1rem}
/* cta + note */
.lb-cta{display:flex;align-items:center;justify-content:center;gap:18px;flex-wrap:wrap;margin-top:26px}
.lb-cta p{color:var(--text-mid)}
.lb-note{margin-top:18px;text-align:center;font-size:.8rem;color:var(--text-low);line-height:1.9}
@media(max-width:640px){
  .lb-proof{grid-template-columns:1fr;gap:10px}
  .proof-cell{display:flex;align-items:baseline;justify-content:center;gap:10px;padding:16px}
  .proof-num,.proof-cell.hero .proof-num{font-size:1.9rem}
  .proof-lbl{margin-top:0}
  .lb-champion{flex-direction:column;text-align:center;gap:14px}
  .lb-rest .lb-row{grid-template-columns:1.6em 28px 1fr auto;gap:10px;padding:12px 14px}
  .lb-bar{display:none}
}

/* ============ MOTION SYSTEM ============
   Specs from ui-ux-pro-max presets: hover 150-200ms/<2px, reveal 300-400ms/
   8-16px, stagger 20-40ms, ease-out entering, nothing >500ms for UI.
   Ambient loops are limited to 3 on the landing page (logo, route packet,
   buyers marquee) per taste-skill "don't animate everything that moves". */
:root{
  --glow-sm:0 0 0 1px rgba(34,201,242,.20), 0 0 14px rgba(34,201,242,.14);
  --glow-md:0 0 0 1px rgba(34,201,242,.32), 0 0 26px rgba(34,201,242,.22);
  --glow-lg:0 0 40px rgba(34,201,242,.30);
  --glow-gold-sm:0 0 0 1px rgba(245,179,53,.28),0 0 14px rgba(245,179,53,.16);
  --glow-gold-md:0 0 0 1px rgba(245,179,53,.40),0 0 28px rgba(245,179,53,.24);
  --glow-gold-lg:0 0 44px rgba(245,179,53,.32);
  --ease-out:cubic-bezier(.16,1,.3,1);
}

/* reveal: hidden ONLY when JS is present, so a JS failure never blanks content */
html.js .rv{opacity:0;transform:translateY(14px)}
html.js .rv.in{opacity:1;transform:none;
  transition:opacity .36s var(--ease-out),transform .36s var(--ease-out)}
html.js .rv-d1.in{transition-delay:.04s}
html.js .rv-d2.in{transition-delay:.08s}
html.js .rv-d3.in{transition-delay:.12s}
html.js .rv-d4.in{transition-delay:.16s}
html.js .rv-d5.in{transition-delay:.20s}
html.js .rv-d6.in{transition-delay:.24s}

@keyframes breathe{
  0%,100%{filter:drop-shadow(0 0 18px rgba(34,201,242,.28))}
  50%{filter:drop-shadow(0 0 34px rgba(34,201,242,.55))}}
@keyframes packet{
  0%{transform:translateX(0);opacity:0}
  10%{opacity:1} 90%{opacity:1}
  100%{transform:translateX(var(--travel,-190px));opacity:0}}
@keyframes packet-v{
  0%{transform:translateY(0);opacity:0}
  10%{opacity:1} 90%{opacity:1}
  100%{transform:translateY(64px);opacity:0}}
@keyframes hubflare{
  0%,100%{filter:drop-shadow(0 0 10px rgba(34,201,242,.45))}
  55%{filter:drop-shadow(0 0 26px rgba(34,201,242,.9))}}
@keyframes popin{from{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}
@keyframes okpop{0%{transform:scale(1)}45%{transform:scale(1.12)}100%{transform:scale(1)}}

@media (prefers-reduced-motion:no-preference){
  /* 2) route packets — storytelling: your traffic actually moving through us.
     TWO per wire and faster than before (owner asked for hype). They travel a
     NEGATIVE distance because the route is RTL now: positive X would run them
     from the servers back to the player, telling the story backwards. */
  .route .wire{position:relative;overflow:visible}
  .route .wire::before,.route .wire::after{content:"";position:absolute;top:-3px;right:0;
    width:8px;height:8px;border-radius:50%;background:var(--cyan-300);
    box-shadow:0 0 12px var(--cyan-500);animation:packet 1.6s linear infinite;
    --travel:-190px}
  .route .wire::before{animation-delay:.8s}
  .route .wire:last-of-type::after{animation-delay:.4s}
  .route .wire:last-of-type::before{animation-delay:1.2s}
  /* the destinations read as live */
  .srv-art img{filter:drop-shadow(0 0 8px rgba(34,201,242,.35))}
  /* One server at a time. All FOURTEEN occupy the SAME grid cell and take turns:
       each is visible for a fourteenth of the 35s cycle with a fade either side.
       Communicates that the relay carries several servers without needing fourteen
       slots of space. Grid rather than absolute positioning so the cell still
       sizes itself to the artwork.
       Every number here derives from the image COUNT in index.html, and per-logo
       pacing is deliberately UNCHANGED from when this was three, then six, then
       twelve, then fifteen: a slot is still 2.5s (35s / 14), and the percentages
       are the old 7.5s ones scaled by 7.5/35 (5%->1.07%, 28%->6%, 36%->7.71%), so
       each logo still fades in over .375s, holds 1.725s and is gone by 2.7s.
       When the count changes: cycle = 2.5s x count, percentages = old-7.5s-value
       x (7.5 / cycle), and one nth-child delay per image at 2.5s steps. Get any
       one wrong and either two logos overlap or the cell goes blank mid-cycle. */
  @keyframes srv-cycle{
    0%{opacity:0;transform:scale(.9)}
    1.07%,6%{opacity:1;transform:scale(1)}
    7.71%,100%{opacity:0;transform:scale(.9)}}
  .srv-art{display:grid;place-items:center;max-width:none}
  /* back to full size: only one is on screen at a time here */
  .srv-art img{grid-area:1/1;width:72px;height:72px;opacity:0;animation:srv-cycle 35s ease-in-out infinite}
  .node-srv .node-art{height:104px}
  .srv-art img:nth-child(2){animation-delay:2.5s}
  .srv-art img:nth-child(3){animation-delay:5s}
  .srv-art img:nth-child(4){animation-delay:7.5s}
  .srv-art img:nth-child(5){animation-delay:10s}
  .srv-art img:nth-child(6){animation-delay:12.5s}
  .srv-art img:nth-child(7){animation-delay:15s}
  .srv-art img:nth-child(8){animation-delay:17.5s}
  .srv-art img:nth-child(9){animation-delay:20s}
  .srv-art img:nth-child(10){animation-delay:22.5s}
  .srv-art img:nth-child(11){animation-delay:25s}
  .srv-art img:nth-child(12){animation-delay:27.5s}
  .srv-art img:nth-child(13){animation-delay:30s}
  .srv-art img:nth-child(14){animation-delay:32.5s}
  /* stacked route: the packets fall from the player down to the servers, which
     is the same story rotated, so they need their own keyframe */
  @media(max-width:620px){
    .route .wire::before,.route .wire::after{top:0;right:-3px;animation-name:packet-v}
  }
  /* 3) buyers marquee already defined above */
  .avatar-box img.show,.champ-render{animation:popin .2s var(--ease-out)}
  .addr-pill button.done{animation:okpop .28s var(--ease-out)}
  /* /leaderboard's ONE ambient element: the champion's rank pill, marking the week
     as still running. Reuses hubflare above — no new keyframe, no new colour.
     Excluded on founder/partner pods, whose pills are gold/red (see §tags below);
     hubflare's shadow is cyan and would fight those. */
}

/* interaction feedback: <=2px displacement, 150-200ms */
.btn-solid{position:relative;overflow:hidden;transition:background .18s ease-out,
  box-shadow .18s ease-out,transform .12s ease-out}
.btn-solid:hover{box-shadow:var(--glow-lg);transform:translateY(-1px)}
.btn-solid:active{transform:scale(.98)}
.btn-solid::after{content:"";position:absolute;top:0;bottom:0;width:45%;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);
  transform:translateX(-160%) skewX(-18deg);pointer-events:none}
.btn-solid:hover::after{transition:transform .22s ease-out;transform:translateX(260%) skewX(-18deg)}
.addr-pill{transition:box-shadow .18s ease-out}
.addr-pill:hover,.addr-pill:focus-within{box-shadow:var(--glow-md)}
a.plan-card,.plan-card{transition:border-color .18s ease-out,transform .18s ease-out,box-shadow .18s ease-out}
a.plan-card:hover,.plan-card:hover{transform:translateY(-2px);box-shadow:var(--glow-sm)}
.plan-card.best{animation:none}
@media (prefers-reduced-motion:no-preference){
  .plan-card.best{box-shadow:var(--glow-sm)}
  .plan-card.best:hover{box-shadow:var(--glow-md)}
}
.lb-champion{transition:box-shadow .18s ease-out,transform .18s ease-out}
.lb-champion:hover{transform:translateY(-2px);box-shadow:var(--glow-sm)}
.card-form input:focus{box-shadow:var(--glow-sm)}
.seg{transition:background .18s ease-out,color .18s ease-out,box-shadow .18s ease-out}
.seg.on{box-shadow:var(--glow-sm)}
/* leaderboard bars fill on reveal — scaleX (GPU) not width (layout) */
.lb-bar i{width:var(--w,0%)}
html.js .lb-bar i{transform:scaleX(0);transform-origin:right}
html.js .lb-bar i.in{transform:scaleX(1);transition:transform .7s var(--ease-out)}

/* ---------- /leaderboard motion (the +9 pass covered landing + store only) ---------- */

/* Rows cascade in rank order. 10px travel, under the 14px used for blocks: rows are
   short and a bigger jump reads as jitter at this density. Stagger delay is set
   per-row from app.js via CSSOM (CSP style-src 'self' forbids style attributes). */
html.js .rv-row{opacity:0;transform:translateY(10px)}
html.js .rv-row.in{opacity:1;transform:none;
  transition:opacity .3s var(--ease-out),transform .3s var(--ease-out)}

/* Hour bar: hairline on the row's bottom edge, deliberately WITHOUT a background
   track — a filled track reads as dashboard clutter. Width comes from data-w via
   CSSOM; the reveal is scaleX (GPU) not width (layout). */
.brow{position:relative;transition:background-color .16s ease-out}
.brow:hover{background-color:rgba(34,201,242,.06)}
.brow:hover .brank{color:var(--cyan-300)}
.brow-bar{position:absolute;inset-inline-start:0;bottom:0;width:100%;height:2px;
  overflow:hidden;pointer-events:none}
/* Flat fill, NOT a gradient: a 6% bar would sit entirely in a gradient's bright
   end, so the least active players would get the most saturated mark while the
   leader's long bar averaged dim. Length alone carries the magnitude. */
.brow-bar i{display:block;height:100%;width:var(--w,0%);background:rgba(34,201,242,.5)}
.brow.lead .brow-bar i{background:rgba(34,201,242,.72)}
/* the board clips its own bottom corners; the bar must not poke out of them */
.board .brow:last-child .brow-bar{border-bottom-left-radius:11px;border-bottom-right-radius:11px}
html.js .brow-bar i{transform:scaleX(0);transform-origin:right}
html.js .brow-bar i.in{transform:scaleX(1);transition:transform .6s var(--ease-out)}

/* Podium reveals 3rd, 2nd, 1st so the winner lands last and slightly heavier.
   The stagger is NOT set here: `transition` is a shorthand that resets
   transition-delay, and a rank-keyed delay rule would tie on specificity with the
   rule below and lose to source order. It also has to flip at the 700px
   breakpoint, where the pods lose their `order` and DOM order becomes visual
   order. app.js sets it per-pod and clears it once the reveal is done, so hover
   never inherits the delay. */
.pod{transition:transform .18s ease-out,box-shadow .18s ease-out}
html.js .pod-1.rv{transform:translateY(14px) scale(.97)}
/* MUST restate transform:none here. The generic `html.js .rv.in` rule above ties
   with `html.js .pod-1.rv` on specificity and loses on source order, so without
   this #1 keeps its start transform and sits 14px lower than #2 and #3. */
html.js .pod.rv.in{opacity:1;transform:none;
  transition:opacity .36s var(--ease-out),transform .22s var(--ease-out),
  box-shadow .18s ease-out}
/* and hover has to out-specify that transform:none, or #1 stops lifting */
html.js .pod.rv.in:hover{transform:translateY(-2px);box-shadow:var(--glow-sm)}
.pod:hover{transform:translateY(-2px);box-shadow:var(--glow-sm)}

@media (prefers-reduced-motion:reduce){
  html.js .rv-row{opacity:1!important;transform:none!important}
  html.js .brow-bar i{transform:none!important;transition:none}
  .pod-1 .pod-rank{animation:none!important}
  html.js .rv{opacity:1!important;transform:none!important}
  .hero-mark .mark,.route .wire::after,.node.hub .node-dot,
  .avatar-box img.show,.champ-render,.addr-pill button.done{animation:none!important}
  html.js .lb-bar i{transform:none!important;transition:none}
}

/* Hypixel identity (their colours are the one sanctioned exception to cyan-only) */
.hx-badge{display:inline-block;font-family:JBMono,monospace;font-weight:700;font-size:.72rem;
  line-height:1.6;padding:0 7px;border-radius:5px;
  background:color-mix(in srgb,currentColor 14%,transparent);
  border:1px solid color-mix(in srgb,currentColor 35%,transparent);
  margin-inline-start:6px;vertical-align:middle}
.hx-lv{font-size:.72rem;color:var(--text-low);margin-inline-start:6px}

.lb-id{display:flex;align-items:center;gap:6px;min-width:0}
.lb-id .lb-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lb-id .hx-badge{flex:0 0 auto;margin:0}
@media(max-width:640px){.lb-id .hx-badge{display:none}}

/* Hypixel chat-style name: [RANK] Username, all in the rank colour */
.hx-name{font-weight:700;white-space:nowrap;color:var(--text-hi)}
.hx-name .hx-tag{color:inherit}
.hx-name .hx-tag b{font-weight:700}
/* A player with no Hypixel rank renders GRAY, because that is what Hypixel
   itself does - a default account's name is §7 GRAY. Rendering them near-white
   made a rankless player look like the brightest name on the board, which is
   backwards: the ranked players are the ones meant to stand out.
   Covers both cases that produce no tag: a real account that never bought a
   rank, and a name Mojang no longer resolves.
   #AAAAAA is Minecraft's exact GRAY, the same value mcColors uses in
   hypixel.go and .hx-c-AAAAAA already carries - NOT a colour of our choosing.
   Contrast measured: 8.06:1 on the page background, 7.49:1 on a card - both
   clear of WCAG AA. */
.hx-name.plain{color:#AAAAAA}
.lb-id{display:flex;align-items:center;min-width:0;overflow:hidden}
.lb-id .hx-name{overflow:hidden;text-overflow:ellipsis}
.champ-name .hx-name{font-size:1.15rem}
.profile-name .hx-name{font-size:1.2rem}


/* Hypixel colours: Minecraft's EXACT palette (CSP forbids inline styles,
   so they ship as classes). Minecraft's own drop shadow gives the dark
   ones edge definition without changing the hue. */
/* Legibility WITHOUT touching the colour (owner rule): Minecraft's own drop
   shadow plus a faint light halo, so genuinely dark ranks like DARK_BLUE
   separate from the navy surface while the fill stays the exact Hypixel hex. */
.hx-name,.hx-badge{text-shadow:1px 1px 0 rgba(0,0,0,.9), 0 0 3px rgba(255,255,255,.45)}
.hx-c-55FFFF{color:#55FFFF}
.hx-p-55FFFF{color:#55FFFF}
.hx-c-000000{color:#000000}
.hx-p-000000{color:#000000}
.hx-c-5555FF{color:#5555FF}
.hx-p-5555FF{color:#5555FF}
.hx-c-00AAAA{color:#00AAAA}
.hx-p-00AAAA{color:#00AAAA}
.hx-c-0000AA{color:#0000AA}
.hx-p-0000AA{color:#0000AA}
.hx-c-555555{color:#555555}
.hx-p-555555{color:#555555}
.hx-c-00AA00{color:#00AA00}
.hx-p-00AA00{color:#00AA00}
.hx-c-AA00AA{color:#AA00AA}
.hx-p-AA00AA{color:#AA00AA}
.hx-c-AA0000{color:#AA0000}
.hx-p-AA0000{color:#AA0000}
.hx-c-FFAA00{color:#FFAA00}
.hx-p-FFAA00{color:#FFAA00}
.hx-c-AAAAAA{color:#AAAAAA}
.hx-p-AAAAAA{color:#AAAAAA}
.hx-c-55FF55{color:#55FF55}
.hx-p-55FF55{color:#55FF55}
.hx-c-FF55FF{color:#FF55FF}
.hx-p-FF55FF{color:#FF55FF}
.hx-c-FF5555{color:#FF5555}
.hx-p-FF5555{color:#FF5555}
.hx-c-FFFFFF{color:#FFFFFF}
.hx-p-FFFFFF{color:#FFFFFF}
.hx-c-FFFF55{color:#FFFF55}
.hx-p-FFFF55{color:#FFFF55}

/* ---- /leaderboard page ---- */
main.lbp{max-width:860px;padding-top:40px;padding-bottom:70px}
main.lbp h1{text-align:center;font-size:clamp(1.7rem,5vw,2.4rem)}
main.lbp .lb-proof{margin-top:26px}
/* podium */
.podium{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;align-items:end;margin:30px 0 26px}
.pod{position:relative;background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;
  padding:18px 12px 16px;text-align:center;
  /* A grid item's min-width is `auto`, i.e. its content, so a repeat(3,1fr)
     podium could not shrink below three nowrap names. Between the 700px phone
     breakpoint and about 820px that pushed the page sideways - measured at 720px
     even before the name became truncatable. */
  min-width:0}
.pod-1{border-color:rgba(34,201,242,.45);box-shadow:var(--glow-sm);order:2}
.pod-2{order:1}.pod-3{order:3}
/* 1>2>3 staircase: the character box steps down by rank. Podium is align-items:end
   (bottom-aligned) so a taller box lifts that pod's head higher. mcview.js sizes the
   SkinViewer from the .mc3d box (clientWidth/Height), so this scales the real 3D;
   .pod-render matches for the no-JS fallback. Applies to every podium (shared). */
.pod-1 .mc3d{width:118px;height:214px}
.pod-2 .mc3d{width:100px;height:182px}
.pod-3 .mc3d{width:86px;height:156px}
.pod-1 .pod-render{width:118px}
.pod-2 .pod-render{width:100px}
.pod-3 .pod-render{width:86px}
.pod-rank{position:absolute;top:-11px;right:50%;transform:translateX(50%);background:var(--navy-700);
  color:var(--text-mid);border:1px solid var(--navy-600);border-radius:999px;
  min-width:26px;height:22px;line-height:20px;font-size:.78rem;font-weight:700;
  /* explicit, NOT inherited: .pod is text-align:center on desktop but
     text-align:right at <=700px, which shoved the digit against the right edge
     of the pill on phones (owner-reported). */
  text-align:center;padding:0 6px}
.pod-1 .pod-rank{background:var(--cyan-500);color:#04222e;border-color:transparent}
.pod-render{width:92px;height:auto;filter:drop-shadow(0 12px 16px rgba(0,0,0,.5))}
/* Flex, not a plain block: the crest and the name are siblings here, and the
   name is the only one that may shrink. As a block with the name inline, the
   truncation below could not work at all - overflow, text-overflow and max-width
   are all ignored on an inline box (hxNameHTML, main.go:179, emits a <span>), so
   a long name pushed the pod, the pod pushed the podium, and the podium pushed
   the whole page sideways. That is the same symptom the comment in the phone
   block below records being fixed once already: the grid half of that fix landed,
   this half silently did not. */
.pod-name{margin-top:10px;font-size:.9rem;overflow:visible;white-space:nowrap;
  display:flex;align-items:baseline;gap:4px;justify-content:center}
/* The identity unit: crests + name, held together and centered. On the
   leaderboard podium it becomes the centered grid track (below); on the donor
   podium (no .pod-id wrapper) .pod-name stays this flex row unchanged. */
.pod-id{display:inline-flex;align-items:baseline;gap:4px;min-width:0;
  white-space:nowrap;justify-content:center}
/* min-width:0 is what actually lets it shrink - a flex item will not go below its
   content width without it, and this content is white-space:nowrap. */
.pod-name .hx-name{min-width:0;overflow:hidden;text-overflow:ellipsis}
@media(min-width:701px){
  /* Center the NAME on the pod's vertical spine (avatar + hours). The identity
     sits in the middle `auto` track; the favorite-server emblem hangs in the
     trailing 1fr gutter and the empty leading 1fr balances it, so adding the
     emblem can never pull the name off-centre. Leaderboard podium only — the
     donor podium has no emblem and a different child set. */
  .podium:not(.donor-podium) .pod-name{display:grid;
    grid-template-columns:1fr auto 1fr;align-items:center;width:100%}
  .podium:not(.donor-podium) .pod-id{grid-column:2}
  .podium:not(.donor-podium) .pod-fav{grid-column:3;justify-self:start}
}
.pod-hours{margin-top:6px;font-size:.82rem;color:var(--text-low)}
.pod-hours .mono{font-size:1.35rem;font-weight:700;color:var(--text-hi)}
.pod-lv{margin-top:2px;font-size:.72rem;color:var(--text-low)}
@media(max-width:700px){
  .podium{grid-template-columns:1fr;gap:10px}
  /* Pods laid out as rank | character | stacked text. This was a flex ROW, but
     rank+character+name+hours+level abreast needs ~417px and only ~358px is
     available at 390px wide, so the card pushed the WHOLE PAGE sideways
     (scrollWidth 455 vs clientWidth 390). Stacking the three text lines gives the
     name real room instead of ellipsising it to nothing.
     minmax(0,1fr) is what actually lets it shrink: .pod-name is white-space:nowrap
     and a grid item's default min-width is its content. Truncation is handled by
     .pod-name .hx-name, which already has overflow:hidden + text-overflow:ellipsis.
     Do NOT put overflow:hidden on .pod-name itself — the founder/partner crest
     tooltip has to escape it (+19). */
  .pod{display:grid;grid-template-columns:auto auto minmax(0,1fr);
    align-items:center;gap:2px 10px;text-align:right;padding:12px 14px}
  .pod-1,.pod-2,.pod-3{order:0}
  .pod-render,.pod-1 .pod-render,.pod-2 .pod-render,.pod-3 .pod-render{width:54px}
  /* neutralise the desktop staircase — stacked rows stay uniform on phones */
  .pod-1 .mc3d,.pod-2 .mc3d,.pod-3 .mc3d{width:54px;height:98px}
  .pod-rank{position:static;transform:none;grid-column:1;grid-row:1/4}
  .mc3d{grid-column:2;grid-row:1/4}
  .pod-name{grid-column:3;grid-row:1;min-width:0;justify-content:flex-start}
  .pod-hours{grid-column:3;grid-row:2}
  .pod-lv{grid-column:3;grid-row:3}
  .pod-name,.pod-hours,.pod-lv{margin:0}
  /* The donor podium carries DIFFERENT children: .pod-amt, .pod-tier and
     .donor-msg where the leaderboard has .pod-hours and .pod-lv. They matched no
     placement rule above, so they auto-placed into whatever cell was free -
     landing in column 1 and inflating it from ~38px to 104px, which squeezed the
     name column to 22px and pushed the pod, the podium and the whole page
     sideways. Placing them explicitly is what actually fixes the overflow; making
     the name truncate only hid it by ellipsising the name to nothing. */
  .donor-podium .pod-rank,.donor-podium .mc3d{grid-row:1/span 4}
  .donor-podium .pod-name,.donor-podium .pod-amt,
  .donor-podium .pod-tier,.donor-podium .donor-msg{grid-column:3}
  .donor-podium .pod-amt{grid-row:2}
  .donor-podium .pod-tier{grid-row:3}
  .donor-podium .donor-msg{grid-row:4;margin:0}
  /* .board .brow, not .brow: the base rule at ~1076 is BELOW this media block
     and carries the same specificity, so it won every width and this override
     had never applied since the day it was written - phones were rendering the
     desktop column set, which left the name 109px and truncated every player on
     the board to their rank tag alone («[MVP…»). One class deeper makes order
     irrelevant. */
  .board .brow{grid-template-columns:1.8em 32px minmax(0,1fr) auto;gap:10px;padding:12px 14px}
  /* .blv joins .bmeta in hiding. It costs 42px of a 153px name cell, and a
     leaderboard whose names read «[MVP…» has stopped being a leaderboard.
     Without it four of five names fit completely and the fifth truncates to
     something identifiable. The level is still on the podium for the top three,
     and returns with the rest of the row above 700px. */
  .bmeta,.board .brow .blv{display:none}
}
.lb-all{color:var(--cyan-300);font-size:.92rem;border-bottom:1px solid rgba(34,201,242,.35);padding-bottom:2px}

/* leaderboard board: rows with rhythm, one hero number each */
.hx-name{unicode-bidi:isolate}   /* Latin name inside RTL copy must not reorder */
.board{list-style:none;padding:0;margin:26px 0 0;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:12px}
.board .brow:first-child{border-top-left-radius:11px;border-top-right-radius:11px}
.board .brow:last-child{border-bottom-left-radius:11px;border-bottom-right-radius:11px}
.brow{display:grid;grid-template-columns:2.2em 32px minmax(0,1fr) auto auto;align-items:center;
  gap:14px;padding:13px 20px;border-bottom:1px solid var(--navy-700)}
.brow:last-child{border-bottom:0}
.brow.lead{background-color:rgba(34,201,242,.045)}
.brank{color:var(--text-low);font-weight:700;font-size:.9rem;text-align:center}
.brow.lead .brank{color:var(--cyan-300)}
.bface{width:32px;height:32px;image-rendering:pixelated;border-radius:7px}
.bwho{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden;font-size:.95rem}
.bwho .hx-name{overflow:hidden;text-overflow:ellipsis}
.blv{font-size:.72rem;color:var(--text-low);flex:0 0 auto}
/* Favorite-server tag: a small rounded game logo — the game a player logged the most
   weekly playtime on. Logo only; the game name is the img title (hover). The flex
   rows (.bwho/.lb-id) center + gap-space it; inline names (.pod/.champ) take a margin. */
/* One favorite-server emblem, four surfaces. flex:0 0 auto keeps its size in the
   flex/grid rows; align-self:center (flex/grid) + vertical-align:middle (any inline
   fallback) sit it on the text centre. Spacing comes from the parent gap / grid
   gutter, not a per-emblem margin, so it stays symmetric. */
.bfav,.lb-fav,.pod-fav,.champ-fav{border-radius:5px;object-fit:contain;opacity:.9;
  flex:0 0 auto;align-self:center;vertical-align:middle}
.bfav{width:18px;height:18px}
.lb-fav{width:16px;height:16px}
.pod-fav{width:20px;height:20px}
.champ-fav{width:20px;height:20px}
.bmeta{font-size:.76rem;color:var(--text-low);white-space:nowrap}
.bmeta .bdot{opacity:.5;margin:0 4px}
.bhours{display:flex;align-items:baseline;gap:5px;justify-content:flex-end;min-width:76px}
.bhours .mono{font-size:1.15rem;font-weight:700;color:var(--text-hi)}
.bhours small{font-size:.72rem;color:var(--text-low)}

/* ---- our own tags: founders + partnered YouTubers (never Hypixel data) ---- */
.crest{position:relative;z-index:5;display:inline-flex;align-items:center;justify-content:center;
  width:15px;height:15px;flex:0 0 15px;margin-inline-end:5px;vertical-align:-2px;cursor:help}
.crest svg{width:100%;height:100%}
.crest-founder svg{fill:#FFD54A;filter:drop-shadow(0 0 5px rgba(255,199,64,.85))}
.crest-yt svg{fill:#FF3D3D;filter:drop-shadow(0 0 5px rgba(255,61,61,.6))}
/* Early Access tester. A distinct hue from the founder gold and the partner red
   so all three read apart when stacked beside one name. Colour lives here, never
   in a style attribute: the CSP is style-src 'self' and inline styles are
   dropped silently, which would look correct locally and wrong for everyone. */
.crest-tester svg{fill:#5FD0FF;filter:drop-shadow(0 0 5px rgba(95,208,255,.7))}
.crest-tester:focus-visible{outline:2px solid #5FD0FF;outline-offset:2px;border-radius:3px}
.crest-founder:focus-visible{outline:2px solid #FFD54A;outline-offset:2px;border-radius:3px}
.crest-yt:focus-visible{outline:2px solid #FF3D3D;outline-offset:2px;border-radius:3px}
/* «پلاس» crest: the Plus mark itself (the 16px mark-plus-s.svg, plusEmblem in
   leaderboard.go), one pixel larger than the 15px icon crests so the heavier
   «+» stays legible at row size. No pill, no border, no text: the tier's mark
   is the one glyph every Plus surface shares, and the tooltip carries the word.
   The gold lives in the svg; nothing here colours a .row- class. */
.crest-plus{width:16px;height:16px;flex:0 0 16px;padding:0;border:0;background:none;cursor:help}
.crest-plus img{width:100%;height:100%;display:block}
.crest-plus:focus-visible{outline:2px solid var(--gold-500);outline-offset:2px;border-radius:4px}
/* tooltip: CSS only, above the glyph, RTL, never clipped (position:fixed-like escape) */
.crest::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 7px);right:50%;
  max-width:min(60vw,220px);overflow-wrap:anywhere;
  transform:translateX(50%) translateY(3px);white-space:nowrap;direction:rtl;
  background:var(--navy-700);color:var(--text-hi);border:1px solid var(--navy-600);
  border-radius:7px;padding:4px 10px;font-family:Vazirmatn,sans-serif;font-size:.74rem;
  font-weight:400;line-height:1.6;box-shadow:0 6px 18px rgba(0,0,0,.5);
  opacity:0;pointer-events:none;transition:opacity .15s ease-out,transform .15s ease-out;z-index:60}
/* hover is not enough: touch devices cannot hover, so tap/click (:focus) and
   keyboard (:focus-visible) must reveal it too. */
.crest:hover::after,.crest:focus::after,.crest:focus-visible::after,
.crest:active::after{opacity:1;transform:translateX(50%) translateY(0)}
/* avatar rings */
.bface.is-founder,.lb-avatar.is-founder{box-shadow:0 0 0 2px #FFD54A,0 0 12px rgba(255,199,64,.5)}
.bface.is-partner,.lb-avatar.is-partner{box-shadow:0 0 0 2px #FF3D3D,0 0 12px rgba(255,61,61,.45)}
.pod-render.is-founder{filter:drop-shadow(0 12px 16px rgba(0,0,0,.5)) drop-shadow(0 0 10px rgba(255,199,64,.45))}
.pod-render.is-partner{filter:drop-shadow(0 12px 16px rgba(0,0,0,.5)) drop-shadow(0 0 10px rgba(255,61,61,.45))}
/* donator glow (violet) — same treatment as founder/partner, used when donator is
   the player's top rank (glowrank). Owner/youtuber outrank it, so decisiv3 glows
   gold, not violet. */
/* حامی/قهرمان stay brand cyan; violet is EXCLUSIVE to افسانه (t3). */
.bface.is-donor-t1,.lb-avatar.is-donor-t1{box-shadow:0 0 0 2px rgba(255,255,255,.8),0 0 7px rgba(255,255,255,.3)}
.bface.is-donor-t2,.lb-avatar.is-donor-t2{box-shadow:0 0 0 2px var(--cyan-300),0 0 14px rgba(127,227,250,.55)}
.bface.is-donor-t3,.lb-avatar.is-donor-t3{box-shadow:0 0 0 2px var(--violet-500),0 0 12px rgba(139,92,246,.5)}
.pod-render.is-donor-t1{filter:drop-shadow(0 12px 16px rgba(0,0,0,.5)) drop-shadow(0 0 8px rgba(255,255,255,.32))}
.pod-render.is-donor-t2{filter:drop-shadow(0 12px 16px rgba(0,0,0,.5)) drop-shadow(0 0 12px rgba(127,227,250,.55))}
.pod-render.is-donor-t3{filter:drop-shadow(0 12px 16px rgba(0,0,0,.5)) drop-shadow(0 0 10px rgba(139,92,246,.5))}
/* rows must not clip the tooltip */
.brow,.lb-rest .lb-row{overflow:visible}
.bwho,.lb-id{overflow:visible}
.bwho .hx-name,.lb-id .hx-name{overflow:hidden;text-overflow:ellipsis}

/* ---- tagged players: the whole row carries their colour ---- */
/* RTL: the row starts on the right, so the wash fades right -> left. */
.row-founder{
  background-image:linear-gradient(to left,rgba(255,213,74,.14),rgba(255,213,74,.05) 42%,transparent 72%);
  box-shadow:inset -3px 0 0 0 #FFD54A;
}
.row-partner{
  background-image:linear-gradient(to left,rgba(255,61,61,.14),rgba(255,61,61,.05) 42%,transparent 72%);
  box-shadow:inset -3px 0 0 0 #FF3D3D;
}
/* a tagged row wins over the generic top-3 tint */
.brow.lead.row-founder,.brow.lead.row-partner{background-color:transparent}
.brow.lead.row-founder{background-image:linear-gradient(to left,rgba(255,213,74,.14),rgba(255,213,74,.05) 42%,transparent 72%)}
.brow.lead.row-partner{background-image:linear-gradient(to left,rgba(255,61,61,.14),rgba(255,61,61,.05) 42%,transparent 72%)}
/* podium + champion cards: full-card wash, border picks up the colour */
.pod.row-founder,.lb-champion.row-founder{border-color:rgba(255,213,74,.5);
  background-image:linear-gradient(180deg,rgba(255,213,74,.13),transparent 60%);
  box-shadow:0 0 26px rgba(255,199,64,.12)}
.pod.row-partner,.lb-champion.row-partner{border-color:rgba(255,61,61,.5);
  background-image:linear-gradient(180deg,rgba(255,61,61,.13),transparent 60%);
  box-shadow:0 0 26px rgba(255,61,61,.12)}
.pod.row-founder .pod-rank{background:#FFD54A;color:#3a2c00;border-color:transparent}
.pod.row-partner .pod-rank{background:#FF3D3D;color:#2c0000;border-color:transparent}
/* donator rows/podium/champion glow — cyan for حامی/قهرمان, violet only for افسانه */
.row-donor-t1{
  background-image:linear-gradient(to left,rgba(255,255,255,.08),transparent 68%);
  box-shadow:inset -3px 0 0 0 rgba(255,255,255,.65);
}
.row-donor-t2{
  background-image:linear-gradient(to left,rgba(127,227,250,.16),rgba(127,227,250,.05) 42%,transparent 72%);
  box-shadow:inset -3px 0 0 0 var(--cyan-300);
}
.row-donor-t3{
  background-image:linear-gradient(to left,rgba(139,92,246,.15),rgba(139,92,246,.05) 42%,transparent 72%);
  box-shadow:inset -3px 0 0 0 var(--violet-500);
}
.brow.lead.row-donor-t1{background-color:transparent;
  background-image:linear-gradient(to left,rgba(255,255,255,.08),transparent 68%)}
.brow.lead.row-donor-t2{background-color:transparent;
  background-image:linear-gradient(to left,rgba(127,227,250,.16),rgba(127,227,250,.05) 42%,transparent 72%)}
.brow.lead.row-donor-t3{background-color:transparent;
  background-image:linear-gradient(to left,rgba(139,92,246,.15),rgba(139,92,246,.05) 42%,transparent 72%)}
.pod.row-donor-t1,.lb-champion.row-donor-t1{border-color:rgba(255,255,255,.38);
  background-image:linear-gradient(180deg,rgba(255,255,255,.08),transparent 60%);
  box-shadow:0 0 20px rgba(255,255,255,.09)}
.pod.row-donor-t2,.lb-champion.row-donor-t2{border-color:rgba(127,227,250,.55);
  background-image:linear-gradient(180deg,rgba(127,227,250,.15),transparent 60%);
  box-shadow:0 0 28px rgba(127,227,250,.18)}
.pod.row-donor-t3,.lb-champion.row-donor-t3{border-color:rgba(139,92,246,.5);
  background-image:linear-gradient(180deg,rgba(139,92,246,.14),transparent 60%);
  box-shadow:0 0 26px rgba(139,92,246,.14)}
/* Donor podium position = HEIGHT + a neutral cyan number chip. Force cyan even
   when the player is owner/youtuber (whose rank recolours the chip elsewhere), so
   the only rank colour on the donor podium is the glow — no gold #1 clash. */
.donor-podium .pod .pod-rank,
.donor-podium .pod.row-founder .pod-rank,
.donor-podium .pod.row-partner .pod-rank{background:var(--cyan-500);color:#04222e;border-color:transparent}
/* name glow — the fourth glow element (round-1 ask), tinted per rank family */
.row-founder .hx-name{text-shadow:0 0 8px rgba(255,199,64,.55)}
.row-partner .hx-name{text-shadow:0 0 8px rgba(255,61,61,.5)}
.row-donor-t1 .hx-name{text-shadow:0 0 7px rgba(255,255,255,.4)}
.row-donor-t2 .hx-name{text-shadow:0 0 9px rgba(127,227,250,.6)}
.row-donor-t3 .hx-name{text-shadow:0 0 8px rgba(139,92,246,.6)}

/* ---- interactive 3D character (skinview3d canvas) ---- */
.mc3d{position:relative;display:block;width:110px;height:200px;margin:0 auto;
  touch-action:none;cursor:grab;outline-offset:4px}
.mc3d.grab{cursor:grabbing}
.mc3d canvas{display:block;width:100%;height:100%;background:transparent}
.mc3d > img{width:100%;height:100%;object-fit:contain;
  filter:drop-shadow(0 12px 16px rgba(0,0,0,.5))}
.mc3d.ready > img{display:none}
/* Contact shadow on the ground. ::before is painted before the canvas, so the
   character's feet sit on top of it. Only once the model is up — an ellipse
   floating under an empty box looks like a bug. */
.mc3d.ready::before{content:"";position:absolute;left:50%;bottom:8px;
  width:68%;height:16px;transform:translateX(-50%);border-radius:50%;pointer-events:none;
  background:radial-gradient(ellipse at center,rgba(0,0,0,.68) 0%,rgba(0,0,0,.34) 42%,transparent 70%)}
.mc3d.is-founder canvas{filter:drop-shadow(0 0 10px rgba(255,199,64,.45))}
.mc3d.is-partner canvas{filter:drop-shadow(0 0 10px rgba(255,61,61,.45))}
.mc3d.is-donor-t1 canvas{filter:drop-shadow(0 0 9px rgba(255,255,255,.35))}
.mc3d.is-donor-t2 canvas{filter:drop-shadow(0 0 12px rgba(127,227,250,.55))}
.mc3d.is-donor-t3 canvas{filter:drop-shadow(0 0 10px rgba(139,92,246,.5))}
@media(max-width:700px){.mc3d{width:88px;height:160px}}

/* ---------- accounts (signup / login / forgot / account) ---------- */
.auth-wide{max-width:980px;margin:0 auto;padding:44px 20px 80px}
.auth-narrow{max-width:480px;margin:0 auto;padding:56px 20px 90px}
.auth-head{margin-bottom:30px;max-width:620px}
.auth-head h1{font-size:clamp(1.8rem,4.5vw,2.6rem)}
.auth-lead{margin-top:10px;color:var(--text-mid)}

/* Guide beside the field it feeds, so the form is never below the fold. The
   form column is the wider of the two: it is the one being used. */
.auth-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:22px;align-items:start}
.auth-panel{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:16px;padding:26px 24px}
.auth-panel.solo{padding:30px 26px}
.panel-head{margin-bottom:22px}
.panel-head h1{font-size:1.6rem}
.panel-head p{margin-top:8px;font-size:.92rem;color:var(--text-low);line-height:1.9}
.auth-panel .card-form{margin-top:0;background:none;border:0;padding:0}

.auth-alt{margin-top:20px;padding-top:18px;border-top:1px solid var(--navy-700);
  text-align:center;font-size:.88rem;color:var(--text-low)}
.auth-alt a{color:var(--cyan-500)}
.alt-sep{margin:0 8px;color:var(--navy-600)}

.inline-addr{display:flex;align-items:center;justify-content:space-between;gap:10px;
  background:var(--navy-900);border:1px solid var(--navy-600);border-radius:12px;
  padding:8px 8px 8px 14px;margin-bottom:20px}
.inline-addr .mono{color:var(--cyan-300);font-size:.95rem}

@media(max-width:820px){
  .auth-split{grid-template-columns:1fr;gap:18px}
  .auth-wide{padding-top:32px}
}

/* The steps card teaches the flow before the form asks for its result. */
.steps-card{list-style:none;margin:0;padding:0;display:grid;gap:12px}
.steps-card li{display:flex;gap:14px;align-items:flex-start;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:14px;padding:16px 18px}
.step-n{flex:none;width:28px;height:28px;border-radius:50%;
  display:grid;place-items:center;background:var(--navy-700);color:var(--cyan-500);
  font-family:JBMono,monospace;font-size:.85rem;font-weight:700}
.steps-card .step-t{font-weight:700;color:var(--text-hi);font-size:.95rem}
.steps-card .step-d{margin-top:4px;font-size:.82rem;color:var(--text-low);line-height:1.9}
.steps-card .addr{margin-top:8px;font-size:1rem;color:var(--cyan-300);letter-spacing:.02em}
.steps-card .copy-btn{margin-top:8px;font-size:.8rem;padding:6px 14px}

/* The code is retyped from a kick screen, so give it room and spacing. */
.code-input{text-transform:uppercase;letter-spacing:.28em;font-size:1.25rem;text-align:center}

.verified-chip{display:inline-flex;align-items:center;gap:6px;margin-top:8px;
  background:rgba(46,204,113,.12);border:1px solid rgba(46,204,113,.35);
  color:#7ee2a8;font-size:.76rem;border-radius:999px;padding:4px 12px}
.verified-chip::before{content:"✓";font-weight:700}

/* Neutral state pill: same shape as .verified-chip, but a locked cracked name is
   not a success — it never gets the accent or the green. */
.locked-chip{display:inline-flex;align-items:center;gap:6px;margin-top:8px;
  background:rgba(132,148,171,.12);border:1px solid rgba(132,148,171,.35);
  color:#8494AB;font-size:.76rem;border-radius:999px;padding:4px 12px}

.logout-form{margin-top:28px;text-align:center}
@media(max-width:700px){
  .code-input{font-size:1.05rem;letter-spacing:.2em}
}
.buying-for{display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:12px;padding:12px 14px;margin-bottom:6px}
.buying-for .bf-lbl{font-size:.85rem;color:var(--text-low)}
.buying-for .bf-name{color:var(--text-hi);font-weight:700}
.verified-chip.small{margin-top:0;font-size:.7rem;padding:2px 9px}
/* The server address is an LTR string inside an RTL card: as bare text it drifts
   to the far edge and reads as unrelated to its step. A pill keeps it attached. */
.steps-card .addr{display:inline-block;background:var(--navy-900);border:1px solid var(--navy-600);
  border-radius:10px;padding:8px 14px;margin-top:10px}

/* Header, small screens. The brand and the identity chip are the two things a
   phone user needs on the first row; section links move to their own line and
   scroll sideways rather than stacking into a four-line wall. */
@media(max-width:820px){
  /* One compact row, about 56px. The links used to sit on a second full-width
     row, which cost 112px of a phone screen for the whole scroll once the bar
     became sticky. They live in the menu now. */
  .site-head{flex-direction:row;flex-wrap:nowrap;gap:10px;padding:9px 14px}
  .mark{height:34px;width:auto}
  .brand{gap:8px}
  .nav-menu{display:block;position:relative;margin-inline-end:auto}
  .nav-toggle{display:grid;place-items:center;width:38px;height:38px;cursor:pointer;
    list-style:none;color:var(--text-mid);border:1px solid var(--navy-600);
    border-radius:10px;background:var(--navy-800)}
  .nav-toggle::-webkit-details-marker{display:none}
  .nav-menu[open] .nav-toggle{color:var(--cyan-300);border-color:var(--cyan-700)}
  .nav-i-close{display:none}
  .nav-menu[open] .nav-i-open{display:none}
  .nav-menu[open] .nav-i-close{display:block}
  /* Absolute so opening the menu overlays the page instead of shoving it down. */
  .head-links{position:absolute;inset-inline-start:0;top:calc(100% + 10px);
    flex-direction:column;align-items:flex-end;gap:2px;margin:0;min-width:184px;
    background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;
    padding:8px;box-shadow:0 18px 40px rgba(0,0,0,.5)}
  .head-links a{width:100%;padding:9px 12px;border-radius:8px;font-size:.92rem}
  .head-links a:hover{background:var(--navy-700)}
  /* .head-links sets display:flex, which overrides the UA rule that hides a
     closed <details>'s children, so the panel has to be hidden explicitly. */
  .nav-menu:not([open]) .head-links{display:none}
  /* Same reasoning, smaller numbers: mobile is 63px expanded, 59 condensed. */
  #how,#leaderboard,#plans{scroll-margin-top:76px}
}
@media(max-width:420px){
  .acct-name{display:none}          /* the face still says who you are */
  .acct-chip{padding:5px 6px;border-radius:12px}
  .site-head .cta{padding:8px 14px;font-size:.9rem}
}

/* Gift warning. Amber, not red: this is a consequence to understand before
   paying, not an error the customer has already made. */
.gift-warn{background:rgba(255,176,32,.07);border:1px solid rgba(255,176,32,.32);
  border-radius:12px;padding:14px 16px;margin-top:14px}
.gift-warn .gw-head{font-weight:700;color:var(--warn);font-size:.9rem;margin-bottom:6px}
.gift-warn p{font-size:.84rem;color:var(--text-mid);line-height:1.95}
.gift-warn p + p{margin-top:8px}
.check-row{display:flex;align-items:flex-start;gap:10px;margin-top:14px;cursor:pointer;
  font-size:.87rem;color:var(--text-mid);line-height:1.8}
.check-row input{flex:none;width:18px;height:18px;margin-top:4px;accent-color:var(--cyan-500);cursor:pointer}



/* An author `display` beats the UA stylesheet's [hidden]{display:none}, so any
   element we style AND toggle needs this or it never actually hides. */
[hidden]{display:none!important}

/* The recipient fields are wrapped in a div so they can be toggled as a unit,
   but .card-form is a flex column and a wrapper would drop its inputs out of
   that layout (they collapse to intrinsic width). display:contents keeps the
   wrapper for scripting while the fields stay flex items of the form. */
#recipient-block{display:contents}

/* ---------- header account menu ---------- */
/* <details> carries open/close and keyboard support natively, so the chevron
   is honest: it opens a real menu instead of decorating a link. */
.acct-menu{position:relative}
.acct-menu summary{list-style:none;cursor:pointer}
.acct-menu summary::-webkit-details-marker{display:none}
.acct-caret{width:14px;height:14px;color:var(--text-low);flex:none;transition:transform .18s ease}
.acct-menu[open] .acct-caret{transform:rotate(180deg)}
.acct-menu[open] .acct-chip{border-color:var(--cyan-700);background:var(--navy-700)}

.acct-drop{position:absolute;top:calc(100% + 8px);inset-inline-start:0;min-width:170px;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:12px;
  padding:6px;z-index:40;box-shadow:0 16px 40px rgba(0,0,0,.5)}
/* 44px rows: these are tapped on phones, and the guideline is a floor not a target. */
.acct-drop a,.acct-drop button{display:flex;align-items:center;width:100%;min-height:44px;
  padding:0 14px;border-radius:8px;background:none;border:0;cursor:pointer;
  font-family:inherit;font-size:.92rem;color:var(--text-mid);text-align:start}
.acct-drop a:hover,.acct-drop button:hover{background:var(--navy-700);color:var(--text-hi)}
/* line-height too: a <button> does not inherit it, so it computed as `normal`
   against the anchors' 26.5px and its icon sat 0.53em above the baseline where
   every other row is at 0.48. A row that looks identical and measures
   differently is exactly how this drifts. */
.acct-drop a,.acct-drop button{gap:10px;line-height:inherit}
.acct-drop .ic{width:16px;height:16px;flex:none;color:var(--text-low);transition:color .2s}
.acct-drop a:hover .ic,.acct-drop button:hover .ic{color:var(--cyan-500)}
.acct-admin .ic{color:var(--cyan-500)}
/* The only DIRECTIONAL glyph in the set, and it is drawn for LTR: door on the
   left, arrow leaving to the right. In an RTL layout "out" is leftward, so
   unmirrored it reads as an arrow going back INTO the door. Carries the shared
   baseline nudge too, because transform is one property: listing this element in
   the shared rule as well would have one silently overwrite the other. */
.acct-drop button .ic{transform:translateY(0.25em) scaleX(-1)}
.acct-admin{color:var(--cyan-500);font-weight:700}
.acct-drop form{margin:0}
.acct-drop button{color:var(--text-low)}

/* ---------- gift panel ---------- */
/* Was a ~20px underlined text link, which failed the 44px touch minimum and read
   as a stray link rather than an offer. */
.gift-panel{background:var(--navy-800);border:1px solid var(--navy-600);border-radius:16px;
  padding:18px 20px;margin-top:20px;text-align:start}
.gift-head{display:flex;align-items:center;gap:10px}
.gift-mark{width:22px;height:22px;fill:var(--cyan-500);flex:none}
.gift-mark .gift-ribbon{fill:var(--navy-800)}
.gift-title{font-weight:700;color:var(--text-hi);font-size:1rem}
.gift-note{margin-top:6px;font-size:.86rem;color:var(--text-low);line-height:1.9}
.gift-btn{margin-top:14px;display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 26px}

/* ================= ADMIN PANEL =================
   An operations console, not a marketing page. The design brief that follows
   from that: dense over airy, predictable over asymmetric, and motion only when
   something changed. Numbers are the content, so every number is monospaced.

   Status colour is reserved for status. Nothing here is coloured to decorate,
   because if amber can mean "brand accent" it stops meaning "look at this".

   One radius scale (10px panels, 6px controls, 999px pills) and one spacing
   rhythm. The public site's tokens are reused so the two feel like one product,
   but none of its layout is: this is a fixed sidebar and a dense main column. */

body.adm{background:var(--navy-900);color:var(--text-mid);direction:ltr;text-align:left;
  font-size:14px;-webkit-font-smoothing:antialiased}
body.adm a{color:var(--cyan-300)}

/* --- sidebar --- */
/* Fixed rather than scrolling: an operator jumps between sections constantly,
   and a nav that moves under the cursor costs a click every time. */
.adm-side{position:fixed;inset-block:0;inset-inline-start:0;width:208px;background:var(--navy-800);
  border-inline-end:1px solid var(--navy-700);display:flex;flex-direction:column;padding:18px 12px;z-index:20}
.adm-mark{display:flex;align-items:center;gap:9px;padding:0 8px 18px;color:var(--text-hi)}
.adm-mark .mark{width:26px;height:26px}
.adm-mark span{font-family:JBMono,monospace;font-size:.72rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-low)}
.adm-side nav{display:flex;flex-direction:column;gap:2px}
/* Group labels break the sidebar into Overview/Money/Access/Infra/System. */
.adm-navgroup{font-size:.6rem;text-transform:uppercase;letter-spacing:.13em;font-weight:700;
  color:var(--text-low);opacity:.65;margin:14px 12px 4px}
.adm-navgroup:first-of-type{margin-top:4px}
.adm-side nav a{display:flex;align-items:center;gap:9px;min-height:36px;padding:0 12px;border-radius:var(--r-ctl);
  color:var(--text-low);font-size:.88rem;transition:background .15s,color .15s}
.adm-side nav a:hover{background:var(--navy-700);color:var(--text-mid)}
/* Icons follow the link's text colour (low -> mid on hover -> hi when active). */
.adm-side nav a .ic{width:16px;height:16px;flex:none}
.ic{stroke:currentColor;stroke-width:1.7;fill:none;stroke-linecap:round;stroke-linejoin:round}

/* Icons beside text: the fold rows sit a quarter-em too high.
 *
 * Measured against the text BASELINE (a zero-width inline strut, so this reads
 * the real baseline rather than trusting font metrics), the same .ic lands in
 * two different places:
 *
 *     .sec-head          icon centre 0.494em above the baseline
 *     .acct-fold summary icon centre 0.744em above the baseline
 *
 * A quarter of an em apart, which is what reads as "not on the same level".
 * 0.494em is the correct one: it matches the painted ink centre of «اتصال» to
 * within 0.2px, and «اتصال» has no descenders, so its ink IS the base band.
 *
 * Two things that look like fixes and are not. `align-items:baseline` does not
 * work here: a flex item's synthesized baseline left these icons at 0.94em and
 * 1.09em, higher and still disagreeing. And aligning to a word's painted ink is
 * worse than the bug, because Persian descenders (ر ز ی ج) drag the ink centre
 * down per word, so «رمز» and «اتصال» would pull the icon to different heights.
 * A reader's sense of "same level" is baseline-relative, not ink-relative.
 *
 * So: one measured correction. Expressed in em so it holds if the row's
 * font-size changes, and listed once for every context that needs it rather
 * than pasted as a magic number in four places. Everything here is an .ic beside
 * BODY-sized text (line-height 1.8); .sec-head is h2 (1.4) and is already right,
 * which is why it is absent from this list. */
.acct-fold summary .ic,
.foot-nav a .ic,
.acct-drop a .ic,
.adm-side nav a .ic{transform:translateY(0.25em)}
/* .acct-drop button .ic is deliberately NOT in that list: it needs a mirror as
   well, and a second rule with the same specificity later in the file would
   silently replace the nudge rather than add to it. One rule owns both. */
/* Current section: a filled slab, not a coloured word. Colour alone is a weak
   signal on a dark ground and fails for anyone with low colour vision. */
.adm-side nav a.on{background:var(--navy-700);color:var(--text-hi);box-shadow:inset 2px 0 0 var(--cyan-500)}
.adm-pill{margin-inline-start:auto;background:var(--cyan-500);color:#04222e;font-family:JBMono,monospace;
  font-size:.7rem;font-weight:700;border-radius:999px;padding:1px 7px}
.adm-out{margin-top:auto;padding-top:14px;border-top:1px solid var(--navy-700)}
.adm-out button{display:flex;flex-direction:column;gap:2px;width:100%;min-height:40px;padding:4px 12px;
  background:none;border:0;border-radius:6px;color:var(--text-low);font-family:inherit;font-size:.78rem;
  text-align:start;cursor:pointer}
.adm-out button:hover{background:var(--navy-700);color:var(--text-hi)}
.adm-out span{color:var(--text-mid);font-size:.82rem}

.adm-main{margin-inline-start:208px;max-width:1180px;padding:26px 30px 70px}
@media(max-width:820px){
  .adm-side{position:static;width:auto;inset:auto;flex-direction:row;align-items:center;gap:10px;
    padding:10px 14px;border-inline-end:0;border-bottom:1px solid var(--navy-700);overflow-x:auto}
  .adm-mark{padding:0}
  .adm-side nav{flex-direction:row}
  /* Group labels only make sense in the vertical rail; hide them in the
     horizontal mobile strip so the items stay in one scrollable row. */
  .adm-navgroup{display:none}
  .adm-out{margin:0 0 0 auto;padding:0;border:0}
  .adm-main{margin-inline-start:0;padding:18px 14px 60px}
}

/* --- metrics strip --- */
/* Deliberately NOT five bordered cards. At this density a row of boxes is chrome
   competing with the numbers inside it; hairlines separate them for free. */
.adm-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(184px,1fr));
  background:var(--navy-800);border:1px solid var(--navy-700);border-radius:10px;margin-bottom:22px}
.adm-card{padding:16px 18px;border-inline-start:1px solid var(--navy-700)}
.adm-card:first-child{border-inline-start:0}
.adm-k{font-size:.68rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text-low)}
.adm-v{font-family:JBMono,monospace;font-size:1.7rem;color:var(--text-hi);margin-top:6px;line-height:1.1}
.adm-v.small{font-size:.88rem;color:var(--text-mid)}
/* Money figures run long (73,665,000) and clip the 1.7rem cells; a slightly
   smaller nowrap size keeps them whole in a card without shrinking the count
   stats next to them. Paired with the wider min column above. */
.adm-v.money{font-size:1.35rem;white-space:nowrap;letter-spacing:-.02em}
.adm-sub{font-size:.72rem;color:var(--text-low);margin-top:6px;line-height:1.5}
/* Needs-attention is a STATE, not a link: amber, never the cyan accent. */
.adm-card.attn .adm-v{color:var(--warn)}
.adm-card.warn .adm-v{color:var(--warn)}

/* Status pill for a banner headline (e.g. the boost lane's Withheld state).
   Modifier-only: .adm-pill bare is already the cyan nav count badge above, so
   these override every property it sets rather than redefining the base — a
   bad/warn/ok state must never inherit that cyan accent. */
.adm-pill.ok,.adm-pill.warn,.adm-pill.bad{margin-inline-start:0;margin-inline-end:6px;
  display:inline-block;background:none;font-family:inherit;font-size:.68rem;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;padding:2px 8px;border-radius:var(--r-pill)}
.adm-pill.ok{color:var(--ok);border:1px solid var(--ok)}
.adm-pill.warn{color:var(--warn);border:1px solid var(--warn)}
.adm-pill.bad{color:var(--bad);border:1px solid var(--bad)}
/* Neutral "off" state (Off list): bare .adm-pill is the cyan accent badge, and a
   state pill must never use the accent colour. Self-contained rather than
   sharing the .ok/.warn/.bad base above (its own font-family/background are
   spelled out explicitly instead of inherited), but display:inline-block is
   carried over from that base so it overrides the bare selector the same way. */
.adm-pill.off{margin-inline-start:0;margin-inline-end:6px;display:inline-block;background:transparent;
  color:var(--text-mid);border:1px solid var(--navy-600);font-family:JBMono,monospace;font-size:.68rem;
  font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:2px 8px;border-radius:var(--r-pill)}
.adm-field{display:grid;gap:4px;margin:10px 0}
.adm-field label{font-size:.8rem;color:var(--text-mid)}

/* "Why They Have Access" list on /admin/customer: plain rows, no bullets — each
   line already opens with a state pill, which is signal enough. */
.adm-list{list-style:none;margin:8px 0 0;padding:0;display:grid;gap:6px}
.adm-list li{font-size:.9rem;color:var(--text-mid)}

/* --- page header (shell pages) --- */
/* Every page gets a real title, so a page has a top instead of opening straight
   into a dim section label. The action slot sits opposite for page-level buttons. */
.adm-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-4);
  margin-bottom:var(--sp-5);padding-bottom:var(--sp-4);border-bottom:1px solid var(--border)}
.adm-head h1{font-size:var(--fs-h1);color:var(--text-hi);line-height:1.2;font-weight:700}
.adm-head .adm-sub{font-size:.85rem;color:var(--text-low);margin-top:var(--sp-1);line-height:1.5}
.adm-head-actions{display:flex;align-items:center;gap:var(--sp-2);flex:none}

/* --- dashboard action bar --- */
/* Only problems appear here, each a link to its fix. Coloured by severity, not by
   the cyan accent, because urgency is the message. */
.adm-actionbar{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-bottom:var(--sp-5)}
.adm-action{display:inline-flex;align-items:center;gap:6px;padding:8px 13px;border-radius:var(--r-ctl);
  border:1px solid;font-size:.85rem;font-weight:600;transition:filter .15s}
.adm-action::after{content:"→";opacity:.55;margin-inline-start:3px}
.adm-action:hover{filter:brightness(1.18)}
.adm-action.bad{color:var(--bad);border-color:rgba(255,90,95,.5);background:rgba(255,90,95,.09)}
.adm-action.warn{color:var(--warn);border-color:rgba(255,176,32,.5);background:rgba(255,176,32,.09)}
.adm-allclear{color:var(--ok);font-size:.9rem;margin-bottom:var(--sp-5)}
/* A small, light suffix beside a section title (counts, ON/OFF), so it doesn't
   inherit the heading's size/weight. */
.adm-h2note{font-size:.78rem;font-weight:400}

/* --- sections --- */
.adm-sec{padding:0;margin-bottom:var(--sp-6)}
/* A real heading now, not a dim form-legend: this is what restores hierarchy.
   Still a flex row so an inline state tag sits beside the title. */
.adm-sec h2{text-align:left;font-size:var(--fs-h2);color:var(--text-hi);font-weight:700;
  letter-spacing:0;text-transform:none;margin-bottom:var(--sp-3);
  display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
/* The old tiny-caps label, kept for GENUINE sub-labels (a group heading inside a
   section), not for section titles. */
.adm-eyebrow{font-size:var(--fs-label);text-transform:uppercase;letter-spacing:.1em;
  color:var(--text-low);font-weight:700;margin-bottom:var(--sp-2)}
.adm-main section{padding:0}
.adm-note{font-size:.84rem;color:var(--text-low);line-height:1.7;margin-bottom:14px}
.adm-dim{color:var(--text-low)}
.adm-bad{color:var(--bad)}

/* --- tables --- */
/* Wrap wide tables so they scroll horizontally on a phone (emergency use) instead
   of blowing out the page width. */
.adm-nowrap{white-space:nowrap}
.adm-tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.adm-table{width:100%;border-collapse:collapse;background:var(--navy-800);
  border:1px solid var(--navy-700);border-radius:10px;overflow:hidden;font-size:.86rem}
.adm-table th{text-align:left;font-size:.66rem;text-transform:uppercase;letter-spacing:.1em;
  color:var(--text-low);padding:9px 14px;background:var(--navy-900);border-bottom:1px solid var(--navy-700)}
.adm-table td{padding:9px 14px;border-bottom:1px solid var(--navy-700);vertical-align:middle}
.adm-table tr:last-child td{border-bottom:0}
.adm-table tbody tr:hover{background:rgba(42,59,87,.28)}
.adm-table.small td{font-size:.78rem;padding:7px 14px}
/* Shared admin list controls: filters, search, page-size, pager, sortable heads */
.adm-listbar{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin:10px 0 12px;font-size:.85rem}
.adm-search{display:flex;gap:6px}
.adm-search .adm-num{min-width:170px}
.adm-count{margin-inline-end:auto}
.adm-size a,.adm-pager a{color:var(--cyan-300);margin:0 4px;text-decoration:none}
.adm-size a.on{color:var(--text-hi);font-weight:800;text-decoration:underline}
.adm-size a:hover,.adm-pager a:hover{text-decoration:underline}
.adm-filters{display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin:6px 0;font-size:.82rem}
.adm-filters a{padding:3px 10px;border:1px solid var(--navy-600);border-radius:999px;color:var(--text-mid);text-decoration:none}
.adm-filters a.on{background:var(--cyan-500);color:#04222e;border-color:transparent;font-weight:700}
.adm-filters a:hover{border-color:var(--cyan-700)}
.adm-table thead a{color:inherit;text-decoration:none}
.adm-table thead a:hover{color:var(--cyan-300)}
/* An inline-flex SPAN inside the cell, never display:flex on the <td> itself.
   Making a table cell a flex container removes it from the table layout
   algorithm, so its column stops aligning with the header and every row
   staggers — which is exactly how this table broke. */
.adm-player{display:inline-flex;align-items:center;gap:8px;vertical-align:middle}
.adm-face{border-radius:4px;image-rendering:pixelated;background:var(--navy-700);flex:none}
/* A reference/code is selectable DATA, not a link: neutral, not the cyan accent. */
.adm-ref{word-break:break-all;max-width:220px;color:var(--text-mid)}
.adm-empty{color:var(--text-low);padding:30px;text-align:center;background:var(--navy-800);
  border:1px solid var(--navy-700);border-radius:10px;font-size:.86rem}

/* --- status --- */
.adm-tag{font-size:.68rem;padding:2px 9px;border-radius:999px;border:1px solid;white-space:nowrap}
.adm-tag.ok{color:var(--ok);border-color:rgba(61,220,132,.4)}
.adm-tag.soon{color:var(--warn);border-color:rgba(255,176,32,.45)}
/* "permanent"/"queued" is a neutral state, not good/bad and not interactive:
   slate, so cyan stays reserved for things you can click. */
.adm-tag.perm{color:var(--text-mid);border-color:var(--border-soft)}
/* the tier a manual grant was given: Plus in the tier's gold, Regular in .perm */
.adm-tag.plus{color:var(--gold-300);border-color:rgba(245,179,53,.5)}
.adm-tag.bad{color:var(--bad);border-color:rgba(255,90,95,.45)}

/* --- controls --- */
/* 34px rows are dense but still clear the 44px touch floor on the phone
   breakpoint below, where these become full-width. */
.adm-ok,.adm-no{min-height:32px;padding:0 12px;border-radius:6px;border:1px solid;background:none;
  font-family:inherit;font-size:.8rem;cursor:pointer}
.adm-ok{border-color:rgba(61,220,132,.45);color:var(--ok)}
.adm-ok:hover{background:rgba(61,220,132,.12)}
.adm-no{border-color:rgba(255,90,95,.45);color:var(--bad)}
.adm-no:hover{background:rgba(255,90,95,.12)}
.adm-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:flex-start}
/* Restart is destructive (drops players): kept folded behind a disclosure with a
   type-to-confirm, so it can't be a stray click. */
.adm-danger>summary{cursor:pointer;color:var(--bad);font-size:.8rem;list-style:none;
  border:1px solid rgba(255,90,95,.45);border-radius:6px;padding:5px 10px}
.adm-danger[open]{display:block;margin-top:8px;background:rgba(255,90,95,.08);
  border:1px solid rgba(255,90,95,.35);border-radius:8px;padding:10px 12px;max-width:340px}
.adm-danger[open]>summary{border:0;padding:0 0 6px}
.adm-btn{display:inline-flex;align-items:center;justify-content:center;min-height:38px;
  background:var(--cyan-500);color:#04222e;font-weight:700;border:0;border-radius:6px;
  font-family:inherit;font-size:.86rem;cursor:pointer;padding:0 18px}
.adm-btn:hover{background:var(--cyan-300)}
.adm-btn:focus-visible{outline:2px solid var(--cyan-300);outline-offset:3px}
.adm-btn.narrow{width:auto;margin-top:0}
.adm-link{background:none;border:0;color:var(--text-low);font-family:inherit;font-size:.82rem;cursor:pointer}
.adm-link:hover{color:var(--text-hi)}
.adm-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:14px}
.adm-hint{font-size:.75rem;color:var(--text-low);line-height:1.6}
.adm-state{font-size:.9rem;color:var(--text-mid)}
.adm-on{color:var(--ok)}
.adm-off{color:var(--text-low)}
/* The verify switch is the panel's only ASYNCHRONOUS control: pressing it queues
   work for an agent that polls every ~20s, so it has a third state between on
   and off that the store and sign-up switches never need. Amber rather than
   green: nothing has happened yet. */
.adm-pending{color:var(--warn)}
/* A disabled .adm-btn previously had no styling whatsoever, so a control that
   could not be pressed looked exactly like one that could. The verify switch
   disables itself both while a change is in flight and while the command channel
   is off; if neither LOOKS unavailable, the operator just clicks harder. */
.adm-btn:disabled,.adm-btn:disabled:hover{background:var(--navy-600);color:var(--text-low);
  cursor:not-allowed}
/* Amber, not red: a precondition to satisfy, not a fault to alarm about. */
.adm-warn{background:rgba(255,176,32,.1);border:1px solid rgba(255,176,32,.4);color:#ffd489;
  border-radius:8px;padding:10px 13px;font-size:.84rem;margin-top:14px}
.adm-warn a{color:#ffd489}
.adm-num,.adm-reason,.adm-pw,.adm-notice,.adm-search input{background:var(--navy-900);border:1px solid var(--navy-600);
  border-radius:6px;color:var(--text-hi);padding:9px 11px;font-size:.86rem;font-family:inherit}
.adm-num{width:120px;font-family:JBMono,monospace}
/* A reason is required before a second boost slot is given, so the box has to
   look like somewhere you type a sentence. At .adm-num's width it read as part
   of the number field and the owner's approval silently never submitted. */
.adm-reason{width:min(220px,100%);font-family:inherit}
.adm-pw{min-width:240px}
.adm-notice{width:100%;font-family:Vazirmatn,inherit;font-size:.92rem}
/* Text fields and the one <select> on the SMS page. Without this they fall back
   to the browser's white system widgets, which on a navy page reads as broken
   rather than as a form. Focus keeps a real ring: the border tint alone is not
   enough to see where you are. */
.adm-inp,.adm-sel{background:var(--navy-900);border:1px solid var(--navy-600);border-radius:6px;
  color:var(--text-hi);padding:9px 11px;font-size:.86rem;font-family:inherit}
.adm-inp{min-width:200px;font-family:JBMono,monospace}
.adm-inp:focus,.adm-sel:focus,.adm-num:focus,.adm-pw:focus{border-color:var(--cyan-700);
  outline:2px solid rgba(34,201,242,.35);outline-offset:1px}
/* Search sits with the list it filters, on the same line as the heading. */
.adm-sec-head{display:flex;align-items:center;gap:16px;margin-bottom:10px;flex-wrap:wrap}
.adm-sec-head h2{margin-bottom:0}
/* Scoped to the sec-head context only: the auto margin pushes a header search to
   the right. The listbar's own .adm-search (defined above) must NOT get it, or it
   fights .adm-count's margin and the row mis-aligns. */
.adm-sec-head .adm-search{margin:0 0 0 auto}
.adm-search input{width:240px;font-family:JBMono,monospace}
@media(max-width:720px){.adm-search{margin:0;width:100%}.adm-search input{flex:1;width:auto}}
/* A grid, not a flex row: with flex, a field carrying a hint grew taller than
   its neighbours and dragged the whole row out of alignment. Equal-height
   columns keep every label and input on one baseline. */
.adm-grant{display:grid;grid-template-columns:1fr 90px 2fr auto;gap:12px;align-items:end;
  background:var(--navy-800);border:1px solid var(--navy-700);border-radius:10px;padding:16px}
.adm-grant .grow{min-width:0}
.adm-grant .adm-btn{height:38px}
/* Two submits, Regular and Plus, side by side in the grid's last column. The
   Plus one is this page's single gold button (the store's .btn-gold anatomy),
   resized to the admin control height so the pair sits on one baseline. */
.adm-grant-actions{display:flex;gap:8px;align-items:end}
.adm-grant .btn-gold{height:38px;padding:0 16px;border-radius:6px;font-size:.86rem;
  font-family:inherit;white-space:nowrap;display:inline-flex;align-items:center}
.adm-grant .btn-gold:hover{transform:none}
@media(max-width:720px){.adm-grant{grid-template-columns:1fr}}
.adm-grant label{display:block;font-size:.68rem;text-transform:uppercase;letter-spacing:.08em;
  color:var(--text-low);margin-bottom:5px}
.adm-grant input{background:var(--navy-900);border:1px solid var(--navy-600);border-radius:6px;
  color:var(--text-hi);padding:9px 11px;font-size:.86rem;font-family:inherit;width:100%}

/* --- messages --- */
.adm-err{background:rgba(255,90,95,.1);border:1px solid rgba(255,90,95,.4);color:#ffb3b5;
  border-radius:8px;padding:10px 13px;font-size:.84rem;margin-bottom:16px}
.adm-ok-msg{background:rgba(61,220,132,.1);border:1px solid rgba(61,220,132,.4);color:var(--ok);
  border-radius:8px;padding:10px 13px;font-size:.84rem;margin-bottom:16px}
/* The empty-whitelist alarm. The relay fails closed, so this is the one thing on
   the panel allowed to shout. */
.adm-alarm{background:rgba(255,90,95,.12);border:1px solid var(--bad);border-radius:10px;
  padding:15px 17px;margin-bottom:20px}
.adm-alarm-h{color:var(--bad);font-weight:700;letter-spacing:.04em;margin-bottom:5px;font-size:.9rem}
.adm-alarm p{font-size:.84rem;line-height:1.7}

/* --- diff + preview --- */
.adm-diff{background:var(--navy-800);border:1px solid var(--navy-700);border-radius:10px;padding:12px 15px}
.adm-add{color:var(--ok);font-size:.85rem}
.adm-rm{color:var(--bad);font-size:.85rem}
.adm-pre{background:var(--navy-900);border:1px solid var(--navy-700);border-radius:10px;padding:15px;
  font-size:.8rem;line-height:1.65;color:var(--text-mid);overflow-x:auto;white-space:pre;max-height:420px}
.adm-inline{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:12px}
.adm-tabs{display:flex;gap:6px;margin:16px 0}
.adm-tabs a{padding:7px 13px;border-radius:6px;background:var(--navy-800);border:1px solid var(--navy-700);
  color:var(--text-low);font-size:.84rem}
.adm-tabs a.on{border-color:var(--cyan-700);color:var(--text-hi);background:var(--navy-700)}
.adm-tabs b{margin-inline-start:6px;color:var(--cyan-500);font-family:JBMono,monospace}

/* --- gate, enrolment, recovery: no sidebar, one focused task --- */
.adm-gate{min-height:100dvh;display:grid;place-items:center;padding:24px}
.adm-panel{width:100%;max-width:420px;background:var(--navy-800);border:1px solid var(--navy-700);
  border-radius:12px;padding:30px}
.adm-panel.wide{max-width:580px}
.adm-brand{display:flex;align-items:center;gap:9px;font-family:JBMono,monospace;font-size:.7rem;
  color:var(--text-low);letter-spacing:.14em;text-transform:uppercase;margin-bottom:20px}
.adm-brand .mark{width:22px;height:22px}
.adm-brand span{color:var(--cyan-500)}
.adm-panel h1{font-size:1.3rem;margin-bottom:9px}
.adm-panel label{display:block;font-size:.78rem;color:var(--text-hi);font-weight:700;margin:18px 0 6px}
.adm-panel input{width:100%;background:var(--navy-900);border:1px solid var(--navy-600);border-radius:6px;
  color:var(--text-hi);padding:11px 13px;font-size:.95rem;font-family:inherit}
.adm-panel input:focus{border-color:var(--cyan-700);outline:none}
.adm-panel .adm-btn{width:100%;margin-top:18px;min-height:42px}
.adm-code{font-family:JBMono,monospace;letter-spacing:.34em;text-align:center;font-size:1.25rem}
.adm-label{font-size:.68rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text-low);margin-top:18px}
/* The setup key is transcribed by hand into a phone, so it is chunked and given
   room rather than presented as one 32-character wall. */
.adm-secret{font-family:JBMono,monospace;font-size:1rem;color:var(--cyan-300);letter-spacing:.14em;
  word-spacing:.3em;line-height:1.9;word-break:break-all;background:var(--navy-900);
  border:1px solid var(--navy-600);border-radius:8px;padding:13px;margin-top:6px}
.adm-uri{font-family:JBMono,monospace;font-size:.68rem;color:var(--text-low);word-break:break-all;
  background:var(--navy-900);border:1px solid var(--navy-600);border-radius:8px;padding:10px;margin-top:6px}
.adm-codes{list-style:none;margin:18px 0 0;padding:0;display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.adm-codes li{background:var(--navy-900);border:1px solid var(--navy-600);border-radius:6px;
  padding:10px;text-align:center;font-family:JBMono,monospace;font-size:.92rem;color:var(--text-hi)}
@media(max-width:520px){.adm-codes{grid-template-columns:1fr}}
/* QR: a fixed, generous size because it is scanned off a screen by a phone
   camera, and a code that fills less than ~180px is where scanning fails. The
   white ground is required — inverted codes defeat many scanners. */
.adm-qr-wrap{display:flex;justify-content:center;margin:20px 0 10px}
.adm-qr{width:200px;height:200px;background:#fff;border-radius:8px;padding:0;
  border:1px solid var(--navy-600)}
.adm-scan-hint{text-align:center;font-size:.8rem;color:var(--text-low)}

/* Right-aligned action column: buttons line up with each other instead of
   floating at the ragged end of variable-width content. */
.adm-rt{text-align:right;white-space:nowrap;width:1%}
/* Checkboxes inherit the browser's blue otherwise, which reads as a foreign
   control dropped into a dark panel. */
.adm-table input[type=checkbox],.adm-panel input[type=checkbox]{
  width:17px;height:17px;accent-color:var(--cyan-500);cursor:pointer}

/* Gift-card redemption: the confirm screen leads with the character, because
   seeing the wrong face is how a typo gets caught. */
.redeem-who{text-align:center;background:var(--navy-900);border:1px solid var(--navy-600);
  border-radius:12px;padding:20px;margin-bottom:20px}
.redeem-body{object-fit:contain;filter:drop-shadow(0 10px 22px rgba(0,0,0,.45))}
.redeem-name{font-size:1.1rem;color:var(--text-hi);font-weight:700;margin-top:8px}
.redeem-gain{margin-top:8px;color:var(--cyan-300);font-size:1rem;font-weight:700}
.redeem-note{margin-top:8px;font-size:.84rem;color:var(--text-low);line-height:1.9}

/* --- /terms ------------------------------------------------------------- */
/* A legal document, not a landing section. Density is the point: a terms page
   earns trust by being scannable. */

/* Kills the global `section{padding:56px 0}` (see above), which was putting 112px
   of dead space INSIDE every clause block — measured, not guessed: the page was
   4670px tall for nine short lists.
   ⚠️ SPECIFICITY TRAP, learned the hard way: this selector is a class PLUS a type
   (0,1,1), so it outranks any class-only rule on the same element. It silently
   deleted `.tos-alert{padding}` and `.tos-sec{padding-top}`, leaving the callout's
   text touching its own border. Every padding below is therefore re-declared with
   `section.<class>` (0,2,1) so it wins. Do not "simplify" those back. */
.tos section{padding:0}

/* Container width is set by the MEASURE, not by taste: the clause column has to
   land near 65 characters a line. It was 88 before this, because the two-column
   redesign dropped the 68ch cap the first version had. */
.tos{max-width:776px;margin:0 auto;padding:52px 20px 80px}
.tos-head h1{font-size:1.9rem;color:var(--text-hi)}
.tos-lead{margin-top:10px;font-size:.95rem;color:var(--text-mid)}
.tos-meta{margin-top:6px;font-size:.82rem;color:var(--text-low)}

/* The not-a-VPN box. Bordered and tinted so it reads as a statement rather than
   as clause zero: it is the one thing on this page that must not be skimmed. */
.tos-alert{margin-top:24px;background:var(--navy-800);border:1px solid var(--navy-600);
  border-inline-start:3px solid var(--cyan-500);border-radius:12px}
.tos section.tos-alert{padding:18px 20px}
.tos-alert h2{font-size:1.05rem;color:var(--cyan-300);margin-bottom:8px}
.tos-alert p{font-size:.92rem;color:var(--text-mid);line-height:1.9}
.tos-alert p + p{margin-top:8px}

/* Rhythm by hairline, not by void. The gap between sections has to be clearly
   larger than the gap between bullets or nine sections read as one wall: 20px
   margin + 26px padding lands at roughly 2.3x the 20px between list items. */
.tos-sec{margin-top:20px;border-top:1px solid var(--navy-600)}
.tos section.tos-sec{padding-top:26px}
.tos-sec h2{font-size:1.02rem;color:var(--text-hi);display:flex;align-items:baseline;gap:9px}
/* The number is a marker, not a heading word: fixed width so every heading's
   text starts on the same vertical line however many digits it carries. */
.tos-n{flex:none;min-width:1.5em;font-size:.78rem;color:var(--cyan-500)}
.tos-sec ul{padding-inline-start:0;list-style:none}
.tos-sec li{position:relative;padding-inline-start:16px;margin-bottom:7px;
  font-size:.92rem;color:var(--text-mid);line-height:1.9}
.tos-sec li:last-child{margin-bottom:0}
/* Bright enough to mark the start of a line, quiet enough not to compete with it.
   At --navy-600 the dot was invisible against --navy-900. */
.tos-sec li::before{content:"";position:absolute;inset-inline-start:0;top:.8em;
  width:5px;height:5px;border-radius:50%;background:#41567A}
.tos-sec p{font-size:.92rem;color:var(--text-mid);line-height:1.9}
.tos-sec strong{color:var(--text-hi);font-weight:700}
.tos-sec a{color:var(--cyan-300)}
/* The only action on the page. A ghost button in --navy-600 read as disabled, so
   it takes the page's existing accent — no new colour, and no fill, which would
   make it compete with the document. */
.tos-support{margin-top:12px}
.tos-support .btn-ghost{display:inline-block;text-decoration:none;
  border-color:var(--cyan-700);color:var(--cyan-300)}
.tos-support .btn-ghost:hover{border-color:var(--cyan-500);color:var(--cyan-500)}

/* Two columns from 900px: the number and title sit in the inline-start rail (the
   RIGHT, in RTL) and the clauses run beside them. Standard legal-document layout,
   and it removes a heading's worth of height from every section. 206px because the longest heading needs
   ~190px and was the only one wrapping to two lines; the container grew by the
   same 16px so the clause measure stays put. */
@media(min-width:900px){
  .tos-sec{display:grid;grid-template-columns:206px minmax(0,1fr);gap:26px;align-items:start}
  .tos-sec .tos-rail{position:sticky;top:20px}
  .tos-sec h2{margin:0}
}
@media(max-width:899px){
  .tos-sec .tos-rail{margin-bottom:10px}
  /* Below 900 the rail stacks above the clause instead of sitting beside it, so
     the text inherits the container's full 736px and the line runs to 96
     characters at ~760px wide — measured. Above 900 the 206px rail happens to
     hold it at 68. Legal text is read, not skimmed, so the measure is capped
     explicitly here rather than left to whatever the rail leaves over. */
  .tos-sec > :not(.tos-rail){max-width:64ch}
}
@media(max-width:520px){
  .tos{padding:34px 18px 64px}
  .tos-head h1{font-size:1.45rem}
}

/* --- sticky header condense -------------------------------------------- */
/* Scroll-driven, no JavaScript, same guards as everywhere else on this site: a
   browser without support and a reader who asked for reduced motion both get a
   sticky header at one fixed size. Nothing is ever hidden.
   NOTE on the "animate only transform and opacity" rule: a header that actually
   changes height cannot obey it. The range is short (120px) and the subtree is
   tiny, which is the trade. */
@media (prefers-reduced-motion: no-preference){
  @supports (animation-timeline: scroll()){
    @keyframes head-condense{to{padding-block:10px}}
    @keyframes mark-condense{to{height:40px}}
    .site-head{animation:head-condense linear both;
      animation-timeline:scroll(root block);animation-range:0 120px}
    .site-head .mark{animation:mark-condense linear both;
      animation-timeline:scroll(root block);animation-range:0 120px}
    @media(max-width:820px){
      /* Already compact; only the padding tightens. */
      @keyframes head-condense{to{padding-block:7px}}
      .site-head .mark{animation:none}
    }
  }
}

/* --- /terms motion ------------------------------------------------------- */
/* Default OFF, declared before the guards. Same specificity as the display:block
   inside them, so ORDER is what decides: this must stay above, or the bar never
   shows. (Yes, the previous specificity trap on this page was mine too.) */
.tos-progress{display:none}

/* Scroll-driven CSS, NO JavaScript, and that is deliberate rather than clever.
   The site's other reveal (.rv + app.js + IntersectionObserver) is EVENT-based:
   if the callback never runs the element stays hidden forever, which is exactly
   how redeem_confirm.html and redeem_done.html shipped blank for every visitor
   on 2026-08-13. A scroll timeline is POSITION-based: an element inside the
   viewport is by definition within or past its range, so there is no
   missed-callback state to get stuck in. On a legal page that difference is the
   whole argument.

   ⚠️ THE RULE: every hidden start state lives inside BOTH guards below, never
   outside them. A browser without animation-timeline support, and a reader who
   asked for reduced motion, both get the plain static page. Do not lift a
   keyframe or an opacity:0 out of these blocks. */

@media (prefers-reduced-motion: no-preference){

  /* 1. On load: the top of the page arrives in reading order. Plain animation,
     no timeline, so it works everywhere. The hidden state is in the keyframe's
     `from`, NOT on the element, so even if the animation never ran the text
     would still be visible. */
  @keyframes tos-enter{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
  .tos-head h1,.tos-lead,.tos-meta,.tos-alert{
    animation:tos-enter .5s cubic-bezier(.16,1,.3,1) both}
  .tos-lead{animation-delay:.06s}
  .tos-meta{animation-delay:.09s}
  .tos-alert{animation-delay:.12s}

  /* 5. The only control on the page, so it should feel pressable. The global
     reduced-motion rule already kills these transitions. */
  .tos-support .btn-ghost{transition:border-color .18s ease-out,color .18s ease-out}
  .tos-support .btn-ghost:active{transform:translateY(1px)}

  @supports (animation-timeline: view()){

    /* 2. Each section as you reach it. OPACITY ONLY on the section: it contains
       the sticky rail, and a transformed ancestor would become a containing
       block and change how sticky resolves. The rise goes on the grid children
       instead. */
    @keyframes tos-fade{from{opacity:0}to{opacity:1}}
    @keyframes tos-rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
    .tos-sec{
      animation:tos-fade linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 45%}
    .tos-sec > *{
      animation:tos-rise linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 45%}

    /* 3. The hairline wipes in from the start edge as the boundary is crossed.
       The real border stays as the base for unsupporting browsers; here it goes
       transparent and a pseudo-element does the wipe. */
    .tos-sec{position:relative;border-top-color:transparent}
    .tos-sec::before{content:"";position:absolute;inset-block-start:-1px;inset-inline:0;
      height:1px;background:var(--navy-600);transform:scaleX(0);transform-origin:right;
      animation:tos-wipe linear both;
      animation-timeline:view();
      animation-range:entry 0% entry 35%}
    @keyframes tos-wipe{from{transform:scaleX(0)}to{transform:scaleX(1)}}
  }

  @supports (animation-timeline: scroll()){
    /* 4. How much document is left. z-index 50 is a documented tier on the
       site's scale (5 crest / 20 admin rail / 40 account dropdown / 60 tooltip):
       a page-level scroll indicator sits above the dropdown, below the tooltip. */
    @keyframes tos-progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
    .tos-progress{display:block;position:fixed;inset-block-start:0;inset-inline:0;
      height:2px;background:var(--cyan-500);transform:scaleX(0);transform-origin:right;
      z-index:50;pointer-events:none;
      animation:tos-progress linear both;
      animation-timeline:scroll(root block)}
  }
}

/* --- footer glow + motion ------------------------------------------------ */
/* Same rule as /terms: every hidden start state lives inside BOTH guards. The
   footer is on EVERY customer page, so a footer that can be trapped at
   opacity:0 by a missing script is a worse failure than no animation at all.
   Scroll timelines are position-based and cannot get stuck that way. */

@media (prefers-reduced-motion: no-preference){

  /* Hover: these are objects, so they respond. The glow is the brand's own cyan
     token, not a new colour. Transform and opacity only. */
  .srv img,.trust summary img{transition:filter .2s ease-out,transform .2s ease-out}
  .srv:hover img,.trust summary:hover img{filter:drop-shadow(0 0 10px rgba(34,201,242,.55));transform:translateY(-2px)}
  .trust summary:active img{transform:translateY(0)}
  .soc-i{transition:transform .2s ease-out}
  .soc:hover .soc-i{transform:scale(1.12)}

}

/* --- /list "servers" page ------------------------------------------------ */
/* Rebuilt 2026-08-19. The previous version centred all eleven blocks, tiled five
   identical cards whose only datum ("خلوت") was identical on every one, and
   showed six CTAs with one intent — while a product that sells lower ping
   printed no ping anywhere.
   So: latency is the page's subject, the routes are a RANKED comparison rather
   than equal tiles, and the page is composed left-aligned in four sections with
   one CTA. Occupancy keeps the ok/warn/bad colour language (word + bars, never
   colour alone); latency deliberately does NOT reuse those hues — one accent,
   and two colour languages on one screen would make both unreadable. */
/* isolation:isolate makes the stacking context the ambient glow sits in, so a
   z-index:-1 pseudo-element lands behind the content but still above the page
   background. overflow-x:clip contains the glow's blur (and any transient FLIP
   excess) without turning this into a scroll container; overflow-y stays
   visible so the glow's vertical bleed and the reveals are never clipped. */
.listp{max-width:1100px;margin:0 auto;padding:8px 20px 90px;text-align:start;
  position:relative;isolation:isolate;overflow-x:clip}

/* ---- 1. Connect. The address is the subject: it leads, at size, on the
   inline-start edge, with its measurements beside it rather than under a badge
   stack. Asymmetric on purpose (1.35fr/1fr) — a 50/50 split would read as two
   equal things, and the address is not equal to its own footnotes. ---- */
.cn{padding:6px 0 6px;position:relative}
/* Ambient cyan behind the connect block. Scoped to THIS section rather than the
   whole page: it is atmosphere around the primary action (the address), and a
   full-page wash would just tint everything and stop meaning anything.
   ⚠️ inset is 0 on the sides. A negative horizontal inset makes the box wider
   than the viewport and trips a horizontal scrollbar on phones; the blur and
   transform add no layout width, so the vertical bleed is free. */
/* ⚠️ The blur is STATIC and the element never transforms. Animating transform on
   a filter:blur() layer forces the blur to be recomputed every frame; measured
   on 2026-08-20 that single animation cost 103% CPU (a full core) in software
   raster. A static blur is rastered once and then just composited. */
/* ⚠️ Inset 5% at the sides, not 0. .listp sets overflow-x:clip, and a glow box
   flush to that edge has its 26px blur sliced off with a hard vertical line down
   both sides. Pulling the box in lets the falloff finish inside the clip.
   The clip itself stays: a NEGATIVE horizontal inset here once made the box wider
   than the viewport and tripped a horizontal scrollbar on phones. */
.cn::before{content:"";position:absolute;z-index:-1;inset:-40% 5% -25%;
  pointer-events:none;
  background:radial-gradient(58% 46% at 70% 40%,rgba(34,201,242,.24),transparent 70%);
  filter:blur(26px)}
.listp h1{font-size:clamp(1.9rem,5vw,2.8rem);margin:0 0 10px;letter-spacing:-.02em}
.cn-lead{color:var(--text-mid);max-width:52ch;margin-bottom:24px}
/* Flex, not a fr-based grid: the address pill hugs its content, so fixed tracks
   left it floating in a half-empty card with a void across the rest — the exact
   dead space this rebuild set out to remove. space-between puts the address at
   one end and its readings at the other, and the card is full at every width. */
/* ⚠️ Capped, and NOT stretched to the page width. The card holds two things
   (the address, and its two readings) that come to ~720px. Across the full
   1060px column, space-between pushed them to opposite ends and left a 377px
   void down the middle. Narrower than the route list below is deliberate: the
   card is the focal object, and the pre-rebuild layout pinned it the same way. */
.cn-main{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;
  max-width:780px;gap:26px 34px;
  background:var(--navy-800);border:1px solid var(--cyan-700);border-radius:16px;
  padding:24px 26px;position:relative;
  box-shadow:0 0 0 1px rgba(34,201,242,.22),0 20px 52px -28px rgba(34,201,242,.55),
             inset 0 1px 0 rgba(255,255,255,.04)}

.cn-main>div:first-child{min-width:0}
.cn-label{display:block;font-size:.85rem;font-weight:700;color:var(--text-low);margin-bottom:10px}
/* Bedrock's port is a second copyable pill under the address; give its label room. */
.cn-label-port{margin-top:16px}
.addr-pill.cn-port-pill .addr{letter-spacing:.06em}
/* ⚠️ The pill HUGS its address. Stretching it to the card width does not fill
   anything: the address is `dir="ltr"` inside an RTL row, so the text stays at
   one end and ~300px of the pill renders as empty surface. Measured 2026-08-20.
   The card's own width is what gets capped instead (see .cn-main). */
.addr-pill.cn-pill{display:inline-flex;width:auto;max-width:100%;text-align:start;
  box-shadow:0 0 0 1px rgba(34,201,242,.20)}

/* The halo is a WRAPPER, not the pill. .addr-pill sets overflow:hidden to clip
   the copy button against its rounded corner, which also clipped this glow's
   outer 22px away completely: it animated and painted nothing.
   ⚠️ It is also the SMALLEST element that could carry the page's one looping
   glow. Compositing cost scales with layer AREA: the same fade across the whole
   connect block measured 21% CPU per open tab, here it is ~1%. Do not move this
   loop onto .cn-main or .cn::before. */
.addr-halo{position:relative;display:block}
.addr-halo::after{content:"";position:absolute;z-index:-1;inset:-3px;
  border-radius:17px;pointer-events:none;opacity:0;
  box-shadow:0 0 22px 2px rgba(34,201,242,.55)}
.cn-hint{font-size:.85rem;color:var(--text-low);margin-top:12px}
.cn-hint code{font-family:inherit;color:var(--text-mid);font-weight:700}

/* The measurement rail. A definition list because that is what it is: two
   labelled readings about the address to its side. */
.cn-stats{display:flex;gap:30px;margin:0;flex:none;
  border-inline-start:1px solid var(--navy-600);padding-inline-start:30px}
.cn-stats>div{display:flex;flex-direction:column;gap:5px}
.cn-stats dt{font-size:.8rem;color:var(--text-low);line-height:1.5}
.cn-stats dd{margin:0}

/* ---- The latency readout. Tabular figures so the digits sit in fixed columns
   and a 9→75 change does not shuffle the layout every poll. ---- */
.ms{display:inline-flex;align-items:baseline;gap:4px;font-variant-numeric:tabular-nums;
  color:var(--text-hi);font-weight:800;line-height:1}
.ms b{font-size:1.7rem;letter-spacing:-.02em}
.ms i{font-style:normal;font-size:.85rem;font-weight:700;color:var(--text-low)}
/* No reading yet. Never a zero: a route that has not reported would otherwise
   advertise itself as instant, which is the one number we must never print. */
.ms.none{color:var(--text-low)}
.ms.none b{font-size:1.35rem}

/* ---- 2. The ranked comparison. Rows, not cards: five equal tiles cannot
   express an order, and "which one do I pick" is the only question this section
   answers. Rank comes from a CSS counter on the <ol> so the numbers stay correct
   after the live poller re-orders the DOM — no JS renumbering. ---- */
.rt-sec{margin-top:52px}
.rt-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin-bottom:14px}
.rt-head h2{font-size:1.3rem;margin:0}
.rt-head p{font-size:.85rem;color:var(--text-low);margin:0;max-width:46ch}
.rt{list-style:none;counter-reset:rt;margin:0;padding:0;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:14px;overflow:hidden}
/* The action column sizes to its CONTENT, for both viewers. A fixed fraction
   cannot serve both: 1.5fr left a visitor's padlock floating in 414px, and any
   value small enough for the padlock would crush a subscriber's address pair.
   Whatever is left goes to the latency column, where a longer bar is finer
   comparison resolution. */
.rt-row{counter-increment:rt;display:grid;
  grid-template-columns:auto minmax(0,1.2fr) minmax(200px,1fr) auto;
  align-items:center;gap:18px;padding:16px 20px;border-bottom:1px solid var(--navy-700)}
.rt-row:last-child{border-bottom:0}

/* The fastest row is the recommendation; the cyan wash and the glowing bar say
   so without a badge competing with the number.
   ⚠️ The wash plus the inset glow put up to 0.28 cyan alpha behind this row's
   text. Measured: --text-low lands at 3.10:1 on that surface, under the 4.5:1
   floor, and 2.92:1 on hover. The fix is NOT to dim the glow and it is NOT to
   leave it: this row's supporting text steps up to --text-mid, which reads
   6.05:1 on the same surface. That is also the correct hierarchy - the row the
   page recommends should read stronger than the four below it. */
.rt-row:first-child{background:rgba(34,201,242,.06);
  box-shadow:inset 0 0 30px -8px rgba(34,201,242,.22)}
.rt-row:first-child .rt-desc,
.rt-row:first-child .rt-lock,
.rt-row:first-child .rt-off,
.rt-row:first-child .ms i{color:var(--text-mid)}
.rt-row:first-child .rt-lock .lock-ico{color:var(--cyan-500)}
/* Hover: the row lifts its own surface rather than translating. These sit in a
   bordered list, and moving one row would shear the hairlines either side. */
.rt-row{transition:background .22s ease-out,box-shadow .22s ease-out}
.rt-row:hover{background:rgba(34,201,242,.05)}
.rt-row:first-child:hover{background:rgba(34,201,242,.09)}
.rt-row.is-down{opacity:.5}

.rt-rank{font-weight:800;color:var(--text-low);font-size:1rem;width:1.6em;flex:none}
.rt-rank::before{content:counter(rt,persian)}
.rt-row:first-child .rt-rank{color:var(--cyan-300)}

.rt-id{min-width:0}
.rt-name{display:block;font-size:1.05rem;font-weight:800;color:var(--text-hi);letter-spacing:.01em}
.rt-desc{display:block;font-size:.84rem;color:var(--text-low);line-height:1.5;margin-top:2px}

/* Latency cell: the number, then a bar showing it against the slowest route on
   the page. Bar length is proportional to MILLISECONDS, so longer = slower —
   the rank number and the printed figure both say the same thing, and no
   colour is asked to carry the meaning. */
.rt-lat{display:flex;flex-direction:column;gap:7px;min-width:0}
.rt-lat .ms b{font-size:1.25rem}
.rt-bar{display:block;height:5px;border-radius:999px;background:var(--navy-700);overflow:hidden}
.rt-bar i{display:block;height:100%;width:var(--w,0);border-radius:999px;
  background:var(--cyan-700);box-shadow:0 0 10px rgba(34,201,242,.45)}
/* Static glow. The pulsing version animated box-shadow and cost 19% CPU on its
   own, for a 5px bar nobody was watching that closely. */
.rt-row:first-child .rt-bar i{background:linear-gradient(90deg,var(--cyan-700),var(--cyan-300));
  box-shadow:0 0 16px rgba(34,201,242,.85)}
/* Sweep-on-reveal: the bar starts empty and grows to its share as the row
   arrives, so the comparison draws itself in ranking order instead of being
   there already. Gated on html.js because without JS neither the class nor the
   --w custom property is ever set. */
html.js .rt-bar i{width:0}
html.js .rt-row.in .rt-bar i{width:var(--w,0)}

.rt-act{min-width:0;display:flex;justify-content:flex-end}
.rt-act>*{max-width:100%}
/* Hug, never stretch. ⚠️ `width:auto` is NOT hug here: .addr-pill is a
   block-level flex container, so auto means FILL the parent. The parent div is
   sized to the wider of a primary/alternate pair, so the shorter address kept
   rendering an empty slab before its copy button. fit-content is the shrink-to-fit
   this needs. */
.rt-row .addr-pill{width:fit-content;max-width:100%}
.rt-row .addr-pill code{font-size:.95rem;padding:10px 13px}
.rt-row .addr-pill button{padding:0 14px;font-size:.86rem}
.rt-row .addr-pill button svg{width:16px;height:16px}
.rt-alt{margin-top:7px}
.rt-alt-label{font-size:.72rem;color:var(--text-low);display:block;margin-bottom:4px}
/* The occupancy meter moves under the name: it is a property of the route, and
   in its own column it competed with the latency figure for the same glance. */
.rt-id .sig{margin-top:7px;font-size:.82rem}
/* «خلوت» printed identically on all five rows, which is the same "every card
   shows the same datum" flaw this page was rebuilt to remove. The three-bar
   meter still shows the state on every row; the WORD now appears only when a
   route is busy, so seeing it means something.
   ⚠️ Scoped to .rt-row: the connect card must still read «شلوغی الان / خلوت».
   No accessibility loss - sigmeter carries role="img" aria-label="شلوغی: ..."
   on .sig itself, and app.js's paintMeter keeps rewriting that class live. */
.rt-row .sig.band-low .band-word{display:none}

/* Locked row. The address is withheld, the MEASUREMENT is not — that is the
   whole point of showing ping here: a visitor gets the evidence, a subscriber
   gets the address. */
/* A quiet state label, not a control. Five bordered pills down one edge read as
   five buttons however they behave, and "a wall of padlocks" is the complaint
   this rebuild started from. No border, no fill, no button shape. */
.rt-lock{display:inline-flex;align-items:center;gap:7px;padding:6px 0;
  color:var(--text-low);font-size:.85rem;font-weight:700}
.rt-lock .lock-ico{color:var(--text-low)}
.rt-lock .lock-ico{width:15px;height:15px;flex:none}
.rt-off{font-size:.86rem;font-weight:700;color:var(--text-low)}

/* ---- The game switcher. Leads the page: a player picks a GAME before a
   route, so this row is the first thing under the nav and carries real
   weight instead of floating as three small chips. ---- */
/* ---- edition split (java / cracked / bedrock): the top-level choice ----
   auto-fit rather than a fixed column count: the row now carries three cards,
   and editionsFrom DROPS an edition with no live games, so the grid has to look
   right at two cards as well as three. */
.edsw{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;margin:6px 0 10px}
@media(max-width:560px){.edsw{grid-template-columns:1fr}}
.ed-card{display:flex;align-items:center;gap:14px;text-align:start;cursor:pointer;font:inherit;
  background:var(--navy-800);border:1px solid var(--navy-700);border-radius:14px;padding:14px 18px;
  color:var(--text-mid);transition:border-color .18s ease-out,transform .18s ease-out,box-shadow .18s ease-out,background .18s ease-out}
.ed-card:hover:not([disabled]){transform:translateY(-2px);border-color:var(--navy-600)}
.ed-card.on{border-color:var(--cyan-500);box-shadow:0 0 0 1px var(--cyan-500),0 6px 22px rgba(34,201,242,.18);
  background:linear-gradient(180deg,rgba(34,201,242,.08),transparent 72%),var(--navy-800)}
/* Coming-soon edition (Bedrock while it is still in development): visible so the
   roadmap is clear, but dimmed and not selectable. */
.ed-card.is-soon{cursor:default;opacity:.62;filter:saturate(.6)}
.ed-card.is-soon .ed-block{filter:grayscale(.5)}
.ed-soon{margin-inline-start:auto;flex:none;font-size:.72rem;font-weight:700;color:var(--warn);
  background:rgba(255,176,32,.12);border:1px solid rgba(255,176,32,.32);border-radius:999px;padding:4px 12px;white-space:nowrap}
.ed-block{width:50px;height:50px;flex:none;filter:drop-shadow(0 5px 8px rgba(0,0,0,.45))}
.ed-meta{display:flex;flex-direction:column;min-width:0;gap:1px}
.ed-name{font-weight:700;font-size:1.15rem;color:var(--text-hi);line-height:1.3}
.ed-card.on .ed-name{color:var(--cyan-300)}
.ed-sub{font-size:.78rem;color:var(--text-low);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ed-count{margin-inline-start:auto;flex:none;font-size:.78rem;font-weight:700;color:var(--text-low);
  background:var(--navy-900);border:1px solid var(--navy-700);border-radius:999px;padding:4px 12px;white-space:nowrap}
.ed-card.on .ed-count{color:var(--cyan-300);border-color:rgba(34,201,242,.3)}
/* One tag look for both game-type marks (بدراک and سرور کرکی): same cyan pill,
   so the page keeps a single accent and the two read as the same kind of label. */
.be-tag,.krk-tag{font-size:.42em;font-weight:700;vertical-align:middle;color:var(--cyan-300);
  background:rgba(34,201,242,.12);border:1px solid rgba(34,201,242,.32);border-radius:7px;padding:3px 9px;margin-inline-start:8px;letter-spacing:.02em}

/* «کرک» tag on a cracked game tile. The tile is only ~112px wide, so this is a
   quiet corner pill rather than a shout: the category card already said بدون اکانت.
   inset-inline-start puts it top-RIGHT under dir=rtl. */
.gsw-tab{position:relative}
.gsw-krk{position:absolute;top:6px;inset-inline-start:6px;font-size:.58rem;font-weight:700;
  color:var(--cyan-300);background:rgba(34,201,242,.12);border:1px solid rgba(34,201,242,.32);
  border-radius:999px;padding:1px 7px;letter-spacing:.02em;pointer-events:none}
.gsw-tab.on .gsw-krk{background:rgba(34,201,242,.22);border-color:rgba(34,201,242,.5)}
/* The pill needs a band of its own. A 112px tile has NO corner clear of the
   56px centred logo, so an absolutely-positioned badge sits on the artwork
   (measured 12x9px at 1440, 15x11px at 390 before this rule) and the other
   three corners trade the logo for the ping text. Reserve the height instead
   of nudging the badge somewhere it still collides. Only cracked tiles pay
   for it, and a row only ever holds one edition, so heights stay uniform. */
.gsw-tab[data-edition="cracked"]{padding-top:27px}
/* No tier badge on a tile: the tier is the GROUP the tile sits in (.gsw-group,
   its .gsw-gh header carries the Plus mark), so a Plus game reserves no band
   and a game that is both cracked and Plus shows only the کرک corner tag. */

/* ---- occupancy meter (شلوغی) ----
   RESTORED 2026-09-10. The base geometry and the per-band colours were dropped
   from the shipped stylesheet during the route-card -> .rt-row rebuild, leaving
   the <i> bars with no size and no colour, so the meter rendered as nothing on
   every game while app.js kept faithfully swapping the band-* class.
   One bar green / two amber / three red, next to the Persian word, so colour
   REINFORCES the word and is never the only signal. */
.sig{display:inline-flex;align-items:center;gap:9px;font-size:.9rem;font-weight:700;
  color:var(--text-mid);align-self:flex-start}
.sig-bars{display:inline-flex;align-items:flex-end;gap:3px;height:18px}
.sig-bar{width:5px;border-radius:2px;background:var(--navy-600);opacity:.4}
.sig-bar.b1{height:8px}.sig-bar.b2{height:13px}.sig-bar.b3{height:18px}
.sig.band-low .b1{background:var(--ok);opacity:1}
.sig.band-mid .b1,.sig.band-mid .b2{background:var(--warn);opacity:1}
.sig.band-high .b1,.sig.band-high .b2,.sig.band-high .b3{background:var(--bad);opacity:1}
.sig.band-updating{color:var(--text-low)}
.sig.band-updating .sig-bar{background:var(--text-low);opacity:.3}

.gsw-wrap{margin:2px 0 8px}
/* The switcher is a stack of tier GROUPS (.gsw-group: a header, then a row).
   The scroller is the ROW, not the switcher: each group scrolls on its own, so
   the fades, the drag and the indicator below are all per row. The group is
   the positioned box the fades hang off (a scroller's own pseudo-elements would
   scroll away with its content); the header stacks above them. */
.gsw{display:flex;flex-direction:column;gap:4px}
.gsw-group{position:relative}
/* Horizontal scroll-snap is what makes this scale: a fourth or tenth game
   needs no layout change, the row just scrolls. Start-aligned under its
   header on every screen: a centred run under a start-aligned label read as
   two unrelated things, and centring breaks the moment a row overflows (the
   first tile becomes unreachable by scrolling). */
.gsw-row{position:relative;display:flex;gap:12px;justify-content:flex-start;overflow-x:auto;
  /* padding-block is load-bearing: overflow-x:auto also clips the Y axis, so a
     selected tile's glow was being sliced flat top and bottom. This gives it room
     INSIDE the scroll box (same fix the addr-pill row carries); the bottom also
     seats the 2px indicator 8px under the tiles. */
  scroll-snap-type:x proximity;padding:14px 8px 22px;
  scrollbar-width:none;-webkit-overflow-scrolling:touch}
.gsw-row::-webkit-scrollbar{display:none}
/* Mouse users can grab and pull the row sideways (JS drag-to-scroll in app.js).
   Show the grab cursor only while it actually scrolls; kill snap + text
   selection mid-drag so the pull is smooth and nothing highlights. */
.gsw-group.overflowing .gsw-row{cursor:grab}
.gsw-row.dragging{cursor:grabbing;scroll-snap-type:none;user-select:none;-webkit-user-select:none}
/* Edge fades signal "there is more" without adding a control to maintain. One per
   edge, and each is shown ONLY while that edge is actually hiding tiles — app.js
   (syncFade) sets .fade-s / .fade-e on the GROUP from its row's scroll position.
   This replaces a single ALWAYS-ON fade pinned to inset-inline-start: under
   dir=rtl that resolves to the RIGHT, i.e. the row's START edge, so it painted a
   56px near-black band across the first (and selected) tile with nothing hidden
   behind it — the tile's logo and ping were half-swallowed (owner-reported).
   Gradients run transparent (inward) -> page ground (outward); the outward
   direction differs per edge and these defaults are the RTL ones (start=right,
   end=left). An LTR page would set --gsw-out-s:left and --gsw-out-e:right.
   z-index: tiles are unpositioned but hover-transform into their own stacking
   context, which would otherwise paint a hovered tile over the fade. The fade
   spans the group's height; the header sits above it (z-index 3, .gsw-gh). */
.gsw-group::before,.gsw-group::after{content:"";position:absolute;inset-block:0;width:56px;
  pointer-events:none;opacity:0;transition:opacity .2s;z-index:2}
.gsw-group::before{inset-inline-start:0;
  background:linear-gradient(to var(--gsw-out-s,right),transparent,var(--navy-900))}
.gsw-group::after{inset-inline-end:0;
  background:linear-gradient(to var(--gsw-out-e,left),transparent,var(--navy-900))}
.gsw-group.fade-s::before,.gsw-group.fade-e::after{opacity:1}
@media(min-width:701px){
  /* Once Java passed ~8 games the single row hid about three tiles off its edge,
     and a mouse could only reach them by dragging (there is no wheel-to-horizontal
     mapping), so the row WRAPS here instead: every game visible at once, two
     rows if it must, nothing to scroll and no edge fade to draw. Phones keep the
     swipe row (see the max-width:700px block) — a natural gesture there, and 11
     tiles would stack four rows deep on a 390px screen, pushing the connect
     address and ping panel off-screen. */
  .gsw-row{flex-wrap:wrap;overflow-x:visible;scroll-snap-type:none}
  /* Belt and braces: nothing overflows once wrapped (syncFade drops .overflowing
     because scrollWidth now equals clientWidth), but a stale class must never
     offer a grab cursor on an unscrollable row. */
  .gsw-group.overflowing .gsw-row{cursor:auto}
}

.gsw-tab{flex:0 0 auto;scroll-snap-align:start;
  display:flex;flex-direction:column;align-items:center;gap:6px;
  min-width:112px;min-height:112px;padding:12px 14px 10px;cursor:pointer;
  background:var(--navy-800);border:1px solid var(--navy-600);
  border-radius:var(--r-card);
  /* transform + border/background only. NEVER animate box-shadow or filter:
     three keyframes doing exactly that cost ~a full core each on this page. */
  transition:border-color .18s,background .18s,transform .12s cubic-bezier(.16,1,.3,1)}
.gsw-logo{width:56px;height:56px;display:block;
  /* Full-colour logos — selection reads off the tile's cyan border + glow, not by
     desaturating the rest. Coming-soon tiles still grey out (see .is-soon). */
  opacity:.9;transition:opacity .2s}
.gsw-name{font-family:Vazirmatn;font-weight:700;font-size:.86rem;color:var(--text-mid);
  white-space:nowrap;transition:color .18s}
.gsw-ping{font-size:.72rem;font-weight:700;color:var(--text-low);line-height:1}
.gsw-ping b{font-size:.86rem;color:var(--text-mid)}
.gsw-ping i{font-style:normal;margin-inline-start:2px;opacity:.75}
.gsw-ping.none,.gsw-ping.soon{color:var(--text-low)}

.gsw-tab:hover:not([disabled]){border-color:var(--cyan-700);background:var(--navy-700);transform:translateY(-2px)}
.gsw-tab:hover:not([disabled]) .gsw-logo{opacity:1}
.gsw-tab:active:not([disabled]){transform:translateY(0) scale(.97)}

/* Active: cyan ring plus a STATIC glow. The glow never animates, so it costs
   one paint and nothing thereafter. */
.gsw-tab.on{border-color:var(--cyan-500);background:var(--navy-700);
  box-shadow:0 0 0 1px var(--cyan-500),0 4px 18px rgba(34,201,242,.28)}
.gsw-tab.on .gsw-logo{opacity:1}
.gsw-tab.on .gsw-name{color:var(--text-hi)}
.gsw-tab.on .gsw-ping b{color:var(--cyan-300)}

.gsw-tab.is-soon{cursor:not-allowed;background:var(--navy-900);border-color:var(--navy-700)}
.gsw-tab.is-soon .gsw-logo{filter:grayscale(1);opacity:.28}
.gsw-tab.is-soon .gsw-name{color:var(--text-low)}
.gsw-ping.soon{background:var(--navy-700);border-radius:var(--r-pill);padding:2px 8px}

@media (max-width:560px){
  .gsw-tab{min-width:96px;min-height:104px;padding:10px 12px 8px}
  .gsw-tab[data-edition="cracked"]{padding-top:25px}
  .gsw-logo{width:46px;height:46px}
}

/* Only hide panels when JS can bring them back. The `js` class is set in
   <head> by the one inline script the CSP hash pins, so this needs no new
   script and no CSP change. Without JS every panel stays visible, which is
   the no-JS design and what a crawler sees. */
html.js .game-panel:not(.on){display:none}
/* Edition gating: JS marks the active edition on .listp; hide the other
   edition's tabs and panels. Without JS neither class is set, so both show. */
/* Stated as "hide everything that is NOT the active edition" rather than as a
   pair per combination: with three editions the pairwise form needs six rules and
   silently breaks the moment a fourth is added. */
/* The tabs are gated by their GROUP (header and row go together); the per-tab
   rule stays so a tab can never outlive its group's gating. */
html.js .listp.ed-java .gsw-group:not([data-edition="java"]),
html.js .listp.ed-java .gsw-tab:not([data-edition="java"]),
html.js .listp.ed-java .game-panel:not([data-edition="java"]),
html.js .listp.ed-cracked .gsw-group:not([data-edition="cracked"]),
html.js .listp.ed-cracked .gsw-tab:not([data-edition="cracked"]),
html.js .listp.ed-cracked .game-panel:not([data-edition="cracked"]),
html.js .listp.ed-bedrock .gsw-group:not([data-edition="bedrock"]),
html.js .listp.ed-bedrock .gsw-tab:not([data-edition="bedrock"]),
html.js .listp.ed-bedrock .game-panel:not([data-edition="bedrock"]){display:none}

/* A state transition, which is a motivated reason to animate. transform and
   opacity ONLY, plays once per switch, never loops. */
.game-panel.on{animation:panel-in .26s cubic-bezier(.16,1,.3,1) both}
@keyframes panel-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .game-panel.on{animation:none}
  .gsw-tab,.gsw-logo,.gsw-name{transition:none}
}

/* ---- 4. One CTA, for visitors only. It replaces six (five per-row padlocks
   plus a footer button) that all pointed at /store. ---- */
.list-cta{display:flex;align-items:center;justify-content:space-between;gap:18px;
  flex-wrap:wrap;margin-top:44px;padding:22px 24px;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:14px}
.list-cta p{margin:0;color:var(--text-mid);max-width:48ch}
.list-cta b{color:var(--text-hi)}
/* 74ch: this ran 162 characters wide, the only prose on the page without a
   measure cap (.cn-lead, .rt-head p, .rm p and .list-cta p all have one). */
.list-note{font-size:.82rem;color:var(--text-low);margin-top:22px;max-width:74ch}

/* Hero → /list secondary link. */
.hero-more{margin-top:16px}
.hero-more a{font-size:.92rem;color:var(--text-mid);font-weight:700}
.hero-more a:hover{color:var(--cyan-300)}

/* Account → connect link. */
.acct-connect{margin-top:10px}
.acct-connect a{font-size:.92rem;font-weight:700;color:var(--cyan-300)}

/* ---- Responsive. Desktop is the connecting surface — Minecraft Java is
   desktop-only and someone copying an address is at their PC — so the row keeps
   its four columns as long as they are genuinely readable. Below that the row
   becomes a small card, which is the browsing shape, rather than four columns
   crushed to nothing. ---- */
@media(max-width:900px){
  .rt-row{grid-template-columns:auto minmax(0,1fr) minmax(130px,.7fr);
    grid-template-areas:"rank id lat" ". act act";row-gap:12px}
  .rt-rank{grid-area:rank}
  .rt-id{grid-area:id}
  .rt-lat{grid-area:lat}
  .rt-act{grid-area:act;justify-content:flex-start}
}
@media(max-width:720px){
  .cn-main{grid-template-columns:1fr;gap:20px;padding:20px}
  .cn-main{gap:18px}
  .cn-stats{border-inline-start:0;border-top:1px solid var(--navy-600);
    padding-inline-start:0;padding-top:18px;gap:28px;flex-wrap:wrap;width:100%}
  .rt-head{gap:6px}
}
/* Below ~430px the auto pill (address at 1.15rem + the copy button) is wider
   than the viewport and pushes a horizontal scrollbar. The address must not be
   truncated — it is the thing people copy — so the type steps down instead. */
@media(max-width:430px){
  .addr-pill.big code{font-size:1rem}
  .addr-pill.big button{padding:0 12px;font-size:.85rem}
  .rt-row .addr-pill code{font-size:.85rem;padding:9px 10px}
  .rt-row .addr-pill button{padding:0 10px;font-size:.8rem}
}
@media(max-width:560px){
  .listp{padding-inline:16px}
  .rt-row{grid-template-columns:auto minmax(0,1fr);
    grid-template-areas:"rank id" ". lat" ". act";gap:10px 14px;padding:16px}
  .rt-lat{flex-direction:row;align-items:center;gap:12px}
  .rt-lat .rt-bar{flex:1;min-width:60px}
  .list-cta{padding:18px}
  .list-cta .btn-solid{width:100%;text-align:center}
}

/* Motion layer. EVERY start-state and animation lives inside this guard, so
   reduced-motion gets a fully-composed, still page with nothing missing.
   Transform and opacity only, with one documented exception (the bar sweep).

   Each animation below earns its place or it is not here:
     ambient drift  - atmosphere around the primary action
     card breathe   - marks the address as the live, recommended way in
     bar sweep      - the ranking draws itself, in rank order
     row cascade    - arrival order matches the order the list ranks by
     meter pulse    - an active route looks alive rather than printed
     hover/active   - feedback that a control responded
   The ping figures arrive as a SKELETON and count in (600ms, Persian digits,
   app.js list IIFE + CP.countUp) once their row or panel reveals: the digits
   are transparent under the shimmer until then, so no transient value is ever
   readable, and the true figure is in the HTML from the first byte for no-JS,
   crawlers and reduced motion (which never adds .skel). See the Plus UI: list
   block at the end of this sheet. */
@media (prefers-reduced-motion:no-preference){
  /* ⚠️ EVERY looping animation here animates OPACITY ONLY, on its own layer.
     This is not style preference, it is measured: the first version of this
     block drifted a blurred layer (transform on filter:blur) and pulsed two
     box-shadows. Each of those cost ~100% CPU on its own in software raster,
     and a single open tab burned 1.3 cores forever. See the runbook entry for
     2026-08-20. If you add a loop here, animate opacity or transform on an
     element WITHOUT a filter, and measure it before shipping.

     The ambient glow breathes by fading, not by moving. The blur underneath it
     is static, so it is rastered once and this is pure compositing. */
  /* ⚠️ ONE looping glow, on the smallest element that can carry it.
     Measured on 2026-08-20, per open tab, headless software raster:
       transform on a filter:blur() layer ......... 103%   (removed)
       box-shadow keyframes on the card ........... 104%   (removed)
       box-shadow keyframes on the bar ............  19%   (removed)
       opacity fade across the whole connect block   21%   (removed)
       opacity fade on the address pill ...........  ~1%   (this)
     Compositing cost scales with layer AREA, not with how subtle the effect
     looks. Before adding a loop here, measure it with a tab open; the page
     idles at ~4% and it should stay there. */
  .addr-halo::after{animation:addr-glow 4.6s ease-in-out infinite}
  @keyframes addr-glow{0%,100%{opacity:0}50%{opacity:1}}

  /* Bars ease when the band changes; the lit meter gently breathes so an active
     route looks live. Slight per-row offset keeps the pulses from marching. */
  .sig-bar{transition:background .5s ease,opacity .5s ease,height .3s ease}
  .rt-row:not(.is-down) .sig-bars,.cn-main .sig-bars{animation:sig-live 2.8s ease-in-out infinite}
  .rt-row:nth-child(2) .sig-bars{animation-delay:.5s}
  .rt-row:nth-child(3) .sig-bars{animation-delay:1s}
  .rt-row:nth-child(4) .sig-bars{animation-delay:1.5s}
  .rt-row:nth-child(5) .sig-bars{animation-delay:2s}
  @keyframes sig-live{0%,100%{opacity:1}50%{opacity:.82}}

  /* The sweep. ⚠️ This animates WIDTH, which the rest of the motion here avoids.
     It is deliberate and matches the site's existing bar system (.goal-bar,
     .lb-bar, .brow-bar all transition width): a scaleX would squash the 999px
     end caps of a 5px-tall bar into ellipses. One-shot on reveal, five elements,
     no loop, so the layout cost is paid once. The delay lets the row's own fade
     land first, so the bar draws into a row that is already there. */
  html.js .rt-bar i{transition:width .7s cubic-bezier(.2,.8,.2,1) .12s}

  /* Tactile: the copy button and the CTA acknowledge a press. */
  .rt-lock,.rt-off{transition:color .2s ease-out}
  .list-cta .btn-solid:active{transform:translateY(1px) scale(.985)}

  /* FLIP: app.js sets the invert transform, then adds .flipping to play the glide. */
  .rt-row.flipping{transition:transform .42s cubic-bezier(.2,.8,.2,1);will-change:transform}
}

/* ============ Donations — «قهرمانان ما» supporters board ============ */
/* Reuses the leaderboard visual system (.lb-champion/.podium/.board/.brow/.rv).
   One accent stays cyan; the tier emblems are the only added semantic hues
   (t1 light-cyan, t2 cyan, t3 legendary gold). */

.donors{max-width:1060px;margin:0 auto;padding:64px 20px 8px;text-align:center}
.donors h2{font-size:clamp(1.7rem,4.4vw,2.4rem)}

/* Goal thermometer */
.goal{max-width:620px;margin:22px auto 30px;text-align:start}
.goal-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:8px}
.goal-label{font-weight:700;color:var(--text-mid);font-size:.95rem}
.goal-nums{font-size:.9rem;color:var(--text-low)}
.goal-nums b{font-size:1.05rem}
.goal-bar{height:14px;border-radius:999px;background:var(--navy-800);border:1px solid var(--navy-600);overflow:hidden}
.goal-bar i{display:block;height:100%;width:var(--w,0);border-radius:999px;
  background:linear-gradient(90deg,var(--cyan-700),var(--cyan-300))}

/* Champion card retired — landing + /donate now use the 3-person .donor-podium,
   which inherits the .pod glow via row-donor-t* (see the glow block above). */
.champ-badge .crown-ico{width:15px;height:15px;vertical-align:-3px;fill:#F5C451;margin-inline-end:3px}
.donor-tier-name{font-size:.85rem;font-weight:700;color:#fff;margin-top:2px}
.row-donor-t2 .donor-tier-name{color:var(--cyan-300)}
.row-donor-t3 .donor-tier-name{color:var(--violet-300)} /* label is TEXT: violet-300 = ~10:1 on navy (AA); violet-500 was ~4.46:1, under the 4.5 floor */
/* donor podium: amount under the name + the tier label */
.pod-amt{margin-top:6px;font-size:.82rem;color:var(--text-low)}
.pod-amt .mono{font-size:1.15rem;font-weight:700}
.pod-tier{font-size:.8rem;margin-top:2px}
/* One reserved line, clamped: keeps every podium pod the same height whether or
   not there's a note (fixes message-less pods sitting lower) and never wraps. */
.donor-msg{font-size:.9rem;color:var(--text-mid);font-style:italic;margin-top:6px;
  line-height:1.4;min-height:1.4em;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Donor rows (landing reuses .lb-row; /donate reuses .brow) */
.donor-amount{white-space:nowrap}
.donor-msg-inline{color:var(--text-low);font-size:.85rem;font-style:italic;margin-inline-start:6px}
.donor-podium .pod-hours .cy,.donor-champ .champ-hours .cy{font-weight:700}

/* Tier emblems (site-wide, keyed by the emblem span's own class) */
.donor-emblem svg{fill:currentColor}
/* Clear ladder: حامی deep/no-glow -> قهرمان bright+glow -> افسانه violet+crown */
.donor-emblem.donor-t1{color:#fff}
.donor-emblem.donor-t2{color:var(--cyan-300);filter:drop-shadow(0 0 5px rgba(127,227,250,.8))}
.donor-emblem.donor-t3{color:var(--violet-500);filter:drop-shadow(0 0 5px rgba(139,92,246,.85))}
.donor-emblem .emb-crown{fill:var(--violet-300)}
.donor-emblem:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:3px}

/* Empty state — the waiting throne */
.donor-empty{max-width:520px;margin:10px auto 4px;padding:34px 24px;text-align:center;
  background:var(--navy-800);border:1px dashed var(--navy-600);border-radius:16px}
.throne .crown-ico{width:44px;height:44px;fill:#F5C451;filter:drop-shadow(0 0 12px rgba(245,196,81,.5))}
.throne-title{font-size:1.2rem;font-weight:800;color:var(--text-hi);margin-top:10px}
.throne-sub{color:var(--text-mid);margin-top:6px}

.donors-cta{margin-top:26px}
.donors-cta .btn-solid .heart-ico{width:16px;height:16px;vertical-align:-3px;fill:#04222e;margin-inline-end:5px}

/* Nav «حمایت» button */
.nav-donate{display:inline-flex;align-items:center;gap:5px;color:var(--cyan-300)!important;font-weight:700}
.nav-donate .heart-ico{width:15px;height:15px;fill:currentColor}
.nav-donate:hover{color:#fff!important}

/* ---- /donate page ---- */
.donatep{max-width:900px;margin:0 auto;padding:8px 20px 90px}
.donatep h1{font-size:clamp(1.9rem,5vw,2.8rem);text-align:center;margin:18px 0 12px}
.donatep .section-lead{text-align:center;margin-bottom:24px}
.donatep .goal{margin-inline:auto}

.thanks-banner{max-width:620px;margin:12px auto 24px;padding:20px 22px;text-align:center;
  background:var(--navy-800);border:1px solid var(--cyan-700);border-radius:16px;
  box-shadow:0 16px 40px -24px rgba(34,201,242,.6)}
.thanks-title{font-size:1.25rem;font-weight:800;color:var(--cyan-300)}
.thanks-sub{color:var(--text-mid);margin:6px 0 14px}

/* Donate form (bento card) */
.donate-form{max-width:560px;margin:8px auto 34px;padding:24px 22px;text-align:start;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:16px}
/* Not an error: the sign-in method changed. Informational blue, so it does not
   read as "you did something wrong" to someone who did nothing wrong. */
.auth-moved{background:rgba(34,201,242,.10);border:1px solid rgba(34,201,242,.35);
  color:var(--text-mid);border-radius:8px;padding:12px 16px;margin-top:18px;
  font-size:.88rem;line-height:1.9}
.auth-moved a{color:var(--cyan-300);text-decoration:underline}
.form-err{background:rgba(255,90,95,.12);border:1px solid var(--bad);color:#ffd7d8;
  border-radius:10px;padding:9px 12px;margin-bottom:14px;font-size:.9rem}
.df-label{font-weight:700;color:var(--text-hi);margin-bottom:10px}
.amt-presets{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px}
.amt-preset{display:flex;flex-direction:column;align-items:center;gap:2px;padding:12px 6px;cursor:pointer;
  background:var(--navy-700);border:1px solid var(--navy-600);border-radius:12px;color:var(--text-hi);
  font-family:Vazirmatn;font-weight:700;font-size:.95rem;transition:border-color .18s,transform .08s,background .18s}
.amt-preset small{font-size:.72rem;font-weight:700;color:var(--text-low)}
.amt-preset:hover{border-color:var(--cyan-700)}
.amt-preset:active{transform:scale(.97)}
.amt-preset.on{border-color:var(--cyan-500);background:var(--navy-600)}
.amt-preset.on small{color:var(--cyan-300)}
.amt-custom{position:relative;display:flex;align-items:center;margin-bottom:16px}
#donate-amount{flex:1;background:var(--navy-900);border:1px solid var(--navy-600);border-radius:12px;
  color:var(--text-hi);font-family:JBMono,monospace;font-size:1.1rem;padding:13px 16px;width:100%}
#donate-amount:focus{outline:none;border-color:var(--cyan-500)}
.amt-unit{position:absolute;inset-inline-start:14px;color:var(--text-low);font-size:.9rem;pointer-events:none}
.df-field{display:block;margin-bottom:14px}
.df-field span{display:block;font-size:.88rem;color:var(--text-mid);margin-bottom:6px}
.df-field input{width:100%;background:var(--navy-900);border:1px solid var(--navy-600);border-radius:12px;
  color:var(--text-hi);font-family:Vazirmatn;font-size:1rem;padding:12px 14px}
.df-field input[dir=ltr]{font-family:JBMono,monospace}
.df-field input:focus{outline:none;border-color:var(--cyan-500)}
.donate-as{font-size:.92rem;color:var(--text-mid);margin-bottom:14px}
.verified-mark{color:var(--ok);font-weight:700;font-size:.82rem;margin-inline-start:4px}
.df-check{display:flex;align-items:flex-start;gap:9px;font-size:.88rem;color:var(--text-mid);margin-bottom:18px;cursor:pointer}
.df-check input{margin-top:3px;flex:none;accent-color:var(--cyan-500)}
.df-submit{width:100%;display:flex;align-items:center;justify-content:center;gap:7px}
.df-submit .heart-ico{width:17px;height:17px;fill:#04222e}
.df-note{font-size:.8rem;color:var(--text-low);margin-top:12px;text-align:center}

/* Tier legend */
.tier-legend{max-width:660px;margin:36px auto 0;padding:22px;text-align:center;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:16px}
.tl-head{font-weight:800;color:var(--text-hi);margin-bottom:16px}
/* the three tiers as an escalating climb: colour + emblem size grow, افسانه is
   the highlighted pinnacle. Not three identical cards — a ladder. */
.tl-ladder{list-style:none;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:0 0 14px}
.tl-step{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:8px;
  padding:18px 12px;background:var(--navy-900);border:1px solid var(--navy-600);border-radius:14px}
.tl-step .crest{margin:0;cursor:default}
.tl-name{font-weight:800;font-size:1.02rem;color:var(--text-hi)}
.tl-amt{color:var(--text-low);font-size:.82rem;margin:0}
.tl-t1 .crest{width:30px;height:30px;flex:0 0 30px}
.tl-t2 .crest{width:37px;height:37px;flex:0 0 37px}
.tl-t3 .crest{width:44px;height:44px;flex:0 0 44px}
.tl-t2 .tl-name{color:var(--cyan-300)}
.tl-t3 .tl-name{color:var(--violet-300)}
.tl-t3{border-color:rgba(139,92,246,.5);
  background:linear-gradient(180deg,rgba(139,92,246,.12),transparent 72%),var(--navy-900);
  box-shadow:0 0 26px rgba(139,92,246,.12)}
.tl-note{font-size:.86rem;color:var(--text-low);margin:0}
@media(max-width:560px){
  .tl-ladder{grid-template-columns:1fr;gap:8px}
  .tl-step{flex-direction:row;justify-content:flex-start;gap:12px;padding:12px 16px}
  .tl-t1 .crest,.tl-t2 .crest,.tl-t3 .crest{width:28px;height:28px;flex:0 0 28px}
  .tl-amt{margin-inline-start:auto}
}

@media(max-width:560px){
  .amt-presets{grid-template-columns:1fr 1fr}
  /* Three tiers in two columns left the third alone on its own row. It is the
     TOP tier, so spanning it reads as promotion rather than a remainder - the
     same 2+1 treatment the route cards get. :has() keeps it to a set of exactly
     three; a fourth tier would tile 2+2 and must not be touched. */
  .amt-presets:has(> .amt-preset:nth-child(3):last-child) > .amt-preset:last-child{grid-column:1/-1}
  .goal-head{flex-direction:column;gap:2px;align-items:flex-start}
}

@media (prefers-reduced-motion:no-preference){
  .goal-bar i{transition:width 1s cubic-bezier(.2,.8,.2,1)}
  .donor-champ,.amt-preset,.donor-row{transition:box-shadow .2s ease,transform .12s ease,border-color .18s}
  .donatep .brow:hover,.donors .lb-row:hover{transform:translateX(-2px)}
  .donor-emblem.donor-t3{animation:legend-glow 3s ease-in-out infinite}
  @keyframes legend-glow{0%,100%{filter:drop-shadow(0 0 5px rgba(139,92,246,.6))}50%{filter:drop-shadow(0 0 9px rgba(139,92,246,.95))}}
}

/* ============ Donor-system motion + per-donor tier bar ============ */
/* Static structure first (JS-off / reduced-motion safe); animations are gated in
   the no-preference block below. */

/* Per-donor tier-progress bar. Row bars reuse .lb-bar/.brow-bar for width+reveal;
   these rules only recolour them by tier. Podium bars are standalone .tier-bar. */
.tier-bar.donor-t1 i{background:#fff}
.tier-bar.donor-t2 i{background:var(--cyan-500)}
/* progress bar flanked by the current emblem and the (dimmed) target emblem:
   ♥ ═══o─── ♥crown — makes the climb legible. */
.tier-track{display:flex;align-items:center;gap:5px}
.tier-track .crest{width:13px;height:13px;flex:0 0 13px;margin:0;cursor:default}
.tier-track .crest:last-child{opacity:.45} /* the next tier is a dimmed goal */
.tier-track .tier-bar,.tier-track .lb-bar{flex:1 1 auto;min-width:0;margin:0}
.pod .tier-track{max-width:160px;margin:8px auto 0;justify-content:center}
.tier-track .tier-bar{height:5px;background:var(--navy-700);border-radius:999px;overflow:hidden}
.tier-track .tier-bar i{display:block;height:100%;width:var(--w,0%);border-radius:999px}
.tier-next{display:block;font-size:.72rem;color:var(--text-low);margin-top:5px}
/* Landing donor rows: no progress bar (moved to /donate) → drop the bar column,
   and show the donor's note inline instead. */
.donor-rest .lb-row{grid-template-columns:1.6em 28px minmax(120px,1fr) auto}
.donor-rest .donor-msg-inline{display:inline-block;max-width:min(48%,240px);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
/* /donate rows: the inline emblem-progress track needs a real column */
.donor-board .brow{grid-template-columns:2.2em 32px minmax(0,1fr) 118px auto}
.donor-board .tier-track{width:100%}
.tier-next b{color:var(--text-mid)}
.tier-max{display:inline-block;margin-top:8px;font-family:JBMono,monospace;font-weight:800;
  font-size:.82rem;letter-spacing:.12em;color:var(--violet-300);
  border:1px solid rgba(139,92,246,.55);border-radius:999px;padding:2px 12px}

/* Emblem hover (works even with animations off) */
.crest{transition:transform .16s var(--ease-out)}
.crest:hover,.crest:focus-visible{transform:scale(1.2)}

/* Success celebration + toast structure */
.thanks-emblem{position:relative;display:flex;justify-content:center;margin-bottom:10px}
.thanks-emblem .crest{width:56px;height:56px;flex:0 0 56px;margin:0}
.donor-toast-wrap{position:fixed;inset-block-end:18px;inset-inline-start:18px;z-index:60;
  display:flex;flex-direction:column;gap:8px;pointer-events:none;max-width:min(78vw,320px)}
.donor-toast{display:flex;align-items:center;gap:8px;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:12px;padding:9px 14px;font-size:.9rem;
  color:var(--text-mid);box-shadow:0 12px 32px rgba(0,0,0,.4)}
.donor-toast .crest{width:16px}
.dt-bolt{color:var(--cyan-500)}
.dt-bolt svg{fill:currentColor}
/* Toast copy may bold a word; scope it to the toast's own text so it can NEVER
   repaint a Minecraft rank tag. `.donor-toast b` (0,1,1) outranked the
   single-class .hx-p-XXXXXX (0,1,0) and painted every rank's +/++ white --
   the dark-blue/dark-red/green pluses simply vanished. The rank <b> lives at
   .dt-name > .hx-name > .hx-tag > b, so the child combinator misses it. */
.donor-toast .dt-txt > b{color:var(--text-hi)}
.dt-txt{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

@media (prefers-reduced-motion:no-preference){
  /* Idle breathing — bob the character (canvas/img), not the .mc3d box, so the
     contact shadow stays put; #1 floats a touch higher; pause while dragging. */
  .pod .mc3d canvas,.pod .mc3d > img{animation:pod-bob 4.6s ease-in-out infinite}
  .pod-1 .mc3d canvas,.pod-1 .mc3d > img{animation:pod-bob-hi 4.2s ease-in-out infinite}
  .pod .mc3d.grab canvas,.pod .mc3d.grab > img{animation-play-state:paused}
  @keyframes pod-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
  @keyframes pod-bob-hi{0%,100%{transform:translateY(-3px)}50%{transform:translateY(-10px)}}

  /* Emblems pop in on load; the افسانه heart beats instead (later rule wins on t3) */
  .pod-name .crest svg,.lb-id .crest svg,.bwho .crest svg,.champ-name .crest svg{
    animation:crest-pop .5s var(--ease-out)}
  .donor-emblem.donor-t3 svg{animation:legend-beat 3s ease-in-out infinite}
  @keyframes crest-pop{0%{transform:scale(0)}62%{transform:scale(1.2)}100%{transform:scale(1)}}
  @keyframes legend-beat{0%,100%{transform:scale(1)}50%{transform:scale(1.09)}}

  /* Podium rises into the staircase (3rd->2nd->1st stagger comes from app.js) */
  html.js .pod.rv{transform:translateY(28px)}
  html.js .pod-1.rv{transform:translateY(28px) scale(.98)}
  html.js .pod.rv.in{transition:opacity .4s var(--ease-out),transform .5s var(--ease-out),box-shadow .18s ease-out}

  /* Tier bar fills; MAX badge breathes */
  .tier-track .tier-bar i{transition:width .9s var(--ease-out)}
  .tier-max{animation:max-glow 2.6s ease-in-out infinite}
  @keyframes max-glow{0%,100%{box-shadow:0 0 6px rgba(139,92,246,.35)}50%{box-shadow:0 0 15px rgba(139,92,246,.8)}}

  /* Goal thermometer: slow shine sweep across the fill */
  .goal-bar i{position:relative;overflow:hidden}
  .goal-bar i::after{content:"";position:absolute;inset:0;
    background:linear-gradient(100deg,transparent 20%,rgba(255,255,255,.4) 50%,transparent 80%);
    transform:translateX(-100%);animation:goal-shine 4.5s ease-in-out 1.2s infinite}
  @keyframes goal-shine{0%{transform:translateX(-100%)}55%,100%{transform:translateX(200%)}}

  /* Signature: success emblem stamp + expanding glow ring, bigger for higher tier */
  .celebrate .thanks-emblem .crest{animation:stamp .6s var(--ease-out) both}
  .celebrate .thanks-emblem::after{content:"";position:absolute;top:50%;left:50%;
    width:44px;height:44px;border-radius:50%;border:2px solid var(--cyan-500);--rs:6;
    animation:success-ring 1.1s var(--ease-out) .15s forwards}
  .celebrate-t2 .thanks-emblem::after{border-color:var(--cyan-300);--rs:8}
  .celebrate-t3 .thanks-emblem::after{border-color:var(--violet-500);--rs:11;animation-duration:1.4s}
  @keyframes stamp{0%{transform:scale(0) rotate(-14deg);opacity:0}
    60%{transform:scale(1.28) rotate(5deg);opacity:1}100%{transform:scale(1) rotate(0)}}
  @keyframes success-ring{0%{transform:translate(-50%,-50%) scale(.4);opacity:.85}
    100%{transform:translate(-50%,-50%) scale(var(--rs,6));opacity:0}}

  /* Live toast slide */
  .donor-toast{opacity:0;transform:translateY(12px)}
  .donor-toast.in{opacity:1;transform:none;
    transition:opacity .35s var(--ease-out),transform .35s var(--ease-out)}

  /* Form micro-interactions */
  .amt-preset.bounce{animation:bounce-pop .3s var(--ease-out)}
  @keyframes bounce-pop{0%{transform:scale(1)}42%{transform:scale(1.08)}100%{transform:scale(1)}}
  #donate-amount.shake{animation:shake .4s}
  @keyframes shake{10%,90%{transform:translateX(-2px)}30%,70%{transform:translateX(4px)}50%{transform:translateX(-5px)}}
  .df-submit.loading{animation:pulse-dim 1s ease-in-out infinite}
  @keyframes pulse-dim{0%,100%{opacity:1}50%{opacity:.6}}
}

/* ============ Tabbed leaderboards (قهرمانان ما ⇄ لیدربورد هفتگی) ============ */
/* CSS-only radio tabs: no JS, CSP-safe, keyboard-accessible. Donors default. */
.boards{max-width:1060px;margin:0 auto;padding:64px 20px 8px}
.board-radio{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}

.board-tabs{display:flex;max-width:460px;margin:0 auto 26px;background:var(--navy-800);
  border:1px solid var(--navy-600);border-radius:999px;padding:5px}
.board-tab{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;
  padding:11px 14px;border-radius:999px;cursor:pointer;color:var(--text-mid);font-weight:700;
  font-size:.98rem;white-space:nowrap;transition:color .2s ease,background .25s ease}
.board-tab .heart-ico{width:15px;height:15px;fill:currentColor}
.board-tab:hover{color:var(--cyan-300)}
#tab-donors:checked ~ .board-tabs label[for="tab-donors"],
#tab-weekly:checked ~ .board-tabs label[for="tab-weekly"]{background:var(--cyan-500);color:#04222e}
#tab-donors:checked ~ .board-tabs label[for="tab-donors"]:hover,
#tab-weekly:checked ~ .board-tabs label[for="tab-weekly"]:hover{color:#04222e}
#tab-donors:focus-visible ~ .board-tabs label[for="tab-donors"],
#tab-weekly:focus-visible ~ .board-tabs label[for="tab-weekly"]{outline:2px solid var(--cyan-500);outline-offset:2px}

.board-panel{display:none}
#tab-donors:checked ~ .panel-donors{display:block}
#tab-weekly:checked ~ .panel-weekly{display:block}
/* A hidden tab never triggers the scroll observer, so inside the component we
   force .rv visible — the tab switch is the reveal. */
html.js .board-panel .rv,html.js .board-panel .rv-row{opacity:1;transform:none}
.board-panel{max-width:760px;margin:0 auto}
.board-lead{max-width:620px;margin:0 auto 22px;text-align:center;color:var(--text-mid)}

@media (prefers-reduced-motion:no-preference){
  #tab-donors:checked ~ .panel-donors,#tab-weekly:checked ~ .panel-weekly{animation:board-fade .3s ease-out}
  @keyframes board-fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
}

.donor-more{text-align:center;margin-top:14px;color:var(--text-low);font-size:.9rem}
.donor-more a{color:var(--cyan-300);font-weight:700}

/* --- phone + OTP auth ------------------------------------------------------ */
/* The number and the code are the only thing on their screens, so they get real
   presence. Both are LTR islands inside the RTL page. */
.phone-input{font-size:1.15rem;letter-spacing:.06em;text-align:center}
.auth-change{margin-inline-start:6px;font-size:.82rem}
.dev-note{border:1px dashed var(--navy-600);border-radius:var(--r-ctl,6px);padding:8px 10px}
/* Touch targets: 44px minimum, per the mobile audit. */
.auth-panel .card-form input,.auth-panel .card-form button{min-height:44px}
.auth-panel .btn-ghost{min-height:44px}
.set-h{font-size:.95rem;color:var(--text-hi);margin:18px 0 4px}
.set-h:first-of-type{margin-top:0}
.form-ok{background:rgba(61,220,132,.1);border:1px solid rgba(61,220,132,.4);
  color:var(--ok);border-radius:8px;padding:10px 13px;font-size:.88rem;margin-bottom:16px}
.steps-inline{margin:0 0 14px;padding-inline-start:18px;font-size:.86rem;
  color:var(--text-low);line-height:2}

/* ============================================================================
   AUTH MOTION  (phone step + OTP step)
   ---------------------------------------------------------------------------
   Every animation here earns its place, in one sentence each:
     entry stagger  - establishes reading order on a page with one job
     field check    - says "this number is complete" before the button is pressed
     travelling ring- says "you are here now" without six blinking boxes
     cell pop       - acknowledges each keystroke on a field with no other feedback
     button lock    - acknowledges the tap and makes a double submit impossible
     resend counter - turns a dead button into a visible wait
     shake          - a wrong code is the one moment worth being physical about
   Durations sit in the 150-300ms band; entrances ease out, exits are quicker.
   Only transform and opacity are animated. Everything collapses under
   prefers-reduced-motion at the bottom of this block.
   ========================================================================== */

.auth-narrow{--ease-out:cubic-bezier(.16,1,.3,1)}

/* --- entry ---------------------------------------------------------------- */

@keyframes auth-rise{from{opacity:0;transform:translate3d(0,14px,0) scale(.985)}
  to{opacity:1;transform:none}}
@keyframes auth-step-in{from{opacity:0;transform:translate3d(-18px,0,0)}
  to{opacity:1;transform:none}}
@keyframes auth-item{from{opacity:0;transform:translate3d(0,10px,0)}to{opacity:1;transform:none}}

/* The panel itself arrives once. The second step slides in from the inline-end
   side (left, in RTL) so the two screens read as one flow moving forward. */
.step-in{animation:auth-rise .34s var(--ease-out) both}
.step-in[data-step="code"]{animation-name:auth-step-in;animation-duration:.3s}

.stagger{animation:auth-item .34s var(--ease-out) both}
.s0{animation-delay:.05s}
.s1{animation-delay:.10s}
.s2{animation-delay:.13s}
.s3{animation-delay:.17s}
.s4{animation-delay:.21s}
.s5{animation-delay:.25s}
.s6{animation-delay:.29s}

/* An error is the reason the page reloaded, so it arrives last and announces. */
.auth-panel .form-err{animation:auth-item .3s var(--ease-out) .1s both}

/* --- phone field ---------------------------------------------------------- */

.field{position:relative;display:block}
.field input{width:100%}
/* Room for the tick so a long number never slides under it. */
.field .phone-input{padding-inline-end:42px}

.field-ok{position:absolute;inset-inline-end:12px;top:50%;width:20px;height:20px;
  margin-top:-10px;fill:none;stroke:var(--ok);stroke-width:2.4;stroke-linecap:round;
  stroke-linejoin:round;opacity:0;pointer-events:none}
.field-ok path{stroke-dasharray:30;stroke-dashoffset:30}
/* Drawn, not faded: the stroke finishes exactly when the number does. */
.field.ok .field-ok{opacity:1}
.field.ok .field-ok path{transition:stroke-dashoffset .28s var(--ease-out);stroke-dashoffset:0}

/* --- OTP cells ------------------------------------------------------------ */

.otp{--cw:46px;--cg:8px;position:relative;direction:ltr}

/* With JavaScript the real input goes transparent and sits on top of the cells,
   keeping focus, paste, autofill and the screen reader on the REAL field. */
.otp.cells-on .code-input{position:relative;z-index:2;color:transparent;background:none;
  border-color:transparent;box-shadow:none;caret-color:transparent;letter-spacing:0;
  height:56px;width:100%}
.otp.cells-on .code-input::placeholder{color:transparent}
.otp.cells-on .code-input:focus{box-shadow:none}

.otp-cells{display:none}
.otp.cells-on .otp-cells{position:absolute;inset:0;z-index:1;display:flex;
  justify-content:center;align-items:center;gap:var(--cg);pointer-events:none}

.otp-cell{position:relative;width:var(--cw);height:56px;border-radius:8px;
  background:var(--navy-900);border:1px solid var(--navy-600);
  display:flex;align-items:center;justify-content:center;
  font-family:JBMono,monospace;font-size:1.35rem;color:var(--text-hi);
  transition:border-color .18s ease-out,background .18s ease-out}
.otp-cell.filled{border-color:rgba(34,201,242,.35);background:var(--navy-800)}
@keyframes otp-pop{0%{transform:scale(1)}45%{transform:scale(1.07)}100%{transform:scale(1)}}
.otp-cell.pop{animation:otp-pop .16s ease-out}

/* The caret only exists on the cell being typed into. */
.otp-cell.active::after{content:"";position:absolute;width:2px;height:22px;
  background:var(--cyan-500);border-radius:1px;animation:otp-caret 1.05s steps(1) infinite}
@keyframes otp-caret{0%,49%{opacity:1}50%,100%{opacity:0}}
.otp-cell.filled.active::after{display:none}

/* One ring for six cells: it slides, so the eye follows a thing rather than
   re-finding a new highlight. --i is set from JS (CSSOM, allowed under CSP). */
/* --x and --w are measured from the real cell by app.js (CSSOM, allowed under
   CSP). They are not derived from --cw and --cg: the row is centred, so any
   arithmetic from the container edge lands the ring in the gap between cells. */
.otp-ring{position:absolute;inset-block:0;left:0;width:var(--w,var(--cw));
  border-radius:8px;border:1.5px solid var(--cyan-500);box-shadow:var(--glow-sm);
  opacity:0;transform:translateX(var(--x,0));
  transition:transform .22s var(--ease-out),opacity .18s ease-out}
.otp.focused .otp-ring{opacity:1}

/* Success: the cells confirm from left to right before the page navigates. */
@keyframes otp-done{0%{transform:translateY(0)}40%{transform:translateY(-5px)}
  100%{transform:translateY(0)}}
.otp.done .otp-cell{animation:otp-done .3s var(--ease-out) both;border-color:var(--ok)}
.otp.done .otp-cell:nth-child(2){animation-delay:.03s}
.otp.done .otp-cell:nth-child(3){animation-delay:.06s}
.otp.done .otp-cell:nth-child(4){animation-delay:.09s}
.otp.done .otp-cell:nth-child(5){animation-delay:.12s}
.otp.done .otp-cell:nth-child(6){animation-delay:.15s}
.otp.done .otp-ring{opacity:0}

/* A wrong code is worth being physical about, once. */
@keyframes otp-shake{0%,100%{transform:translateX(0)}
  20%{transform:translateX(-6px)}40%{transform:translateX(5px)}
  60%{transform:translateX(-3px)}80%{transform:translateX(2px)}}
.otp.wrong{animation:otp-shake .38s ease-out}
.otp.wrong .otp-cell{border-color:rgba(255,90,95,.55)}

/* --- submit button -------------------------------------------------------- */

.auth-go{position:relative}
.auth-go .go-lbl{display:inline-block;transition:opacity .16s ease-out,transform .16s ease-out}
.go-spin{position:absolute;inset:0;margin:auto;width:18px;height:18px;border-radius:50%;
  border:2px solid rgba(4,34,46,.35);border-top-color:#04222e;opacity:0}
/* The label leaves and the ring arrives in the same 160ms, so the button never
   looks empty mid-swap. */
.auth-go.busy{cursor:progress}
.auth-go.busy .go-lbl{opacity:0;transform:translateY(4px)}
.auth-go.busy .go-spin{opacity:1;animation:go-spin .7s linear infinite}
@keyframes go-spin{to{transform:rotate(360deg)}}

/* --- resend countdown ----------------------------------------------------- */

.rs-btn{position:relative;overflow:hidden}
.rs-btn[disabled]{opacity:.65;cursor:default}
/* A depleting line under the label: the wait becomes something you can see
   ending, instead of a button that looks broken. */
.rs-bar{position:absolute;inset-inline-start:0;bottom:0;height:2px;width:100%;
  background:var(--cyan-700);transform-origin:left center;transform:scaleX(var(--p,0));
  opacity:0;transition:transform 1s linear}
.rs-btn[disabled] .rs-bar{opacity:1}

/* --- motion off ----------------------------------------------------------- */

@media (prefers-reduced-motion:reduce){
  .step-in,.stagger,.auth-panel .form-err,.otp.wrong,.otp.done .otp-cell,.otp-cell.pop{
    animation:none}
  .step-in,.stagger,.auth-panel .form-err{opacity:1;transform:none}
  .otp-ring,.otp-cell,.auth-go .go-lbl,.rs-bar{transition:none}
  .otp-cell.active::after{animation:none;opacity:1}
  .auth-go.busy .go-spin{animation:none}
  .field.ok .field-ok path{transition:none;stroke-dashoffset:0}
}

@media(max-width:420px){
  .otp{--cw:40px;--cg:6px}
  .otp-cell{height:52px;font-size:1.2rem}
  .otp.cells-on .code-input{height:52px}
}

/* Secondary ways out of the sign-in page: a different door (phone), or the way
   back in (reset). Two links, so they get a row rather than the single-item
   layout .auth-alt was written for. */
.auth-alt{display:flex;gap:var(--sp-4);flex-wrap:wrap;align-items:center;justify-content:space-between}
.auth-alt a{color:var(--cyan-500);font-size:.88rem}
.auth-alt-link{margin-top:18px;padding-top:16px;border-top:1px solid var(--navy-700);font-size:.88rem}
.auth-foot{margin-top:18px}


/* ============================================================================
   ACCOUNT
   ---------------------------------------------------------------------------
   Three rules shape this page.

   1. The banner is ABOUT a person, so it holds a person: character and name,
      two children, echoing .lb-champion. The previous version put the character,
      a ring and a text block in one flex row at heights 240/132/90, which is
      where the "somebody dropped these here" reading came from.
   2. Rank colour comes from glowrank, the same resolver the leaderboard and the
      podium use, so the .row-* / .is-* families below already paint it. Nothing
      here invents a colour; an owner who also donates glows gold, not violet.
   3. Everything below the fold states its current value and opens only when the
      person wants to change it.
   ========================================================================== */

/* --- identity banner ------------------------------------------------------ */

.acct-hero{position:relative;display:flex;align-items:center;gap:20px;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:14px;
  padding:20px 22px;margin-bottom:14px}
/* The badge sits ON the edge rather than inside the stack, which welds the label
   to the card. Same device as .champ-badge and .pod-rank. */
.acct-rank{position:absolute;top:-11px;inset-inline-start:22px;
  background:var(--navy-700);border:1px solid var(--navy-600);color:var(--text-hi);
  font-size:.72rem;font-weight:700;border-radius:var(--r-pill,999px);padding:2px 14px}
.acct-figure{width:96px;height:192px;flex:0 0 96px;object-fit:contain;
  filter:drop-shadow(0 12px 22px rgba(0,0,0,.45))}
.acct-figure.inactive{filter:grayscale(1);opacity:.55}
.acct-who{min-width:0;flex:1}
.hero-name{font-size:1.2rem;margin-bottom:8px;line-height:1.5}
/* NOT .acct-name: the nav chip has owned that class since the header was built,
   and it clips to 11ch with an ellipsis. Reusing it here truncated the name to
   "...viperX" in the banner. */

/* The rank's own colour on the badge, so the word and the glow always agree. */
.row-founder .acct-rank{background:#FFD54A;color:#3a2c00;border-color:transparent}
.row-partner .acct-rank{background:#FF3D3D;color:#2c0000;border-color:transparent}
.row-donor-t1 .acct-rank{background:rgba(255,255,255,.9);color:#111;border-color:transparent}
.row-donor-t2 .acct-rank{background:var(--cyan-300);color:#04222e;border-color:transparent}
.row-donor-t3 .acct-rank{background:var(--violet-500);color:#fff;border-color:transparent}

/* Card-shaped rank treatment, matching .pod / .lb-champion exactly. */
.acct-hero.row-founder{border-color:rgba(255,213,74,.5);
  background-image:linear-gradient(180deg,rgba(255,213,74,.13),transparent 60%);
  box-shadow:0 0 26px rgba(255,199,64,.12)}
.acct-hero.row-partner{border-color:rgba(255,61,61,.5);
  background-image:linear-gradient(180deg,rgba(255,61,61,.13),transparent 60%);
  box-shadow:0 0 26px rgba(255,61,61,.12)}
.acct-hero.row-donor-t1{border-color:rgba(255,255,255,.38);
  background-image:linear-gradient(180deg,rgba(255,255,255,.08),transparent 60%);
  box-shadow:0 0 20px rgba(255,255,255,.09)}
.acct-hero.row-donor-t2{border-color:rgba(127,227,250,.55);
  background-image:linear-gradient(180deg,rgba(127,227,250,.15),transparent 60%);
  box-shadow:0 0 28px rgba(127,227,250,.18)}
.acct-hero.row-donor-t3{border-color:rgba(139,92,246,.5);
  background-image:linear-gradient(180deg,rgba(139,92,246,.14),transparent 60%);
  box-shadow:0 0 26px rgba(139,92,246,.14)}
/* The character carries it too, so the colour crowns the PERSON and not a box. */
.acct-figure.is-founder{filter:drop-shadow(0 12px 22px rgba(0,0,0,.45)) drop-shadow(0 0 10px rgba(255,199,64,.45))}
.acct-figure.is-partner{filter:drop-shadow(0 12px 22px rgba(0,0,0,.45)) drop-shadow(0 0 10px rgba(255,61,61,.45))}
.acct-figure.is-donor-t1{filter:drop-shadow(0 12px 22px rgba(0,0,0,.45)) drop-shadow(0 0 8px rgba(255,255,255,.32))}
.acct-figure.is-donor-t2{filter:drop-shadow(0 12px 22px rgba(0,0,0,.45)) drop-shadow(0 0 12px rgba(127,227,250,.55))}
.acct-figure.is-donor-t3{filter:drop-shadow(0 12px 22px rgba(0,0,0,.45)) drop-shadow(0 0 10px rgba(139,92,246,.5))}

/* --- the subscription ring: arc colours per state ------------------------- */
/* The ring lives in the tier card (.tier-card__ring, Plus UI: account); these
   rules only colour the arc, so they stay put. */
.ring-track{stroke:var(--navy-700)}
.ring-arc{stroke:var(--cyan-500)}
.ring-warn .ring-arc{stroke:var(--warn)}
.ring-off .ring-arc{stroke:none}
.ring-inf .ring-arc{stroke:var(--cyan-300);filter:drop-shadow(0 0 6px rgba(34,201,242,.55))}
/* Only .ring-inf used to glow, so an ordinary live subscription looked flat next
   to the VIP one. Each state glows in its OWN arc colour. */
.ring-on .ring-arc{filter:drop-shadow(0 0 5px rgba(34,201,242,.45))}
.ring-warn .ring-arc{filter:drop-shadow(0 0 5px rgba(255,176,32,.45))}
/* --- sections ------------------------------------------------------------- */

/* The global `section{padding:56px 0}` (line 227) applies here too and put ~112px
   of dead air inside every block, which is where the page's empty bands came
   from. Same fix the FAQ page already uses: neutralise it and let the block's own
   margin do the spacing. The global `section h2` is centred at up to 2.1rem,
   which is also why these headings read as generic. */
.acct section{padding:0}
.acct section h2{font-size:1.1rem;text-align:start;margin-bottom:8px}
.acct-block,.acct-buy{margin-bottom:30px}
.acct-lead{font-size:.9rem;color:var(--text-low);line-height:1.9;margin-bottom:14px;
  max-width:none;text-align:start}
.acct-line{font-size:.95rem;color:var(--text-mid);margin:4px 0 8px}
.acct-block .addr-pill{width:100%}
.acct-block .hint{margin-top:8px}
/* Gifting and redeeming are real, and neither deserves a panel. */
.acct-quiet{margin-top:14px;font-size:.88rem;color:var(--text-low)}
.acct-quiet a{color:var(--cyan-500)}
.dot-sep{margin:0 8px;color:var(--navy-600)}
.acct-support{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:18px}
.acct-support a{color:var(--cyan-500);font-size:.88rem}

/* --- disclosures: same anatomy as the FAQ, one line per fact -------------- */

.acct-fold{background:var(--navy-800);border:1px solid var(--navy-600);
  border-radius:12px;margin-bottom:10px;padding:0 18px}
.acct-fold summary{cursor:pointer;list-style:none;position:relative;
  display:flex;align-items:center;gap:12px;min-height:52px;padding-inline-end:26px}
.acct-fold summary::-webkit-details-marker{display:none}
.acct-fold summary::after{content:"+";position:absolute;inset-inline-end:0;top:50%;
  transform:translateY(-50%);color:var(--cyan-500);font-family:JBMono,monospace;font-size:1.2rem}
.acct-fold[open] summary::after{content:"–"}
.acct-fold[open]{border-color:var(--cyan-700)}
.acct-fold summary:hover{color:var(--cyan-300)}
.acct-fold summary:focus-visible{outline:2px solid var(--cyan-700);outline-offset:2px;border-radius:8px}
.fold-k{font-weight:700;color:var(--text-hi);font-size:.95rem;flex:0 0 auto}
.fold-v{color:var(--text-low);font-size:.9rem;margin-inline-start:auto;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fold-form{display:flex;flex-direction:column;gap:6px;padding:0 0 18px;text-align:start}
.fold-form label{color:var(--text-hi);font-weight:700;font-size:.9rem;margin-top:10px}
.fold-form input{background:var(--navy-900);border:1px solid var(--navy-600);border-radius:8px;
  color:var(--text-hi);padding:11px 13px;font-size:.95rem;font-family:inherit;min-height:44px}
.fold-form input:focus{border-color:var(--cyan-700);outline:none;box-shadow:var(--glow-sm)}
.fold-form .hint{font-size:.82rem;color:var(--text-low)}
.fold-form button{margin-top:16px;align-self:flex-start}
/* Purchases live inside a fold, so the old section chrome has to go. */
.acct-fold .profile-history{padding:0 0 14px}
.acct-fold .profile-history h2{display:none}

/* Leaving is not a primary action anywhere on this page. */
.acct-exit{display:flex;gap:16px;flex-wrap:wrap;margin-top:22px;
  padding-top:16px;border-top:1px solid var(--navy-700)}
.btn-quiet{background:none;border:0;color:var(--text-low);font-family:Vazirmatn;
  font-size:.88rem;cursor:pointer;padding:6px 0;min-height:44px}
.btn-quiet:hover{color:var(--bad)}

@media(max-width:560px){
  .acct-hero{gap:16px;padding:18px}
  .acct-figure{width:76px;height:152px;flex:0 0 76px}
  .hero-name{font-size:1.05rem}
}

@media (prefers-reduced-motion: no-preference){
  /* The arc sweeps in from nothing to whatever ringSVG wrote as the element's
     own stroke-dasharray - there is no `to`, so the base value IS the target
     and one keyframe covers every percentage. This only works because a full
     ring now carries a dasharray too; while 100% emitted none, `0 340 -> none`
     was not interpolable and the animation silently did not run. */
  .sub-ring .ring-arc{animation:ring-draw .9s cubic-bezier(.22,.61,.36,1) both}
  @keyframes ring-draw{from{stroke-dasharray:0 340}}

  /* The page's ONE perpetual animation, and only in the state that has
     something to say. Under a week left, the glow breathes; a subscription
     that is comfortably alive holds still. Opacity on a filter only - no
     layout, no repaint of the arc geometry. */
  .ring-warn .ring-arc{animation:ring-draw .9s cubic-bezier(.22,.61,.36,1) both,
                                 ring-urge 2.4s ease-in-out 1.1s infinite}
  @keyframes ring-urge{
    0%,100%{filter:drop-shadow(0 0 4px rgba(255,176,32,.30))}
    50%    {filter:drop-shadow(0 0 9px rgba(255,176,32,.70))}
  }

  .acct-fold{transition:border-color .18s ease-out}
  /* The one interaction on this page that snapped: native <details> shows its
     content on the same frame the marker flips. ::details-content plus
     allow-discrete gives it a height transition with no JavaScript, and where
     the browser does not support them the rules are simply ignored and the
     fold behaves exactly as it does today. */
  /* interpolate-size is REQUIRED for block-size:0 -> auto to transition at all;
     without it the value snaps and this whole block is a silent no-op. Scoped to
     the fold rather than :root so it cannot change how any other size animates
     elsewhere on the site. */
  /* Shared with .faq details, which is the same component: a summary, a +/-
     marker that swaps its content, and a body that used to appear on the same
     frame the marker flipped. An FAQ is eleven of these in a column, so it is
     the page where the snap was most obvious. */
  .acct-fold,.faq details{interpolate-size:allow-keywords}
  .acct-fold::details-content,.faq details::details-content{
    block-size:0;overflow:hidden;opacity:0;
    transition:block-size .26s var(--ease-out),content-visibility .26s allow-discrete,
               opacity .2s ease-out;
  }
  .acct-fold[open]::details-content,.faq details[open]::details-content{block-size:auto;opacity:1}
  /* No transform on the ::after marker: its `content` already swaps + for -, so
     rotating it animates a character that has been replaced mid-flight. */
  .acct-fold summary .ic{transition:color .18s ease-out}

  /* The first-run steps arrive one after the other rather than as one block:
     they are a sequence, and reading them in order is the point. */
  .acct-first .steps-card li{animation:step-in .42s var(--ease-out) both}
  .acct-first .steps-card li:nth-child(2){animation-delay:.10s}
  @keyframes step-in{from{opacity:0;transform:translateY(10px)}}
}

/* --- sign-in: the second door -------------------------------------------- */
/* Email and password is the primary button. The phone is the only way a NEW
   customer can get in at all, so it gets a real button under a divider rather
   than a link that reads as fine print. */
.auth-or{display:flex;align-items:center;gap:12px;margin:22px 0 14px;
  color:var(--text-low);font-size:.82rem}
.auth-or::before,.auth-or::after{content:"";flex:1;height:1px;background:var(--navy-700)}
.btn-outline{display:flex;align-items:center;justify-content:center;width:100%;
  min-height:48px;background:none;border:1px solid var(--cyan-700);color:var(--cyan-300);
  border-radius:12px;font-family:Vazirmatn;font-weight:700;font-size:.98rem;
  text-decoration:none;cursor:pointer}
.btn-outline:hover{background:rgba(34,201,242,.08);color:var(--cyan-300)}
.auth-forgot{margin-top:16px;text-align:center;font-size:.86rem}
.auth-forgot a{color:var(--text-low)}
.auth-forgot a:hover{color:var(--cyan-300)}
@media (prefers-reduced-motion: no-preference){
  .btn-outline{transition:background .18s ease-out,box-shadow .18s ease-out}
  .btn-outline:hover{box-shadow:var(--glow-sm)}
}

/* --- account icons and section heads -------------------------------------- */
/* The .ic family (Lucide, stroke 1.7, sized in CSS) reaches a public page for
   the first time here. Every icon inherits currentColor, so none of them carries
   a colour of its own and the one-accent rule holds. */

/* Size and vertical anchoring come from .ic-row above; this only sets colour. */
.sec-head{display:flex;align-items:center;gap:9px;margin-bottom:8px}
.sec-head h2{margin-bottom:0}
.sec-head .ic{width:18px;height:18px;flex:none;color:var(--cyan-500)}

/* Two classes deep, like .acct .tier-card (Plus UI: account): the `.acct section{padding:0}`
   reset is (0,1,1) and silently stripped this card's padding, which is why its
   text sat against the border. A card that loses its padding does not error, it
   just looks broken. */
.acct .acct-card{background:var(--navy-800);border:1px solid var(--navy-600);
  border-radius:14px;padding:18px 20px;margin-bottom:26px}
/* The pill is full width here rather than hugging its content as it does on the
   landing page, so the address has to GROW into the row. Without this the button
   sits stranded mid-line with dead space beside it. */
.acct-card .addr-pill{width:100%}
.acct-card .addr-pill code{flex:1;min-width:0;font-size:1.15rem;padding:14px 18px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-card .addr-pill button{flex:none}
.acct-card .hint{margin-top:10px}
/* The verify address sits in a list item, not a card, so the .steps-card chip
   rule never reached it and it rendered as bare text. */
.steps-inline .addr{display:inline-block;background:var(--navy-900);
  border:1px solid var(--navy-600);border-radius:8px;padding:2px 9px;
  color:var(--cyan-300);font-size:.86rem;letter-spacing:.02em}

.acct-fold summary .ic{width:16px;height:16px;flex:none;color:var(--text-low)}
.acct-fold[open] summary .ic,.acct-fold summary:hover .ic{color:var(--cyan-500)}

/* The state with nothing in it gets the most deliberate composition on the page:
   one mark, one sentence, and the action that ends it sitting directly below. */
.empty-state{display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:6px;padding:26px 18px 22px;margin-bottom:18px;
  background:var(--navy-800);border:1px dashed var(--navy-600);border-radius:14px}
.empty-state .ic{width:30px;height:30px;color:var(--text-low);opacity:.8}
.empty-line{font-size:1rem;font-weight:700;color:var(--text-mid);margin-top:4px}
.empty-sub{font-size:.88rem;color:var(--text-low)}
.empty-state.small{padding:20px 16px 18px;margin-bottom:14px}
.empty-state.small .ic{width:24px;height:24px}
/* The expired status card carries its own action, so the page never states the
   same thing twice. Small and inline: it is a way out, not a second hero. */
.status-cta{display:inline-block;margin-top:10px;padding:8px 18px;font-size:.9rem}

@media (prefers-reduced-motion: no-preference){
  .acct-fold summary .ic{transition:color .18s ease-out}
}

/* --- first run ------------------------------------------------------------ */
/* .steps-card is a grid, and a grid item's min-width is `auto`, so each <li>
   refuses to shrink below its min-content: the address and the code input made
   that 456px inside a 390px viewport, which pushed the whole RTL column off the
   screen. Every level from the grid item down to the flex child has to be told
   it may shrink. */
.steps-card li{min-width:0}
.steps-card .step-b{min-width:0;flex:1}
.acct-first .card-form,.acct-first .card-form input{min-width:0;max-width:100%}
.acct-first .steps-card{margin-top:var(--sp-4)}
.acct-first .card-form{margin-top:14px}

/* The address is the one string here a new player must read and type into a game
   client, so it may never be ellipsised. It is not inside an .acct-card (see the
   template), so it needs its own size: the landing page's 1.4rem is tuned for a
   14-character address at full card width, and this one is 21 characters inside
   a step indent. */
.acct-first .addr-pill{width:100%}
.acct-first .addr-pill code{flex:1;min-width:0;padding:13px 14px;white-space:nowrap;
  /* .steps-card .addr (style.css:1238) styles a BARE address sitting loose in a
     step - its own chip with a background, a 1px border, a 10px radius and a
     10px top margin. Inside the pill that chip became a second box within the
     pill's own border, and the margin pushed the address 10px below the copy
     button it is supposed to sit level with. The pill owns this element here. */
  display:flex;align-items:center;background:none;border:0;border-radius:0;margin-top:0;
  /* Sized to the viewport rather than to a breakpoint. 21 characters of mono
     inside a step indent has no single size that survives 320px through 640px,
     and every fixed value I tried clipped at one end or looked shrunken at the
     other. Capped at 1.1rem so it stops growing once there is room. */
  font-size:clamp(.78rem,3.9vw,1.1rem)}
.acct-first .addr-pill button{flex:none;padding:0 18px}
/* Phone: the copy button's 97px is more than the address can spare, so the pill
   stacks and each half gets the full width. The divider moves to the new seam. */
@media(max-width:560px){
  .acct-first .addr-pill{flex-direction:column;align-items:stretch}
  .acct-first .addr-pill code{justify-content:center}
  .acct-first .addr-pill button{border-inline-start:0;border-top:1px solid var(--navy-600);
    padding:11px 22px;justify-content:center}
}

/* Step two is a promise, not an action yet. Muted rather than disabled-looking:
   there is nothing here to click, so nothing should look clickable and fail. */
.acct-first .step-next{background:transparent;border-style:dashed}
.acct-first .step-next .step-n{background:transparent;border:1px solid var(--navy-600);color:var(--text-low)}
.acct-first .step-next .step-t{color:var(--text-mid)}

/* The gift-card link sat flush against the next section's heading: zero gap,
   measured, so the two read as one block. */
.acct-first{margin-bottom:var(--sp-6)}
/* The one field a first-time visitor has to find. Its inherited border was
   var(--navy-600) on the step card, which measures 1.42:1 - under the 3:1 WCAG
   asks of a control boundary, and it looked like a dark rectangle rather than an
   input. var(--text-low) gives 5.67:1 and stays neutral, so the cyan accent
   still means "primary action" and is saved for the button below it.
   NOTE: .card-form input (style.css:354) has the same weak border everywhere
   else on the site. Left alone here on purpose - that is a site-wide change. */
.acct-first .step-d a{color:var(--cyan-500)}
.acct-first .code-input{border-color:var(--text-low)}
.acct-first .code-input:focus{border-color:var(--cyan-500)}
/* A form already inside a card does not get a second card. Capping the FORM was
   the wrong fix: it left the block's start edge 160px inside the address pill
   directly above it (L323 against L163 at 902px), which is what read as spacing
   being all over the place. The same move .auth-panel makes at style.css:1190.
   The width limit belongs on the field instead - a six-character code does not
   want a 520px input - so the block keeps the column's edges while the control
   stays the size of its content. */
.acct-first .card-form{background:none;border:0;padding:0}
/* Only cap the control once there is a column wide enough for the cap to read as
   deliberate. On a phone the step body is 270px, so a 220px field just leaves a
   50px ragged edge against the full-width pill above it. */
@media(min-width:561px){
  .acct-first .card-form input,
  .acct-first .card-form button{max-width:220px}
}
/* Vertical rhythm on the project's own --sp scale. It was running 4/12/16/6/22,
   which is what "spacing all over the place" looks like from the inside: the
   label sat closer to the pill above it than to the field it labels. Explicit
   margins with the flex gap zeroed, so each number is stated once. */
.acct-first .card-form{gap:0;margin-top:var(--sp-5)}
.acct-first .card-form label{margin-top:0}
.acct-first .card-form input{margin-top:var(--sp-2)}
.acct-first .card-form button{margin-top:var(--sp-4)}
.acct-first .addr-pill{margin-top:var(--sp-4)}

/* --- /store ---------------------------------------------------------------- */
/* The page's own closed state, stated where reading starts. Muted amber, not a
   red alarm: nothing is broken, the shop is shut. The site-wide .site-notice
   banner is a different thing and keeps its own louder treatment. */
.store-shut{display:flex;align-items:baseline;gap:9px;justify-content:center;
  max-width:620px;margin:-18px auto 30px;padding:11px 16px;
  background:rgba(255,176,32,.08);border:1px solid rgba(255,176,32,.28);
  border-radius:12px;color:var(--text-mid);font-size:.9rem;text-align:start}
.plan-row{margin-top:var(--sp-6)}
.store-shut .ic{width:17px;height:17px;flex:none;color:var(--warn);transform:translateY(0.25em)}
.store-steps{margin-top:var(--sp-6)}
/* The steps list is the same component the account first-run uses, so it needs
   the same shrink permission: .steps-card is a grid and a grid item will not go
   below its content width without min-width:0. That is what grew the page three
   separate times today. */
.store-steps .steps-card{margin-top:14px}
.store-steps .step-b{min-width:0;flex:1}

/* --- donate: the tier you are buying, shown while you choose it -------------- */
/* One source of colour for all three tiers, so the form, the preset and the
   emblem cannot drift apart. These are the values .donor-emblem already uses. */
.amt-preset.tier-t1{--tier:#ffffff;--tier-soft:rgba(255,255,255,.38)}
.amt-preset.tier-t2{--tier:var(--cyan-300);--tier-soft:rgba(127,227,250,.55)}
.amt-preset.tier-t3{--tier:var(--violet-300);--tier-soft:rgba(139,92,246,.55)}
.donate-form[data-tier="1"]{--tier:#ffffff;--tier-soft:rgba(255,255,255,.38);--tier-glow:rgba(255,255,255,.16)}
.donate-form[data-tier="2"]{--tier:var(--cyan-300);--tier-soft:rgba(127,227,250,.55);--tier-glow:rgba(127,227,250,.20)}
.donate-form[data-tier="3"]{--tier:var(--violet-300);--tier-soft:rgba(139,92,246,.55);--tier-glow:rgba(139,92,246,.22)}

/* The emblem sits above the amount, so the button reads reward → price → name. */
.amt-preset .crest{width:26px;height:26px;flex:0 0 26px;margin-bottom:2px}
.amt-preset .amt-n{font-weight:700}

/* Selected: the preset takes its OWN tier rather than the one cyan every tier
   used to share. .on already existed; these override its colour per tier. */
.amt-preset.on{border-color:var(--tier-soft);background:var(--navy-600);
  box-shadow:0 0 0 1px var(--tier-soft),0 0 18px -4px var(--tier-soft)}
.amt-preset.on small{color:var(--tier)}
.amt-preset.on .crest{transform:scale(1.06)}

/* The card answers too, so the choice is felt and not just marked. Border and a
   soft outer glow only - no background wash, which would fight the inputs sitting
   on it and drag every label's contrast down with it. */
.donate-form[data-tier]{border-color:var(--tier-soft);
  box-shadow:0 0 0 1px var(--tier-soft),0 18px 60px -28px var(--tier-glow)}

/* The submit KEEPS its cyan fill. Cyan is the site's one action colour, and a
   violet or white fill here either fails contrast against the dark label or
   turns the primary action into a decoration. It takes the tier as a glow and a
   ring instead, which reads as the tier without touching legibility. */
.donate-form[data-tier] .df-submit{box-shadow:0 0 0 2px var(--tier-soft),0 10px 34px -12px var(--tier-glow)}

@media (prefers-reduced-motion:no-preference){
  .amt-preset .crest,.amt-preset,.donate-form,.df-submit{
    transition:box-shadow .22s var(--ease-out),border-color .22s var(--ease-out),transform .18s var(--ease-out)}
}

/* --- /admin: account row actions -------------------------------------------- */
/* Folded per row so the table stays a table. The panel already uses <details>
   for the nav menu and the trust badges, so this is its own idiom, not a new one. */
.adm-acts{width:1%;white-space:nowrap}
.adm-rowact > summary{cursor:pointer;list-style:none;padding:2px 10px;border-radius:var(--r-ctl);
  border:1px solid var(--border);color:var(--text-low);display:inline-block;line-height:1.4}
.adm-rowact > summary::-webkit-details-marker{display:none}
.adm-rowact > summary:hover{color:var(--text-hi);border-color:var(--border-soft)}
.adm-rowact[open] > summary{color:var(--text-hi);border-color:var(--link)}
.adm-rowact-body{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-3) 0 var(--sp-2);
  min-width:0}
.adm-rowact-body .adm-row{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap}
.adm-newacct{margin:0 0 var(--sp-4)}
.adm-newacct > summary{cursor:pointer;color:var(--link);padding:var(--sp-2) 0}
/* NOT .adm-ok: that is already the approve BUTTON (style.css:1607), and a later
   rule with the same name would have silently restyled every approve control on
   the orders page. */
.adm-saved{background:rgba(61,220,132,.10);border:1px solid rgba(61,220,132,.35);color:var(--text-mid);
  border-radius:var(--r-ctl);padding:10px 14px;margin-bottom:var(--sp-3);font-size:var(--fs-body)}

/* --- /terms ----------------------------------------------------------------- */
/* The legal position is the one line on this page that a buyer, a payment
   processor or a regulator must not scroll past. It is already first and already
   railed; the icon is so it reads as a statement of limit rather than another
   clause. Same .ic family and the same baseline correction as everywhere else. */
.tos-alert h2{display:flex;align-items:center;gap:9px}
.tos-alert h2 .ic{width:19px;height:19px;flex:none;color:var(--cyan-500);transform:translateY(0.25em)}

/* --- Early Access card ---------------------------------------------------
   Sits in the same .plan-row as the ordinary plan cards and inherits their box,
   so only what differs is declared here: a cyan identity matching the tester
   emblem, and the seat line, which is the whole point of a capped product. */
.ea-card{border-color:rgba(95,208,255,.45);
  background:linear-gradient(180deg,rgba(95,208,255,.07),rgba(95,208,255,.02))}
.ea-card .best-badge{background:#5FD0FF;color:#0B1220}
.ea-seats{margin:.35rem 0 0;font-size:.95rem;color:#5FD0FF}
.ea-seats b{font-size:1.05rem}
.ea-seats.sold-out{color:var(--text-low)}
.ea-what{margin:.55rem 0 .2rem;font-size:.88rem;line-height:1.7;color:var(--text-low)}
/* The line that explains why a buyable card sits under a "sales are closed"
   banner. Quiet, but it must not be mistaken for a disabled state. */
.ea-open-note{margin-top:.5rem;font-size:.82rem}
.ea-list{margin:.9rem 0 1.2rem;padding:0 1.1rem 0 0;line-height:2}
.ea-list li{font-size:.95rem}
.ea-list li.hint{margin-top:.4rem;font-size:.85rem;color:var(--text-low);list-style:none;
  padding-inline-start:0}


/* The alternate door sits under its route's primary address, deliberately
   quieter: it is the answer to "the main one doesn't work on my ISP", not a
   second equal choice. */
.addr-pill.alt{margin-top:6px;opacity:.82}
.addr-pill.alt .addr{font-size:.92em}
.addr-alt-label{font-size:.78em;color:var(--muted,#8aa);margin-inline-end:8px;white-space:nowrap}

/* ── Guides (/post) ───────────────────────────────────────────────────────────
   Redesign-preserve: this is the existing system extended, not a new one. Same
   reading column as .tos (776px, proven on this site), same type ramp, same one
   accent. Deliberately quiet: the reader arrived from a search with a broken
   game, often on mobile and a slow line, so legibility and weight beat flourish.
   No motion beyond link hover, nothing that repaints on scroll. */
.art{max-width:776px;margin:0 auto;padding:52px 20px 80px}

.art-head h1{font-size:1.9rem;line-height:1.6;color:var(--text-hi)}
/* The one small-caps label on the page. Persian has no real small-caps, so this
   is separated by colour and size rather than by letter-spacing, which would
   break Persian letter joining. */
.art-kicker{display:inline-block;margin-bottom:10px;font-size:.78rem;color:var(--cyan-500)}
.art-lead{margin-top:12px;font-size:1rem;line-height:2;color:var(--text-mid)}
.art-meta{margin-top:10px;font-size:.82rem;color:var(--text-low)}
.art-dot{margin:0 7px;color:var(--navy-600)}

.art-cover{width:100%;height:auto;margin-top:26px;border-radius:var(--r-card);
  border:1px solid var(--navy-700)}

/* Body prose. Persian needs more leading than Latin at the same size; 2.05 is
   what stops long RTL paragraphs from feeling like a wall. */
.art-body{margin-top:30px;font-size:1rem;line-height:2.05;color:var(--text-mid)}
.art-body h2{margin:38px 0 12px;font-size:1.2rem;line-height:1.7;color:var(--text-hi)}
.art-body h3{margin:28px 0 10px;font-size:1.02rem;line-height:1.7;color:var(--text-hi)}
.art-body p{margin:0 0 16px}
.art-body ul,.art-body ol{margin:0 0 18px;padding-inline-start:0;list-style:none}
.art-body li{position:relative;margin-bottom:9px;padding-inline-start:20px}
/* Marker sits on the start edge, so it flips with direction automatically. */
.art-body ul>li::before{content:"";position:absolute;inset-inline-start:2px;top:.85em;
  width:5px;height:5px;border-radius:50%;background:var(--cyan-700)}
.art-body ol{counter-reset:art}
.art-body ol>li{counter-increment:art}
.art-body ol>li::before{content:counter(art);position:absolute;inset-inline-start:0;
  top:0;font-size:.82rem;color:var(--cyan-500)}
.art-body a{color:var(--link);text-decoration:underline;text-underline-offset:3px}
.art-body a:hover{color:var(--link-hover)}
.art-body strong{color:var(--text-hi);font-weight:600}
.art-body code{padding:2px 6px;border-radius:var(--r-ctl);background:var(--navy-800);
  border:1px solid var(--navy-700);font-family:"JetBrains Mono",monospace;
  font-size:.86em;color:var(--cyan-300);direction:ltr;display:inline-block}
.art-body blockquote{margin:0 0 18px;padding:12px 16px;background:var(--navy-800);
  border:1px solid var(--navy-700);border-inline-start:3px solid var(--cyan-500);
  border-radius:var(--r-card);color:var(--text-mid)}
.art-body img{max-width:100%;height:auto;border-radius:var(--r-card);
  border:1px solid var(--navy-700)}

/* The single offer, after the answer. Same construction as .tos-alert so it reads
   as part of the site rather than as an ad dropped into the page. */
.art-cta{margin-top:44px;padding:22px;background:var(--navy-800);
  border:1px solid var(--navy-600);border-inline-start:3px solid var(--cyan-500);
  border-radius:var(--r-card)}
.art-cta h2{font-size:1.05rem;color:var(--text-hi)}
.art-cta p{margin:10px 0 18px;font-size:.94rem;line-height:2;color:var(--text-mid)}

.art-back{margin-top:28px;font-size:.9rem}
.art-back a{color:var(--link)}
.art-empty{margin-top:30px;color:var(--text-low)}

/* Index list. Rows, not cards: at this count cards would be decoration, and the
   hairline already groups them. */
.art-list{margin-top:30px;padding:0;list-style:none}
.art-list li{border-top:1px solid var(--navy-700)}
.art-list li:first-child{border-top:0}
.art-list a{display:block;padding:22px 0;text-decoration:none}
.art-list h2{margin:6px 0;font-size:1.12rem;line-height:1.7;color:var(--text-hi)}
.art-list p{margin:0 0 8px;font-size:.92rem;line-height:1.95;color:var(--text-mid)}
.art-list a:hover h2{color:var(--cyan-300)}
.art-list .art-meta{margin:0}

@media (max-width:560px){
  .art{padding:34px 16px 60px}
  .art-head h1{font-size:1.5rem}
  .art-body,.art-lead{font-size:.96rem}
}

/* ── Guide editor (/admin/post) ───────────────────────────────────────────────
   The first multi-line editing surface in this panel. Same tokens as every other
   admin form; the only new need is a body field big enough to write in and a
   preview that renders with the real article styles. */
.adm-postform{display:grid;gap:var(--sp-4);max-width:820px}
.adm-postform label{display:grid;gap:var(--sp-2);font-size:var(--fs-label);
  text-transform:uppercase;letter-spacing:.04em;color:var(--text-low)}
.adm-postform input,.adm-postform textarea,.adm-postform select{
  padding:9px 11px;background:var(--surface-alt);color:var(--text-hi);
  border:1px solid var(--border);border-radius:var(--r-ctl);
  font:inherit;font-size:.9rem;text-transform:none;letter-spacing:normal}
.adm-postform textarea{resize:vertical;line-height:1.9}
/* Monospace for the body: the markup is easier to see when # and * line up. */
.adm-postbody{font-family:"JetBrains Mono",monospace;font-size:.85rem}
.adm-postform input:focus,.adm-postform textarea:focus,.adm-postform select:focus{
  outline:2px solid var(--cyan-700);outline-offset:1px;border-color:var(--cyan-700)}
.adm-postform .adm-dim{text-transform:none;letter-spacing:normal;font-size:.78rem}
.adm-postform button{justify-self:start;padding:9px 20px}

/* Preview: the article body styles on the admin surface, so what is checked here
   is what ships. Bordered to make clear it is a preview, not the page. */
.adm-preview{margin-top:var(--sp-3);padding:var(--sp-5);background:var(--surface-alt);
  border:1px solid var(--border);border-radius:var(--r-card);max-width:820px}
.adm-preview:empty::after{content:"Nothing written yet.";color:var(--text-low);font-size:.85rem}

.adm-rowacts{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap}
.adm-rowacts form{display:inline}
.adm-h2{font-size:var(--fs-h2);color:var(--text-hi);margin-bottom:var(--sp-2)}

/* ── Admin classes that were used but never defined ──────────────────────────
   Each of these was referenced in a template with no matching rule, so it
   rendered as an unstyled browser default on the dark panel. */

/* The config-drift row on the dashboard: a node no longer matches its intended
   configuration. It is an alert sharing a table with ordinary rows, so it needs
   to break the rhythm rather than read as one more line of data. */
.adm-drift td{background:rgba(255,176,32,.07);border-inline-start:3px solid var(--warn)}
.adm-drift ul{margin:6px 0 0;padding-inline-start:18px}
.adm-drift li{color:var(--text-mid);font-size:.82rem;line-height:1.7}

/* A checkbox and its label on one line, with the box aligned to the first line
   of text rather than floating against a wrapped label. */
.adm-check{display:flex;align-items:flex-start;gap:var(--sp-2);
  font-size:var(--fs-body);color:var(--text-mid);cursor:pointer}
.adm-check input{margin-top:.25em;flex:none}

/* ── Admin tables: sticky headers, and cards on a phone ──────────────────────
   Two problems this fixes. On desktop, 100 rows is a real page size and the
   column headers scroll away, so halfway down a table nobody knows which column
   is which. On a phone, .adm-tablewrap's horizontal scroll was described in its
   own comment as "emergency use" — you pan sideways to read one row. */

/* Sticky head. The wrapper needs a bounded height for stickiness to have
   anything to stick within, so it gets a viewport-relative max instead of
   growing forever. */
.adm-tablewrap{max-height:calc(100vh - 220px)}
.adm-table thead th{position:sticky;top:0;z-index:1;
  background:var(--navy-800);
  /* A real border would scroll away with the cell box, so the line under the
     head is drawn as a shadow that stays put. */
  box-shadow:inset 0 -1px 0 var(--border)}

/* A table small enough not to scroll should not be capped or sticky at all. */
.adm-tablewrap.short{max-height:none}
.adm-tablewrap.short .adm-table thead th{position:static;box-shadow:none}

@media(max-width:720px){
  /* Card rows. Each cell carries its own label from data-label, so the header
     row can disappear without the values becoming anonymous. */
  .adm-cardrows .adm-tablewrap{max-height:none;overflow:visible}
  .adm-cardrows table,.adm-cardrows tbody,.adm-cardrows tr,.adm-cardrows td{display:block;width:auto}
  .adm-cardrows thead{display:none}
  .adm-cardrows tr{margin-bottom:var(--sp-3);padding:var(--sp-3);
    background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card)}
  .adm-cardrows td{display:flex;gap:var(--sp-3);align-items:baseline;
    padding:5px 0;border:0}
  .adm-cardrows td::before{content:attr(data-label);flex:none;min-width:8.5em;
    font-size:var(--fs-label);text-transform:uppercase;letter-spacing:.04em;
    color:var(--text-low)}
  /* A cell with no label is an actions cell: let it use the full width. */
  .adm-cardrows td:not([data-label]){padding-top:var(--sp-3)}
  .adm-cardrows td:not([data-label])::before{content:none}
  /* Touch targets. A 32px button is fine under a mouse and mean under a thumb;
     the previous stylesheet claimed this happened and no rule ever did it. */
  .adm-cardrows .adm-ok,.adm-cardrows .adm-no,.adm-cardrows .adm-btn{min-height:44px}
}

/* Copy buttons (admin.js). Quiet until hovered, so a table of codes does not
   turn into a wall of buttons. */
.adm-copy{margin-inline-start:var(--sp-2);padding:2px 7px;
  background:transparent;color:var(--text-low);
  border:1px solid var(--border);border-radius:var(--r-ctl);
  font-size:.7rem;font-family:inherit;cursor:pointer;
  transition:color .15s var(--ease-out),border-color .15s var(--ease-out)}
.adm-copy:hover{color:var(--link);border-color:var(--cyan-700)}
.adm-copy.is-done{color:var(--ok);border-color:var(--ok)}
.adm-copy:focus-visible{outline:2px solid var(--cyan-700);outline-offset:1px}
/* "Clear all" sits apart from the chips: it undoes them rather than being one. */
.adm-clear{margin-inline-start:auto;color:var(--text-low);text-decoration:underline;
  text-underline-offset:3px}
.adm-clear:hover{color:var(--link)}

/* ── Global search ───────────────────────────────────────────────────────────
   "Who is this person" is the most common reason to open the panel, so the box
   lives in the sidebar above the sections rather than on a page you navigate to
   first. */
.adm-navsearch{padding:0 0 var(--sp-3)}
.adm-navsearch input{width:100%;padding:7px 10px;background:var(--navy-900);
  color:var(--text-hi);border:1px solid var(--border);border-radius:var(--r-ctl);
  font:inherit;font-size:.82rem}
.adm-navsearch input::placeholder{color:var(--text-low)}
.adm-navsearch input:focus{outline:2px solid var(--cyan-700);outline-offset:1px}

.adm-search-big input{flex:1;min-width:min(420px,100%);font-size:.95rem;padding:10px 12px}

/* Result rows, not cards: at a glance you are scanning for one name. */
.adm-hits{margin-top:var(--sp-4);padding:0;list-style:none}
.adm-hits li{border-top:1px solid var(--border)}
.adm-hits li:first-child{border-top:0}
.adm-hits a{display:flex;gap:var(--sp-3);align-items:baseline;
  padding:11px var(--sp-2);text-decoration:none;border-radius:var(--r-ctl)}
.adm-hits a:hover{background:var(--surface)}
.adm-hit-kind{flex:none;min-width:4.5em;text-align:center}
.adm-hit-title{color:var(--text-hi);font-size:.95rem}
.adm-hit-sub{margin-inline-start:auto;font-size:.8rem}

/* Type-ahead panel, anchored under whichever box has focus. */
.adm-typeahead{position:absolute;z-index:40;min-width:280px;max-width:min(460px,90vw);
  max-height:min(60vh,420px);overflow-y:auto;
  background:var(--surface);border:1px solid var(--border-soft);
  border-radius:var(--r-card);box-shadow:0 18px 44px rgba(0,0,0,.45)}
.adm-typeahead ul{margin:0;padding:4px;list-style:none}
.adm-typeahead a{display:flex;gap:var(--sp-2);align-items:baseline;
  padding:8px 10px;border-radius:var(--r-ctl);text-decoration:none;font-size:.85rem}
.adm-typeahead a:hover,.adm-typeahead li.on a{background:var(--navy-700)}
.adm-typeahead .adm-hit-title{font-size:.85rem}
.adm-typeahead .adm-hit-sub{margin-inline-start:auto;font-size:.75rem}
.adm-typeahead .adm-none{padding:10px;color:var(--text-low);font-size:.82rem}

@media(max-width:820px){
  /* The sidebar becomes a horizontal strip on a phone; the search box would be
     squeezed into it, so it gets its own full-width row instead. */
  .adm-navsearch{order:99;flex:1 0 100%;padding:var(--sp-2) 0 0}
}
/* The view switcher reads as a switch, not as one more filter row. */
.adm-viewswitch{margin-bottom:var(--sp-4);padding-bottom:var(--sp-3);
  border-bottom:1px solid var(--border)}

/* ==== Plus UI: admin funnel ==================================================
   The tier funnel panel (tierfunnel partial): compact on /admin, --wide on
   /admin/tiers. One surface, hairline-separated stat cells like .adm-cards
   (boxes would be chrome competing with the numbers), a two-segment share bar
   whose widths admin.js sets as --w from data-w (the CSP drops a style
   attribute), and the Go-drawn sparkline coloured here because its markup
   carries presentation attributes only. Gold appears on exactly one thing:
   the Plus segment (and the Plus mark beside the title). No motion. */
.adm-funnel h2 .adm-funnel__mark{width:18px;height:18px;flex:none}
.adm-funnel__body{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-card);padding:16px 18px}
.adm-funnel__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
.adm-funnel__stat{padding:2px 0;padding-inline-start:14px;border-inline-start:1px solid var(--border);min-width:0}
/* Share bar + sparkline on one row: the bar grows, the sparkline keeps its
   natural 300px so the svg's 300x40 viewBox draws 1:1 (a 100%-wide svg would
   either letterbox the line in the middle of the row or, stretched, thicken
   the stroke and squash the dot). On a phone they stack. */
.adm-funnel__foot{display:flex;flex-wrap:wrap;align-items:center;gap:16px 32px;margin-top:18px}
.adm-funnel__split{flex:1 1 280px;min-width:0}
.adm-funnel__share{height:8px;display:flex;border-radius:999px;overflow:hidden;background:var(--navy-700)}
.adm-funnel__share i{display:block;height:100%;width:var(--w,0%)}
.adm-funnel__share .base{background:var(--cyan-500)}
.adm-funnel__share .plus{background:var(--gold-500)}
.adm-funnel__legend{display:flex;gap:16px;margin-top:8px;font-size:.72rem;color:var(--text-low)}
.adm-funnel__legend span::before{content:"";display:inline-block;width:8px;height:8px;
  border-radius:2px;margin-inline-end:6px;vertical-align:middle}
.adm-funnel__legend .base::before{background:var(--cyan-500)}
.adm-funnel__legend .plus::before{background:var(--gold-500)}
.adm-funnel__spark{flex:0 1 300px;min-width:0;margin:0}
.adm-spark{display:block;width:100%;max-width:300px;height:40px;stroke:var(--cyan-500)}
.adm-spark circle{fill:var(--cyan-500);stroke:none}
.adm-funnel__spark figcaption .mono{display:block;color:var(--text-mid)}
/* The daily table (--wide only): two 15-row halves side by side, denser rows
   than .adm-table.small, a day that sold nothing dimmed so the eye lands on
   the days that did. */
.adm-funnel--wide .adm-funnel__table{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  gap:12px;margin-top:18px}
.adm-funnel__table .adm-table.small td{padding:5px 14px}
.adm-funnel__quiet td{color:var(--text-low)}
@media(max-width:720px){
  .adm-funnel__body{padding:14px}
  .adm-funnel__grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .adm-funnel__stat{padding-inline-start:10px}
  .adm-funnel__stat .adm-v.money{font-size:1.1rem}
  .adm-funnel__foot{gap:14px}
  .adm-funnel__split,.adm-funnel__spark{flex:1 1 100%}
  .adm-funnel--wide .adm-funnel__table{grid-template-columns:minmax(0,1fr)}
}

/* ==== Plus UI: foundation ====================================================
   Shared by the store panels, the account tier card/upsell/renewal, /list and
   the leaderboard crest (pairs 1-3 of the Plus redesign). Everything here is a
   class, a custom property set by CSSOM, or data-* read through attr(): the CSP
   is style-src 'self' and a style attribute is dropped silently. Motion is
   transform/opacity only and is switched off twice: under prefers-reduced-motion
   below AND in app.js (CP.reduce), so nothing is ever half-animated. */

/* --- tier mark: base = the site glyph (currentColor cyan), Plus = the gold-plus
   composite (svg small/medium, webp with the baked glow for the hero) -------- */
.tier-mark{display:inline-block;vertical-align:middle;flex:none;object-fit:contain}
.tier-mark--s{width:16px;height:16px}
.tier-mark--m{width:48px;height:48px}
/* The hero webp is 155x160; height leads and the width follows the aspect, the
   same rule .mark uses for the portrait brand mark. Consumers size their own
   hero (128 store / 96 account card / 72 upsell); this is only the default. */
.tier-mark--hero{height:128px;width:auto}
.tier-mark--base.tier-mark--hero{width:64px;height:64px}
span.tier-mark{color:var(--cyan-500);line-height:0}
/* the glyph svg inside the base span: fill the span, not .mark's fixed 48px */
.tier-mark .mark{width:100%;height:100%;display:block}
/* The halo is what breathes/blooms: a separate layer so the raster never gets a
   filter animation (that was the +11% CPU lesson at .hub-art). Rests invisible;
   halo-bloom (fill both) leaves it at .35, breathe-once returns it to 0. */
.tier-mark__wrap{position:relative;display:inline-block;line-height:0;flex:none}
.tier-mark__wrap .tier-mark{position:relative;z-index:1}
.tier-mark__halo{position:absolute;inset:-16%;border-radius:50%;pointer-events:none;
  background:radial-gradient(circle at 50% 46%,rgba(245,179,53,.46) 0,rgba(245,179,53,.22) 34%,rgba(34,201,242,.12) 58%,transparent 72%);
  opacity:0;transform:scale(1);will-change:transform,opacity}

/* --- gold CTA: .btn-solid's anatomy in the Plus gold. ONE per page. -------- */
.btn-gold{display:inline-block;position:relative;overflow:hidden;
  background:var(--gold-500);color:var(--gold-ink);font-weight:700;border:0;
  border-radius:12px;padding:12px 30px;font-size:1rem;font-family:Vazirmatn;
  cursor:pointer;text-decoration:none;
  transition:background .18s ease-out,box-shadow .18s ease-out,transform .12s ease-out}
.btn-gold:hover{background:var(--gold-300);box-shadow:var(--glow-gold-lg);transform:translateY(-1px)}
.btn-gold:active{transform:scale(.98)}
.btn-gold:focus-visible{outline:2px solid var(--gold-300);outline-offset:3px}
.btn-gold::after{content:"";position:absolute;top:0;bottom:0;width:45%;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);
  transform:translateX(-160%) skewX(-18deg);pointer-events:none}
.btn-gold:hover::after{transition:transform .22s ease-out;transform:translateX(260%) skewX(-18deg)}

/* --- duration control: pills (real links, so no-JS reloads with ?plan=) over
   the selected quote's figure and ONE CTA. app.js morphs it in place. -------- */
.durctl{display:flex;flex-direction:column;align-items:center;gap:14px;width:100%}
.durctl__pills{display:flex;gap:4px;width:100%;padding:4px;
  border:1px solid var(--navy-600);border-radius:var(--r-pill);background:var(--navy-900)}
.durctl__pill{flex:1 1 0;display:flex;align-items:center;justify-content:center;gap:4px;
  min-height:40px;padding:8px 8px;border-radius:var(--r-pill);
  color:var(--text-mid);font-size:.88rem;line-height:1.3;white-space:nowrap;cursor:pointer;
  transition:background .18s ease-out,color .18s ease-out,box-shadow .18s ease-out,transform .12s ease-out}
.durctl__pill .mono{font-size:.95rem}
.durctl__pill .plan-year,.durctl__pill .plan-inf{font-size:.92rem}
.durctl__pill:hover{color:var(--text-hi);background:var(--navy-700)}
.durctl__pill:active{transform:scale(.98)}
.durctl__pill:focus-visible{outline:2px solid var(--cyan-500);outline-offset:-2px;border-radius:var(--r-pill)}
.durctl__pill.on{background:var(--cyan-500);color:#04222e;font-weight:700;box-shadow:var(--glow-sm)}
.durctl__pill.on .mono,.durctl__pill.on .plan-year,.durctl__pill.on .plan-inf{color:inherit}
/* In the Plus panel the selected pill is a gold OUTLINE, not a fill: a filled
   gold pill would be a second gold button beside the one .btn-gold. */
.tier-panel--plus .durctl__pill.on{background:transparent;color:var(--gold-300);
  box-shadow:inset 0 0 0 1px var(--gold-500),var(--glow-gold-sm)}
.durctl__figure{text-align:center;min-height:5.2em}
.durctl__price{display:flex;align-items:baseline;justify-content:center;gap:6px;
  font-family:JBMono,monospace;font-weight:700;font-size:1.65rem;color:var(--text-hi);
  letter-spacing:-.01em;font-variant-numeric:tabular-nums}
.durctl__price .cur{font-family:Vazirmatn,sans-serif;font-weight:400;font-size:.78rem;color:var(--text-low);letter-spacing:0}
.durctl__perday{font-size:.78rem;color:var(--text-low);margin-top:2px;min-height:1.6em}
.durctl__save{margin-top:6px;font-size:.78rem;line-height:1.75;color:var(--text-low);min-height:1.75em}
.durctl__save .mono{font-family:JBMono,monospace;font-weight:700;color:var(--text-mid)}
.durctl__save.big{display:inline-block;padding:6px 12px;border-radius:10px;color:var(--cyan-300);
  background:rgba(34,201,242,.10);border:1px solid rgba(34,201,242,.30)}
.durctl__save.big .mono{color:var(--text-hi)}
/* the 120ms text swap: opacity only, never layout */
.durctl__perday,.durctl__save{transition:opacity .12s ease-out}
.durctl__figure.is-swapping .durctl__perday,.durctl__figure.is-swapping .durctl__save{opacity:0}
.durctl__cta{width:100%;text-align:center;padding:11px 0}
.durctl .btn-closed{margin-top:0}

/* --- odometer: one column per character, the strip rolls by --d ems -------- */
/* .odo itself does NOT clip: the SSR text and the comma separators keep their
   descenders; only the digit columns clip, and JBMono digits have none. */
.odo{display:inline-flex;align-items:flex-start;height:1em;line-height:1;
  font-variant-numeric:tabular-nums;vertical-align:baseline}
.odo__d{display:inline-block;position:relative;width:1ch;height:1em;overflow:hidden}
.odo__strip{display:block;transform:translateY(calc(var(--d,0) * -1em));
  transition:transform .45s var(--ease-out);will-change:transform}
.odo__strip span{display:block;height:1em;line-height:1;text-align:center}
.odo__sep{display:inline-block;height:1em;line-height:1}

/* --- per-tier server grid: logo tiles with a CSS-only player-count tip ----- */
.tier-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:10px;
  list-style:none;padding:0;margin:0;width:100%}
.tier-grid__label{grid-column:1/-1;font-size:.72rem;font-weight:700;color:var(--text-low);
  letter-spacing:.02em;margin-top:6px}
.tier-grid__label:first-child{margin-top:0}
/* «همهٔ پایه +»: the Plus grid opens with the base tier folded into one row */
.tier-grid__all{grid-column:1/-1;display:flex;align-items:center;gap:8px;
  font-size:.86rem;font-weight:700;color:var(--text-hi);
  padding:8px 12px;border-radius:10px;background:var(--navy-900);border:1px solid var(--navy-700)}
.tier-tile{position:relative;display:flex;flex-direction:column;align-items:center;gap:5px;
  padding:8px 4px 6px;border-radius:10px;background:var(--navy-900);border:1px solid var(--navy-700);
  cursor:default;transition:transform .18s ease-out,border-color .18s ease-out,box-shadow .18s ease-out}
.tier-tile img{width:44px;height:44px;border-radius:9px;object-fit:contain;display:block}
/* overflow-wrap stays normal: under the 2-line clamp `anywhere` split Persian
   names mid-word («اکستریم‌کرف / ت»), and a lone letter on line two reads as a
   typo. A name too long for the tile ellipsises on its clamped line instead. */
.tier-tile span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;max-width:100%;
  font-size:.74rem;line-height:1.3;color:var(--text-mid);overflow:hidden;text-align:center;
  overflow-wrap:normal}
.tier-tile:hover,.tier-tile:focus-visible{transform:translateY(-2px);border-color:var(--cyan-700);box-shadow:var(--glow-sm)}
.tier-tile:focus-visible{outline:2px solid var(--cyan-500);outline-offset:2px}
.tier-grid--compact{grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:8px}
.tier-grid--compact .tier-tile{padding:6px 3px 4px;gap:3px}
.tier-grid--compact .tier-tile img{width:40px;height:40px}
.tier-grid--compact .tier-tile span{font-size:.68rem}
/* tooltip: the .crest::after pattern, above the tile, RTL, never clipped */
.tier-tile[data-tip]::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 7px);right:50%;
  max-width:min(60vw,220px);overflow-wrap:anywhere;
  transform:translateX(50%) translateY(3px);white-space:nowrap;direction:rtl;
  background:var(--navy-700);color:var(--text-hi);border:1px solid var(--navy-600);
  border-radius:7px;padding:4px 10px;font-family:Vazirmatn,sans-serif;font-size:.74rem;
  font-weight:400;line-height:1.6;box-shadow:0 6px 18px rgba(0,0,0,.5);
  opacity:0;pointer-events:none;transition:opacity .15s ease-out,transform .15s ease-out;z-index:60}
.tier-tile[data-tip]:hover::after,.tier-tile[data-tip]:focus::after,
.tier-tile[data-tip]:focus-visible::after,.tier-tile[data-tip]:active::after{opacity:1;transform:translateX(50%) translateY(0)}
/* a tile with a tip shows it on tap too: the grid must not clip it */
.tier-grid,.tier-tile{overflow:visible}

@media(max-width:560px){
  .tier-grid{grid-template-columns:repeat(auto-fill,minmax(56px,1fr))}
  .durctl__pill{padding:8px 6px;font-size:.82rem}
  .durctl__pill .mono{font-size:.88rem}
}

/* --- motion: keyframes and the one-shot classes app.js toggles ------------- */
@media (prefers-reduced-motion:no-preference){
  @keyframes mark-in{from{opacity:0;transform:scale(.8)}to{opacity:1;transform:none}}
  @keyframes halo-bloom{0%{opacity:0;transform:scale(.85)}45%{opacity:.75;transform:scale(1.12)}100%{opacity:.35;transform:scale(1)}}
  /* the halo, once, 1.4s: a single breath of light that fades back out */
  @keyframes breathe-once{0%,100%{transform:scale(1);opacity:0}50%{transform:scale(1.035);opacity:.55}}
  @keyframes gold-sweep{from{transform:translateX(-160%) skewX(-18deg)}to{transform:translateX(260%) skewX(-18deg)}}
  @keyframes skel-sweep{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
  /* tiles cascade in: CP.stagger sets each tile's transitionDelay (30ms steps)
     and clears it on transitionend so hover never inherits the delay */
  html.js .tier-tile{opacity:0;transform:translateY(6px) scale(.96)}
  html.js .tier-tile.in{opacity:1;transform:none;transition:opacity .3s var(--ease-out),transform .3s var(--ease-out),
    border-color .18s ease-out,box-shadow .18s ease-out}
  html.js .tier-tile.in:hover,html.js .tier-tile.in:focus-visible{transform:translateY(-2px)}
  .tier-mark--glow.mark-in,.tier-mark__wrap.mark-in .tier-mark--glow{animation:mark-in .5s var(--ease-out) both}
  .tier-mark--glow.mark-in ~ .tier-mark__halo,.tier-mark__wrap.mark-in .tier-mark__halo,
  .tier-mark__wrap.bloom .tier-mark__halo{animation:halo-bloom .9s var(--ease-out) both}
  .tier-mark__wrap.breathe .tier-mark__halo{animation:breathe-once 1.4s ease-in-out 1}
  .btn-gold.shimmer::after{animation:gold-sweep .7s ease-out 1}
  .skel::after{animation:skel-sweep 1.2s linear infinite}
}

/* --- reduced motion: every rule above collapses to its final state --------- */
@media (prefers-reduced-motion:reduce){
  html.js .tier-tile{opacity:1!important;transform:none!important;transition:none!important}
  .tier-mark--glow,.tier-mark__halo,.btn-gold::after,.tp-lock,.skel::after{animation:none!important}
  .odo__strip,.gsw-ind,.gsw-gh::after{transition:none!important}
  .durctl__perday,.durctl__save,.tier-tile[data-tip]::after,.durctl__pill,.btn-gold{transition:none!important}
  /* the hover sweep (.btn-gold:hover::after) is a transition, not a keyframe:
     the animation:none above does not reach it. The band is pinned at its
     resting place off the start edge (transform:none would park the 45%-wide
     gradient inside the button as a static stripe). */
  .btn-gold::after,.btn-gold:hover::after{transition:none!important;transform:translateX(-160%) skewX(-18deg)!important}
  .skel{background:none;color:inherit} .skel::after{display:none}
}

/* ==== Plus UI: store =========================================================
   /store is two tier panels, Plus the hero. Plus is FIRST in the DOM, which
   under dir=rtl is the START (right) column and, once the grid collapses, the
   first card, so the tier the page sells leads on every screen. The gold on
   this page is the Plus panel's edge and wash plus the one .btn-gold its
   duration control renders; the base panel stays in the site's cyan. Classes
   only (CSP style-src 'self'). The reveal is the generic .rv with Plus leading
   and base .08s behind; the tile cascade and the halo's single breath are the
   foundation's generic hooks (app.js watches every .tier-grid and every
   .tier-panel--plus), so nothing here is wired twice. */
main.storep{padding-top:48px;padding-bottom:80px}
main.storep h1{text-align:center;font-size:clamp(1.7rem,5vw,2.4rem);margin-bottom:12px}
/* the steps keep the old 640 column now that <main> is the full 1060 */
.store-steps{max-width:640px;margin-inline:auto}

/* align-items:start, not stretch: Plus lists ~4x the games, and a stretched
   base card would carry that difference as a void under its five tiles */
.tier-panels{display:grid;grid-template-columns:1.15fr 1fr;gap:20px;
  max-width:1010px;margin:0 auto 34px;align-items:start}
/* A <section>, so the global section{padding:56px 0} and section h2 land here;
   every one of those is restated below. */
.tier-panel{position:relative;display:flex;flex-direction:column;gap:18px;
  padding:26px 24px 24px;margin:0;min-width:0;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:var(--r-card);
  transition:transform .18s ease-out,box-shadow .18s ease-out,border-color .18s ease-out}
.tier-panel:hover{transform:translateY(-2px)}
/* is-focus = the ?tier= the request named: the panel's own colour, one step
   up, never a second button */
.tier-panel--base:hover,.tier-panel--base.is-focus{border-color:var(--cyan-700);box-shadow:var(--glow-sm)}
.tier-panel--plus{border-color:rgba(245,179,53,.45);box-shadow:var(--glow-gold-sm);
  background:linear-gradient(180deg,rgba(245,179,53,.06),transparent 40%),var(--navy-800)}
.tier-panel--plus:hover,.tier-panel--plus.is-focus{box-shadow:var(--glow-gold-md)}

/* head: mark over name over the live totals, centred like the control below.
   The art stage is one fixed height on both panels (the Plus hero is 128px,
   the base glyph 64px) so the two names, totals and pill rows sit on the same
   lines when the panels stand side by side. */
.tier-panel__head{display:flex;flex-direction:column;align-items:center;gap:2px;text-align:center}
.tier-panel__art{display:flex;align-items:center;justify-content:center;height:128px;margin-bottom:8px}
.tier-panel__art .tier-mark--hero{height:128px}
.tier-panel--base .tier-panel__art .tier-mark{width:64px;height:64px}
.tier-panel__name{margin:0;font-size:1.55rem;font-weight:800;line-height:1.25;color:var(--text-hi);text-align:center}
.tier-panel__total{margin:0;font-size:.86rem;color:var(--text-mid)}
/* The base panel's one-line explanation of the weekly rotation. Quieter than
   the totals above it: it is context for the count, not a second headline. */
.tier-panel__rota{margin:2px 0 0;font-size:.76rem;line-height:1.5;color:var(--text-low)}
/* the 30-day anchor the control's totals are measured against */
.tier-panel__price30{margin:-4px 0 0;text-align:center;font-size:.88rem;color:var(--text-low)}
.tier-panel__price30 .mono{font-size:1rem;font-weight:700;color:var(--text-hi)}
.tier-panel__grid{margin-top:4px;padding-top:18px;border-top:1px solid var(--navy-700)}

/* Under html.js the generic reveal (html.js .rv.in, transform:none + a
   transition shorthand) outranks .tier-panel:hover, so the lift and the
   shadow transition are restated at that specificity, the .pod pattern.
   The base panel follows Plus by .08s; hover never waits on that delay. */
html.js .tier-panel.rv.in{opacity:1;transform:none;
  transition:opacity .36s var(--ease-out),transform .22s var(--ease-out),
  box-shadow .18s ease-out,border-color .18s ease-out}
html.js .tier-panel--base.rv.in{transition-delay:.08s}
html.js .tier-panel.rv.in:hover{transform:translateY(-2px);transition-delay:0s}

/* one column under 1000 (Plus first by DOM order); the phone tightening of
   .tier-grid (56px tiles) and .durctl__pill is the foundation's own ≤560 rule */
@media (max-width:1000px){
  .tier-panels{grid-template-columns:1fr;max-width:560px}
}
@media (max-width:560px){
  .tier-panel{padding:18px;gap:16px}
  .tier-panel__art{height:104px;margin-bottom:6px}
  .tier-panel__art .tier-mark--hero{height:104px}
  .tier-panel__name{font-size:1.35rem}
}
@media (prefers-reduced-motion:reduce){
  .tier-panel,html.js .tier-panel.rv.in{transition:none!important}
  .tier-panel:hover,html.js .tier-panel.rv.in:hover{transform:none!important}
}

/* ==== Plus UI: account =======================================================
   The tier card, the Plus upsell and the tier-named renewal on /account. Same
   discipline as the foundation above: classes only, transform/opacity motion,
   and nothing here animates on its own (the card and the upsell are .rv, the
   Plus mark's scale-in and bloom are the foundation's one-shot classes, the
   tile cascade is the duration IIFE's). Two classes deep (.acct .x) wherever
   a card sets its own padding: `.acct section{padding:0}` is (0,1,1) and
   would strip it, the way it once stripped .acct-card's. Gold stays on the
   mark, the Plus edges and the one .btn-gold: the days figure, the ring and
   the copy are the site's cyan and greys. */

/* --- tier card: mark | ring | text, the address row under all three -------- */
.acct .tier-card{display:grid;grid-template-columns:auto auto 1fr;gap:18px;align-items:center;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:14px;
  padding:18px 22px;margin-bottom:26px}
.acct .tier-card--plus{border-color:rgba(245,179,53,.45);box-shadow:var(--glow-gold-sm);
  background:linear-gradient(180deg,rgba(245,179,53,.06),transparent 45%),var(--navy-800)}
/* nothing active: the sentence alone, no empty ring, no mark */
.acct .tier-card--off{grid-template-columns:1fr}
.tier-card__mark{display:flex;align-items:center;justify-content:center;line-height:0}
/* 96px Plus raster (1.3 of the store's 128), 56px base glyph: the card is a
   status row, not a hero */
.tier-card .tier-mark--hero{height:96px}
.tier-card .tier-mark--m{width:56px;height:56px}
/* the halo stays lit once app.js has bloomed it; the foundation rests it at 0
   and halo-bloom ends at .35, so the class lands exactly where the animation
   leaves off and nothing snaps */
.tier-card--plus .tier-mark__wrap.lit .tier-mark__halo{opacity:.35}
.tier-card__ring{position:relative;width:84px;height:84px;flex:none}
.tier-card__ring .sub-ring{width:100%;height:100%;display:block}
.tier-card__text{min-width:0}
.tier-card__days{font-size:1.02rem;font-weight:700;color:var(--text-hi);line-height:1.4}
/* the counted figure: JBMono, tabular, so 30 -> 29 never shifts the sentence */
.tier-card__days b{font-family:JBMono,monospace;font-size:1.6rem;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--text-hi);vertical-align:-.05em;margin-inline-end:2px}
.tier-card__days.off{color:var(--text-low);font-weight:400}
.tier-card__tierline,.tier-card__all,.tier-card__discord{margin-top:5px;font-size:.88rem;color:var(--text-low);line-height:1.7}
.tier-card__tierline .mono{font-size:.86rem}
.tier-card__all{color:var(--text-mid);font-weight:700}
.tier-card__discord a{color:var(--cyan-500)}
.tier-card__addr{grid-column:1/-1;min-width:0}
/* the same full-width pill the «اتصال» card had: the address GROWS into the row */
.tier-card__addr .addr-pill{width:100%}
.tier-card__addr .addr-pill code{flex:1;min-width:0;font-size:clamp(.92rem,3.6vw,1.15rem);padding:14px 14px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0}
.tier-card__addr .addr-pill button{flex:none}
.tier-card__addr .hint{margin-top:10px}

/* --- Plus upsell: the gold-edged card a base customer sees ---------------- */
.acct .plus-upsell{background:linear-gradient(180deg,rgba(245,179,53,.07),transparent 40%),var(--navy-800);
  border:1px solid rgba(245,179,53,.45);border-radius:14px;box-shadow:var(--glow-gold-sm);
  padding:20px 22px;margin-bottom:26px}
.plus-upsell__head{display:flex;align-items:center;gap:16px;margin-bottom:14px}
.plus-upsell__mark{flex:none;line-height:0}
.plus-upsell .tier-mark--hero{height:72px}
.acct .plus-upsell h2{margin:0;font-size:1.15rem;line-height:1.5}
.plus-upsell__strip{margin-bottom:18px}
/* a little wider than the foundation's 52px minimum: the account strip has no
   player counts, so a name the tile ellipsises has no tooltip to fall back on;
   58px fits eight tiles in the 600px column, one row per edition */
.plus-upsell .tier-grid--compact{grid-template-columns:repeat(auto-fill,minmax(58px,1fr))}
.plus-upsell__offer{display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center}
.plus-upsell__quote{font-size:.95rem;color:var(--text-mid)}
.plus-upsell__quote b{color:var(--text-hi)}
.plus-upsell__quote .mono{font-family:JBMono,monospace;font-size:1.05rem}
.plus-upsell__offer form{width:100%}
.plus-upsell__offer .btn-gold{display:block;width:100%;text-align:center;padding:12px 0}
.plus-upsell__deadline{font-size:.8rem;color:var(--text-low)}

/* --- renewal: the tier-named heading over one duration control ------------ */
.renew{margin-bottom:30px}
.renew .renew__mark{width:22px;height:22px}
.renew .acct-lead{margin-bottom:12px}
.renew .durctl{margin-bottom:4px}

@media(max-width:560px){
  /* mark + ring on one row, the text under them, the address under that */
  .acct .tier-card{grid-template-columns:auto 1fr;gap:14px 16px;padding:16px}
  .tier-card__text{grid-column:1/-1}
  .tier-card .tier-mark--hero{height:72px}
  .tier-card .tier-mark--m{width:48px;height:48px}
  .tier-card__ring{width:72px;height:72px}
  .tier-card__days b{font-size:1.45rem}
  .acct .plus-upsell{padding:16px}
  .plus-upsell__head{gap:12px}
  .plus-upsell .tier-mark--hero{height:56px}
  .acct .plus-upsell h2{font-size:1.05rem}
}

/* ==== Plus UI: list ==========================================================
   The game switcher as tier groups (a «پایه» row, then a «پلاس» row under the
   Plus mark, per edition), the locked Plus panel a base subscriber sees, the
   sliding tab indicator and the ping skeleton. The scroller/fade/gating rules
   for the rows live with the older switcher CSS above (retargeted from .gsw to
   .gsw-row / .gsw-group); this block adds only what the redesign introduced.
   Classes and custom properties only (CSP style-src 'self'); motion is
   transform/opacity, guarded by prefers-reduced-motion below and by CP.reduce
   in app.js (which never adds .skel or .nudge under reduced motion). Gold here:
   the mark, the Plus header's label and its underline, and ONE .btn-gold per
   locked panel (only the chosen panel is visible under html.js). */

/* --- group header: the tier's name at the start of its row. The Plus one
   carries the 16px mark and reads gold. .on marks the group holding the chosen
   tab: the label lifts and a 2px line draws under it from the start edge.
   z-index 3 lifts it above the group's edge fades (z-index 2). */
.gsw-gh{position:relative;z-index:3;display:inline-flex;align-items:center;gap:6px;
  margin-inline-start:8px;padding-bottom:3px;--gh-c:var(--cyan-500);
  font-size:.78rem;font-weight:700;letter-spacing:.02em;color:var(--text-low);line-height:1.4}
.gsw-gh span{transition:color .18s ease-out}
.gsw-gh .tier-mark--s{width:16px;height:16px}
.gsw-gh::after{content:"";position:absolute;inset-inline:0;bottom:0;height:2px;border-radius:1px;
  background:var(--gh-c);transform:scaleX(0);transform-origin:var(--gsw-out-s,right)}
.gsw-gh.on::after{transform:scaleX(1)}
.gsw-gh.on span{color:var(--text-hi)}
.gsw-gh--plus{--gh-c:var(--gold-500)}
.gsw-gh--plus span,.gsw-gh--plus.on span{color:var(--gold-500)}

/* --- this week's rotating four. CYAN, mirroring the Plus rule exactly, and
   deliberately NOT gold: gold means Plus everywhere on this site, so a gold
   heading here would read as "these are Plus games" — the precise opposite of
   what the row says. --gh-c is already cyan by default; set again so the
   underline cannot silently change if that default ever moves.
   The deadline is static server-rendered text, never a live countdown: a
   ticking timer is a second source of truth that can disagree with the door,
   and a continuous repaint we have been bitten by before. */
.gsw-gh--week{--gh-c:var(--cyan-500)}
.gsw-gh--week span,.gsw-gh--week.on span{color:var(--cyan-500)}
.gsw-gh--week .gsw-until{font-style:normal;font-weight:600;font-size:.72rem;
  color:var(--text-low);letter-spacing:0}

/* --- the sliding indicator: one 2px bar per row that app.js parks under the
   chosen tab with --ix/--iy (the tab's offsetLeft, and its offsetTop + height,
   so it follows a tile across a scrolled AND a wrapped row) and --is (the
   tab's width / 100). Physical left + translate on purpose: offsetLeft is
   physical and so is the row's scrollLeft maths; a logical inset would put
   the two in opposite frames under dir=rtl. scaleX(0) until the first
   select, so a row with no chosen tab shows nothing. */
.gsw-ind{position:absolute;top:0;left:0;width:100px;height:2px;border-radius:1px;
  background:var(--cyan-500);pointer-events:none;
  transform:translate(var(--ix,0px),var(--iy,0px)) scaleX(var(--is,0));transform-origin:left}

/* --- locked panel: the lock block takes the address column's seat in the
   connect card; the readings beside it are the live rail every panel shows.
   The card itself goes quiet (no cyan glow: nothing here is an address to
   copy), and the block is the Plus story: mark | fact / quote / one button.
   Under 560px it stacks, the button full width like the page's other CTA. */
.game-panel.is-locked .cn-main{border-color:var(--navy-600);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.04)}
.game-panel.is-locked .cn::before{opacity:.55}
.tp-lock{display:grid;grid-template-columns:auto minmax(0,1fr);
  grid-template-areas:"mark msg" "mark quote" "mark cta";
  align-items:center;column-gap:16px;row-gap:6px;min-width:0}
.tp-lock__mark{grid-area:mark;align-self:start;line-height:0}
.tp-lock__mark .tier-mark--m{width:56px;height:56px}
.tp-lock__msg{grid-area:msg;margin:0;font-size:1.15rem;font-weight:800;color:var(--text-hi);line-height:1.4}
.tp-lock__quote{grid-area:quote;margin:0;font-size:.9rem;color:var(--text-mid);line-height:1.8}
.tp-lock__quote b{color:var(--text-hi)}
.tp-lock__quote .mono{font-family:JBMono,monospace;font-size:1rem}
.tp-lock__cta{grid-area:cta;justify-self:start;margin-top:6px;padding:10px 24px;font-size:.95rem}
/* no quote (window closed): the button sits right under the fact */
.tp-lock__msg + .tp-lock__cta{margin-top:2px}

/* --- skeleton: the figure is in the HTML from the first byte, transparent
   under a shimmer until its row or panel reveals and app.js counts it in.
   inline-block so the sweep can clip to the digits' box (an inline <b> has no
   overflow to clip). The sweep keyframe (skel-sweep, transform only) is in the
   foundation block above. color !important: the tab's own .on rule outranks a
   bare class, and a half-shown figure would defeat the point. */
.skel{display:inline-block;position:relative;overflow:hidden;border-radius:4px;
  color:transparent!important;background:rgba(255,255,255,.06)}
.skel::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.10),transparent)}

@media (max-width:560px){
  .tp-lock{grid-template-columns:1fr;grid-template-areas:"mark" "msg" "quote" "cta";
    column-gap:0;row-gap:8px;width:100%}
  .tp-lock__mark .tier-mark--m{width:48px;height:48px}
  .tp-lock__cta{justify-self:stretch;text-align:center}
}

@media (prefers-reduced-motion:no-preference){
  /* the lock block shrugs once per session when its panel is chosen (CP.once) */
  @keyframes lock-nudge{0%,100%{transform:none}25%{transform:translateX(3px)}75%{transform:translateX(-3px)}}
  .tp-lock.nudge{animation:lock-nudge .36s ease-out 1}
  .gsw-ind{transition:transform .28s var(--ease-out),opacity .2s}
  .gsw-gh::after{transition:transform .28s var(--ease-out)}
}
@media (prefers-reduced-motion:reduce){
  .gsw-gh span{transition:none}
  .skel{color:inherit!important}
}

/* ==== Plus UI: landing tier strip ============================================
   Two cards, Plus first, each one link into the store with the tier set. The
   same anatomy as a tier panel at a third of the height: mark on the reading
   side, name, price, one line of names. Gold only on the Plus card's edge and
   its mark; the section's single cyan button sits under the strip. */
.tier-strip{display:grid;grid-template-columns:1fr 1fr;gap:14px;max-width:820px;margin:var(--sp-6) auto 22px;text-align:start}
.tier-strip__card{display:flex;align-items:center;gap:16px;min-height:96px;padding:18px 20px;touch-action:manipulation;
  background:var(--navy-800);border:1px solid var(--navy-600);border-radius:var(--r-card);
  color:var(--text-mid);text-decoration:none;
  transition:transform .18s ease-out,box-shadow .18s ease-out,border-color .18s ease-out}
.tier-strip__card:hover{transform:translateY(-2px);border-color:var(--cyan-700)}
.tier-strip__card:active{transform:scale(.98)}
.tier-strip__card:focus-visible{outline:2px solid var(--cyan-300);outline-offset:3px}
.tier-strip__card--plus{border-color:rgba(245,179,53,.45);box-shadow:var(--glow-gold-sm);
  background:linear-gradient(180deg,rgba(245,179,53,.06),transparent 40%),var(--navy-800)}
.tier-strip__card--plus:hover{border-color:var(--gold-500);box-shadow:var(--glow-gold-md)}
.tier-strip__mark{flex:0 0 auto}
.tier-strip__card .tier-mark--base{width:48px;height:48px;color:var(--cyan-500)}
.tier-strip__body{display:flex;flex-direction:column;gap:4px;min-width:0}
.tier-strip__name{font-weight:800;font-size:1.05rem;color:var(--text-hi)}
.tier-strip__card--plus .tier-strip__name{color:var(--gold-300)}
.tier-strip__price{color:var(--text-hi);font-size:1rem}
.tier-strip__price .mono{font-weight:700;font-size:1.15rem}
.tier-strip__price small{font-size:.8rem;color:var(--text-mid);margin-inline-start:4px}
.tier-strip__list{font-size:.86rem;line-height:1.6;color:var(--text-mid)}
@media (max-width:640px){
  .tier-strip{grid-template-columns:1fr;max-width:480px;gap:12px}
  .tier-strip__card{padding:16px}
}
@media (prefers-reduced-motion:reduce){
  .tier-strip__card{transition:none}
  .tier-strip__card:hover,.tier-strip__card:active{transform:none}
}
