/* ==========================================================================
   ARTEMISPVP  --  STYLESHEET
   Layout/metrics measured from a reference build; palette is ours (violet).
   All colours come from the tokens below and from theme{} in config.js.
   ========================================================================== */
:root{
  /* --- violet palette. Base #7c00ff was given by Eric 2026-08-16. --- */
  --accent:#7c00ff;          /* primary violet */
  --accent-bright:#a44dff;   /* lifted violet for glows and gradient tops */
  --accent-deep:#5a00bd;     /* deeper violet for hovers / gradients */
  --accent-soft:rgba(124,0,255,.12);
  --accent-line:rgba(124,0,255,.34);
  --accent-text:#ffffff;     /* white on a violet fill: 6.4:1, the old near-black was 3.3:1 */
  --accent-on-dark:#b98cff;  /* violet readable as text on the dark background */
  --ip-glow:#c46bff;         /* purple-pink, a hair lighter than --accent: the travelling IP highlight */

  --bg:#101010;
  --card:#161616;
  --card-2:#1b1b1b;
  --crumb:#202020;
  --text:#bdbdbd;
  --text-strong:#ffffff;
  --text-soft:rgba(255,255,255,.75);
  --hair:rgba(255,255,255,.024);
  --pill:rgba(255,255,255,.03);
  --panel:rgba(255,255,255,.01);

  --max:1100px;
  /* THE SECTION GAP - Eric 2026-09-01: "Adding spaces in between sections should be a website
     STANDARD." One token, used by every section-level block, so a new section can never ship
     with a different gap than the one beside it. Change it here, it changes everywhere. */
  --section-gap:56px;
  --bubble:22px;             /* bubbly radius for post cards */
  --radius:8px;
}
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0}
/* The page colour lives on <html> so body's two backdrop layers below can paint
   behind the content instead of being covered by body's own background. */
/* overflow-x:hidden moved here from body, 2026-09-01. On BODY it makes the body its own scroll
   container, and the moment body also became a flex column (see "centre the site" near the end of
   this file) the page stopped scrolling from the top: scrollY stuck, and a tall black band rendered
   above the fixed navbar. Caught by A/B - display:block rendered fine, display:flex did not.
   On HTML it clips the same horizontal overflow with no scroll-container side effect. */
html{background:var(--bg);overflow-x:hidden}
body{
  background:transparent; color:var(--text);
  font-family:'Montserrat','Segoe UI',system-ui,sans-serif;
  font-size:16px; font-weight:400; line-height:24px;
  -webkit-font-smoothing:antialiased;
}

/* ---------- night-sky backdrop (Eric 2026-08-17) ----------
   "some sort of spacey planet night minecraft background but blurred and with
   transparency layer on top 60%". Two fixed layers behind everything:
     ::before  the image itself, blurred
     ::after   the veil, --bd-veil defaulting to his 0.60
   The image is a knob, not a hardcode - config.js backdrop.image can point at
   any picture (drop one of your own screenshots in and it just works); blur and
   veil are config.js too. inset is negative so the blur has room to bleed and
   never shows a soft edge at the viewport border. */
body::before{content:"";position:fixed;inset:-80px;z-index:-2;pointer-events:none;
  background-image:var(--bd-img,none);background-size:cover;background-position:50% 34%;
  background-repeat:no-repeat;filter:blur(var(--bd-blur,15px)) saturate(1.06)}
/* The layer over the picture. TWO stacked washes, painted in this order:
     1. a flat BLUE tint  (--bd-blue) - Eric asked for "a little blue"
     2. the dark veil     (--bd-veil) - his "layer of transperacy ... 60%"
   Between them the photo reads as atmosphere rather than as a photo, which is
   what "so its not super clear/visible" means.
   The veil stays NEUTRAL blue-black, never violet: "background is already to
   much purple" - a purple veil repaints whatever is underneath it. The site's
   violet belongs to the UI. */
body::after{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(180deg,
      rgba(0,0,0,calc(var(--bd-veil,.6) * .92)) 0%,
      rgba(0,0,0,var(--bd-veil,.6)) 46%,
      rgba(0,0,0,calc(var(--bd-veil,.6) * 1.06)) 100%),
    linear-gradient(180deg,
      rgba(38,86,158,var(--bd-blue,0)) 0%,
      rgba(26,62,120,calc(var(--bd-blue,0) * .82)) 100%)}
/* no image configured: fall back to the flat page colour, no empty blur layer */
body.no-backdrop::before,body.no-backdrop::after{display:none}
body.no-backdrop{background:var(--bg)}
img{max-width:100%;display:block;border:0}
a{color:var(--accent-on-dark);text-decoration:none;transition:color .18s ease,opacity .18s ease}
a:hover{color:#d3b8ff}   /* lighter on hover: --accent-deep is darker than the page and vanished */
a:focus-visible,button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
p{margin:0 0 16px}
h2{font-size:32px;font-weight:500;color:var(--text-strong);margin:0 0 16px;line-height:1.2;text-wrap:balance}
h3{font-size:22px;font-weight:600;color:var(--text-strong);margin:26px 0 12px}
h4{font-size:18px;font-weight:600;color:var(--text-strong);margin:0 0 10px}
ul{margin:0 0 16px;padding-left:20px}
code{background:var(--pill);padding:2px 6px;border-radius:4px;color:var(--accent-on-dark);font-size:14.4px}
hr{border:0;border-top:1px solid var(--hair);margin:22px 0}
::selection{background:var(--accent);color:var(--accent-text)}
.container{width:100%;max-width:var(--max);margin:0 auto;padding:0 16px}
.main{padding:34px 0 60px}
.muted{color:var(--text);opacity:.65}

/* ---------- navbar ---------- */
.navbar{position:absolute;top:0;left:0;right:0;z-index:60;height:80px;display:flex;align-items:center;padding:15px 0;transition:background .25s ease,height .25s ease}
/* Eric 2026-08-17: the sticky header is transparent on EVERY size, not just mobile.
   Nav text and icons get their own shadow/pill so they stay readable over whatever
   scrolls beneath. */
.navbar.stuck{position:fixed;background:transparent;backdrop-filter:none;height:64px;padding:0;border-bottom:0}
/* Fully transparent made page text run straight into the nav links as it scrolled
   under them. This is a soft top-down fade, not a panel - the bar still reads as
   see-through, but headings passing beneath no longer collide with the links.
   Delete this rule for a hard 100% transparent bar. */
.navbar.stuck::before{content:"";position:absolute;inset:-1px 0 auto 0;height:104px;pointer-events:none;z-index:-1;
  background:linear-gradient(180deg,rgba(5,3,9,.92) 0%,rgba(6,4,11,.58) 46%,rgba(6,4,11,0) 100%)}
.navbar.stuck .nav-list a,.navbar.stuck .nav-auth a{text-shadow:0 2px 8px rgba(0,0,0,.9)}
.navbar.stuck .nav-social a{background:rgba(16,16,16,.72);backdrop-filter:blur(6px)}
.navbar.stuck .nav-toggle{filter:drop-shadow(0 2px 6px rgba(0,0,0,.85))}
.navbar .container{display:flex;align-items:center;gap:10px}
.nav-list{list-style:none;display:flex;margin:0 auto;padding:0;align-items:center}
.nav-list li{display:flex}
.nav-list a{display:flex;align-items:center;color:rgba(255,255,255,.92);font-size:16.8px;font-weight:600;line-height:25.2px;margin:5.04px 22px;padding:6.72px 0;position:relative;
  transition:color .22s ease,transform .3s cubic-bezier(.34,1.56,.64,1)}
/* RESTORED 2026-08-17: the nav-icon + podium rules were wiped by an earlier
   block replace, so the icons and the Vote/Explore/Store podium had been
   rendering unstyled. Rebuilt here together with the modern motion Eric asked
   for: every header item lifts slightly and grows an underline from the CENTRE
   out, and its icon pops - instead of a colour blink. */
.nav-ic{display:inline-flex;vertical-align:-2px;margin-right:7px;opacity:.85;
  transition:transform .34s cubic-bezier(.34,1.56,.64,1),opacity .22s ease}
.nav-list a:hover .nav-ic{transform:translateY(-1px) scale(1.14);opacity:1}
.nav-list a::before{content:"";position:absolute;left:50%;right:50%;bottom:1px;height:2px;
  border-radius:2px;background:linear-gradient(90deg,var(--accent),var(--accent-bright));
  opacity:0;transition:left .3s cubic-bezier(.34,1.56,.64,1),right .3s cubic-bezier(.34,1.56,.64,1),opacity .22s ease}
.nav-list a:not(.podium):not(.struck):hover{transform:translateY(-2px)}
.nav-list a:not(.podium):not(.struck):hover::before{left:0;right:0;opacity:1}
.nav-list a.podium::before{display:none}

/* THE PODIUM - Vote / Explore / Store side by side, all raised, middle highest */
.nav-list li.podium-li a{margin:5.04px 4px}
/* Eric 2026-08-17: "make the header less purple more black/darker purple".
   Header-scoped only - the global --accent stays #7c00ff for the rest of the
   site. Explore is still the highest, brightest pill of the three, it is just
   a deep near-black violet now instead of full-saturation violet. */
.nav-list a.podium{background:rgba(20,10,34,.66);color:var(--text-strong);
  padding:6.72px 16px;border-radius:999px;
  box-shadow:0 4px 14px rgba(0,0,0,.4),0 0 0 1px rgba(124,0,255,.2) inset;
  transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .26s ease,background .22s ease,color .22s ease}
.nav-list a.podium.p1{background:linear-gradient(180deg,#3a1173,#210840);color:var(--accent-text);transform:translateY(-5px);
  box-shadow:0 8px 22px rgba(0,0,0,.5),0 0 0 1px rgba(168,110,255,.28) inset}
.nav-list a.podium.p2{transform:translateY(-2.5px)}
.nav-list a.podium.p3{transform:translateY(-1px)}
.nav-list a.podium:hover{color:#fff;background:linear-gradient(180deg,#4a1a8c,#2b0b54);
  box-shadow:0 12px 30px rgba(0,0,0,.55),0 0 0 1px rgba(190,140,255,.34) inset}
.nav-list a.podium.p1:hover{transform:translateY(-7px)}
.nav-list a.podium.p2:hover{transform:translateY(-5px)}
.nav-list a.podium.p3:hover{transform:translateY(-4px)}
.nav-list a.podium.struck{opacity:.55}
.nav-list a.podium.struck:hover{background:rgba(20,10,34,.66);color:var(--text-strong);transform:translateY(-1px)}
.nav-list li.active a.podium::after{display:none}
.drawer a.featured{color:var(--accent-on-dark);font-weight:800}
.drawer .nav-ic{margin-right:9px}
.nav-list li.active a{color:var(--accent-on-dark)}
.nav-list li.active a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--accent);border-radius:2px}
.nav-list a:hover{color:var(--accent-on-dark)}
/* Store is struck through until a store exists */
.nav-list a.struck,.drawer a.struck{text-decoration:line-through;text-decoration-thickness:2px;opacity:.5;cursor:not-allowed}
.nav-list a.struck:hover,.drawer a.struck:hover{color:rgba(255,255,255,.92)}
.nav-auth{list-style:none;display:flex;margin:0;padding:0;gap:10px}
.nav-auth a{color:var(--text-strong);font-size:14.4px;font-weight:600;padding:8px 18px;border-radius:999px;border:1px solid var(--accent-line)}
.nav-auth a:hover{background:var(--accent);border-color:var(--accent-on-dark);color:var(--accent-text)}

/* ---------- username dropdown ----------
   Eric: hovering your name opens Profile / Control Panel / Settings / Support.
   He also asked for the highlight around the username to go TRANSPARENT on
   hover, so the trigger explicitly opts out of the filled .nav-auth a:hover. */
.nav-user{position:relative}
.nav-auth .nav-user > a{display:flex;align-items:center;gap:7px}
.nav-auth .nav-user > a:hover,
.nav-user:hover > a,
.nav-user:focus-within > a{background:transparent;border-color:transparent;color:var(--text-strong)}
.nav-user > a .caret{transition:transform .22s ease;opacity:.75}
.nav-user:hover > a .caret,.nav-user:focus-within > a .caret{transform:rotate(180deg)}
/* invisible bridge: the pointer crosses the gap without the menu closing */
.nav-user::after{content:"";position:absolute;left:0;right:0;top:100%;height:14px}
.nav-menu{position:absolute;right:0;top:calc(100% + 12px);min-width:216px;z-index:140;
  background:rgba(19,15,26,.97);backdrop-filter:blur(18px) saturate(130%);-webkit-backdrop-filter:blur(18px) saturate(130%);
  border:1px solid var(--accent-line);border-radius:15px;padding:7px;
  box-shadow:0 22px 54px rgba(0,0,0,.6);
  opacity:0;visibility:hidden;translate:0 -8px;
  transition:opacity .2s ease,translate .24s cubic-bezier(.34,1.4,.64,1),visibility .2s}
.nav-user:hover .nav-menu,.nav-user:focus-within .nav-menu{opacity:1;visibility:visible;translate:0 0}
.nav-menu a{display:flex;align-items:center;gap:11px;padding:10px 12px;border-radius:10px;
  color:var(--text);font-size:14.4px;font-weight:600;border:0;background:none}
.nav-menu a:hover{background:var(--accent-soft);color:var(--text-strong)}
.nav-menu a svg{opacity:.72;flex:0 0 auto}
.nav-menu hr{border:0;border-top:1px solid var(--hair);margin:6px 4px}
.nav-menu .who{padding:9px 12px 5px;display:flex;align-items:center;justify-content:space-between;gap:14px}
.nav-menu .who-k{font-size:12px;letter-spacing:.08em;text-transform:uppercase;opacity:.5;font-weight:800}
/* the prefix keeps its in-game look, so it is NOT uppercased and NOT dimmed */
.nav-menu .who-v{font-size:13px;font-weight:800;white-space:nowrap;opacity:1;
  text-shadow:0 1px 0 rgba(0,0,0,.85)}
/* The equipped IN-GAME TITLE. Minecraft draws chat in a pixel font with a hard
   1px drop shadow; the title reads as itself only if it keeps that. Monospace
   digits keep a title like "[100%]" from jittering when it re-renders, and
   `white-space:pre` preserves any leading/trailing space the author put in the
   token - trimming it would silently change an authored string. */
.nav-menu .mc-title{white-space:pre;letter-spacing:.2px;
  text-shadow:1px 1px 0 rgba(0,0,0,.9);font-variant-numeric:tabular-nums}
.nav-menu .who-hint{font-size:12px;font-weight:600;opacity:.55;text-transform:none}
/* Rank AND Title, two rows. The Rank row is his original order and must not go
   missing again; Title sits under it, tighter, so the pair reads as one block. */
.nav-menu .who-2{padding-top:0;padding-bottom:8px}
/* an obfuscated (&k) run keeps a stable box while its glyphs churn */
.mc-k{display:inline-block;min-width:1ch;text-align:center}
.nav-auth a.solid{background:var(--accent);border-color:var(--accent-on-dark);color:var(--accent-text)}
.nav-auth a.solid:hover{background:var(--accent-deep);border-color:var(--accent-deep);color:#fff}
.nav-toggle{display:none;margin-left:auto;width:42px;height:38px;background:none;border:0;color:#fff;cursor:pointer;align-items:center;justify-content:center}

/* ---------- drawer ---------- */
/* Eric 2026-08-17: "make the actual menu pop up a little transperant as well" -
   translucent panel with a blur behind it so the page still reads through. */
.drawer{position:fixed;top:0;right:0;width:290px;height:100%;z-index:200;background:rgba(19,15,26,.72);backdrop-filter:blur(16px) saturate(130%);-webkit-backdrop-filter:blur(16px) saturate(130%);border-left:1px solid var(--accent-line);transform:translateX(100%);transition:transform .28s ease;overflow-y:auto}
.drawer.open{transform:none}
/* Eric 2026-09-02: a transparent sheet under the open drawer, over the page - a tap outside the
   menu hits this and never the link underneath. z-index 199 = one under the drawer (200). */
.drawer-scrim{position:fixed;inset:0;z-index:199;display:none;background:transparent}
.drawer-scrim.open{display:block}
.drawer-head{display:flex;align-items:center;justify-content:space-between;height:71px;padding:0 18px;border-bottom:1px solid var(--hair)}
.drawer-head span{color:var(--accent-on-dark);font-weight:700;letter-spacing:.05em}
.drawer-head button{width:42px;height:36px;background:#202020;border:0;border-radius:var(--radius);color:var(--text);cursor:pointer}
.drawer ul{list-style:none;margin:0;padding:10px 0}
.drawer li a{display:block;height:48px;line-height:48px;padding:0 22px;color:var(--text);font-size:16px;font-weight:600}
/* Eric 2026-09-02: the account links stay in the same stacked list at the BOTTOM of the drawer -
   only their alignment moves from the left edge to the right one. */
.drawer li.drawer-auth a{text-align:right}
.drawer li.active a,.drawer li a:hover{background:var(--accent-soft);color:var(--accent-on-dark)}

/* ---------- hero ----------
   Layer order, back to front: base -> optional photo -> network canvas -> gradient
   veil (65% at the top, per Eric 2026-08-16) -> logo row.
   The row sits in normal flow under a padding-top that clears the 80px navbar, so
   there is no dead band above it. The old build used height:430px with
   margin-top:-150px, which left ~280px of empty gradient. */
/* transparent so the page backdrop shows through the hero too - the veil below
   is what tints it, exactly as before */
/* Eric 2026-08-31: "maybe we can shrink the header a little bit to make the header smaller because
   right now it's big for no reason. We have a lot of empty space."
   Measured before changing it: the hero was 258px tall on a 1280x720 viewport, of which 166px was
   padding (100 top + 66 bottom) and only ~92px was the actual logo/IP row. The padding was the
   empty space, not the content - so the padding comes down and nothing inside the header moves. */
.hero-wrap{position:relative;overflow:hidden;background:transparent;padding:58px 0 34px;isolation:isolate}
.hero-art{position:absolute;inset:0;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;opacity:.55;z-index:0}
.hero-net{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:1}
/* --veil is the strength of the colour wash (Eric asked for ~65%). It scales the
   coloured stops only; the bottom still resolves to --bg at full alpha so the hero
   blends into the page with no seam. Setting opacity on the element instead would
   wash that bottom stop out and leave a visible band. */
/* Eric 2026-08-17: "make the header less purple more black/darker purple".
   The wash keeps the same SHAPE - same stops, same angles, same seamless
   bottom - but the violets are deep instead of bright and the black arrives
   earlier and heavier. --veil drops from .65 to .40, and the two bright
   violets (164,77,255 / 124,0,255) become deep ones. */
.hero-veil{--veil:.40;position:absolute;inset:0;z-index:2;pointer-events:none;background:
  radial-gradient(ellipse 78% 72% at 50% 0%,rgba(104,38,180,calc(var(--veil) * 1)) 0%,rgba(104,38,180,0) 68%),
  linear-gradient(168deg,
    rgba(58,10,112,calc(var(--veil) * .9)) 0%,
    rgba(30,6,62,calc(var(--veil) * .7)) 34%,
    rgba(7,4,13,.74) 74%,
    rgba(5,3,10,.6) 100%)}
/* The last two stops used to resolve to solid --bg so the hero met a solid page
   with no seam. The page is no longer solid - it is the night sky - so they are
   translucent now for the same reason: the hero has to blend into what is
   actually behind it. The --veil strength Eric set is untouched. */

/* logo row: status | logo | discord/vote  (all links are ours) */
/* gap is what separates the IP / vote bubbles from the wordmark. Eric asked for
   them CLOSER, so this must stay below the old 20px - it is not a free knob. */
.logo-row{display:flex;align-items:center;justify-content:center;gap:12px;max-width:var(--max);margin:0 auto;padding:0 16px;position:relative;z-index:20}
.logo-row>*{flex:1 1 0;display:flex}
.cell-left{justify-content:flex-end}
.cell-logo{flex:0 0 auto;justify-content:center}
.cell-right{justify-content:flex-start}
.brandmark{width:min(380px,74vw);height:auto;display:block;filter:drop-shadow(0 12px 34px rgba(0,0,0,.7))}

.info-box{display:flex;align-items:center;gap:14px;padding:14px 20px;background:rgba(255,255,255,.04);border:1px solid var(--accent-line);border-radius:999px;color:var(--text);
  animation:drift3 10.7s ease-in-out infinite;will-change:translate,scale;
  transition:scale .32s cubic-bezier(.34,1.56,.64,1),background .25s ease,border-color .25s ease}
/* Eric 2026-08-31: "it would be cool if the server IP bubble and the support us bubble moved
   differently - they didn't move at the same time in the same direction, so they're not lined up
   or synced."
   They already had different names and durations, which is why this looked done and was not: BOTH
   keyframes open by moving DOWN-RIGHT (drift3 hits 5px,7px and drift5 hits 6px,5px), so for the
   first couple of seconds of every cycle the two bubbles travel together, and 10.7s vs 13.1s is
   close enough to re-align often. drift-cta below is deliberately the OPPOSITE path - it opens
   up-left while the left bubble goes down-right - and the duration is moved well away from the
   left one so the two cycles drift apart instead of beating together. */
.info-box.right{animation-name:drift-cta;animation-duration:16.3s;animation-delay:-6.7s}
@keyframes drift-cta{0%{translate:0 0}26%{translate:-6px -5px}52%{translate:7px -3px}78%{translate:3px 8px}100%{translate:0 0}}
.info-box:hover{background:var(--accent-soft);color:var(--text);border-color:var(--accent);scale:1.055}
/* Eric 2026-08-17: the Server-IP icon and the vote STAR light up on hover.
   The icon becomes its own lamp - fill warms, glyph goes white, halo blooms. */
.info-box .ico{width:42px;height:42px;flex:0 0 42px;display:grid;place-items:center;border-radius:50%;
  background:var(--accent-soft);color:var(--accent-on-dark);position:relative;
  transition:background .28s ease,color .28s ease,transform .34s cubic-bezier(.34,1.56,.64,1),
             box-shadow .34s ease}
.info-box .ico::after{content:"";position:absolute;inset:-6px;border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(185,140,255,.55),transparent 68%);
  opacity:0;transform:scale(.7);transition:opacity .3s ease,transform .34s ease}
.info-box:hover .ico{background:var(--accent);color:#fff;transform:scale(1.1);
  box-shadow:0 0 20px rgba(124,0,255,.6),0 0 0 1px rgba(255,255,255,.16) inset}
.info-box:hover .ico::after{opacity:1;transform:scale(1)}
.info-box .ico svg{transition:filter .28s ease}
.info-box:hover .ico svg{filter:drop-shadow(0 0 6px rgba(255,255,255,.85))}
.info-box .top{color:var(--accent-on-dark);font-size:15px;font-weight:700;text-transform:uppercase;line-height:20px}
.info-box .mid{color:var(--text-strong);font-size:13px;font-weight:600;text-transform:uppercase;line-height:18px}
.info-box .bot{color:var(--text-soft);font-size:11.5px;font-weight:600;text-transform:uppercase;line-height:17px;opacity:.85}
.info-box.copied .bot{color:var(--accent-on-dark)}
.info-box.right{flex-direction:row-reverse;text-align:right}

/* ---------- content ---------- */
.breadcrumb{list-style:none;display:flex;flex-wrap:wrap;gap:6px;
  background:rgba(20,17,28,.55);border:1px solid rgba(255,255,255,.05);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  border-radius:999px;padding:9px 20px;font-size:13.6px;margin:0 0 18px}
.breadcrumb a{color:var(--accent-on-dark)}
.breadcrumb li::after{content:"/";margin-left:6px;opacity:.45}
.breadcrumb li:last-child::after{content:""}
/* Glass, not a flat grey box. This is what lets the sky read through the page,
   and it is the single biggest change away from the stock dark-card look.
   A browser without backdrop-filter still gets a legible panel - the rgba fill
   is doing most of the work and the blur is the garnish. */
.card{background:rgba(20,17,28,.60);border:1px solid rgba(255,255,255,.055);
  border-radius:var(--bubble);margin-bottom:20px;overflow:hidden;
  backdrop-filter:blur(11px) saturate(1.15);-webkit-backdrop-filter:blur(11px) saturate(1.15);
  box-shadow:0 18px 44px rgba(0,0,0,.34)}
.card-header{color:var(--accent-on-dark);font-size:20.8px;font-weight:700;padding:18px 24px 0;line-height:1.2}
.card-body{padding:14px 24px 24px;color:var(--text)}
.card-body>*:last-child{margin-bottom:0}

/* ---------- bubbly blog cards ---------- */
/* The news list is a section in its own right, so it takes the standard gap below it. Without
   this the Server Staff card on the home page sat directly under the last post - Eric 2026-09-01:
   "add space between posts and staff section on home page". The 22px INSIDE the list is the gap
   between individual posts and is deliberately tighter than the gap between SECTIONS. */
.post-list{display:flex;flex-direction:column;gap:22px;margin-bottom:var(--section-gap)}
/* nothing trails the list on a page that is only posts, so no stray gap above the footer */
.post-list:last-child{margin-bottom:0}
/* Glass, matching .card. These were the last opaque boxes on the public pages -
   with a night sky behind the site, a solid slab reads as a stock dark-theme
   card, which is exactly the look Eric asked to get away from. */
.post-card{display:flex;background:rgba(20,17,28,.58);border:1px solid rgba(255,255,255,.055);
  backdrop-filter:blur(11px) saturate(1.12);-webkit-backdrop-filter:blur(11px) saturate(1.12);
  border-radius:var(--bubble);overflow:hidden;will-change:scale;
  transition:scale .34s cubic-bezier(.34,1.56,.64,1),border-color .25s ease,box-shadow .3s ease}
.post-card:hover{scale:1.014;border-color:var(--accent-line);box-shadow:0 18px 44px rgba(0,0,0,.5),0 0 0 5px var(--accent-soft)}
.post-card .art{flex:0 0 56%;position:relative;overflow:hidden;min-height:260px;border-radius:var(--bubble)}
.post-card .art img{width:100%;height:100%;object-fit:cover}
.post-card .art .empty{position:absolute;inset:0;display:grid;place-items:center;text-align:center;padding:24px;background:linear-gradient(160deg,rgba(124,0,255,.18),rgba(124,0,255,.03));color:var(--accent-on-dark);font-size:13.6px;font-weight:600;text-transform:uppercase;letter-spacing:.08em}
.post-card .side{flex:1 1 auto;padding:24px 26px;display:flex;flex-direction:column}
.post-card.no-art .art{display:none}
.post-title{color:var(--accent-on-dark);font-size:26px;font-weight:700;line-height:36px;display:block;margin-bottom:10px}
.post-excerpt{color:var(--text);flex:1 1 auto}
.post-foot{display:flex;align-items:center;gap:12px;margin-top:22px}
.avatar{width:40px;height:40px;border-radius:50%;flex:0 0 40px;background:var(--accent-soft)}
.by{font-size:15px;font-weight:600;color:var(--text-strong);line-height:19px}
.when{font-size:13px;color:var(--text);opacity:.7}
.readtime{margin-left:auto;background:var(--pill);border-radius:999px;padding:8px 18px;font-size:14px;font-weight:600;color:var(--text)}
.post-card:hover .readtime{background:var(--accent);color:var(--accent-text)}

/* empty state (no posts yet) */
.empty-state{background:rgba(20,17,28,.5);border:1px solid rgba(255,255,255,.05);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  border-radius:var(--bubble);padding:56px 28px;text-align:center}
.empty-state h3{margin:0 0 8px}
.empty-state p{margin:0;color:var(--text);opacity:.8}

/* ---------- lists / tiles ---------- */
.rules-list{list-style:none;margin:0;padding:0;counter-reset:r}
.rules-list li{counter-increment:r;position:relative;padding:11px 0 11px 40px;border-bottom:1px solid var(--hair)}
.rules-list li:last-child{border-bottom:0}
.rules-list li::before{content:counter(r);position:absolute;left:0;top:10px;width:26px;height:26px;display:grid;place-items:center;border-radius:50%;background:var(--accent-soft);color:var(--accent-on-dark);font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.grid{display:grid;gap:20px}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
/* Bubbly tiles (Eric 2026-08-16): they drift on their own, barely but always, and
   answer a hover with a springy pop instead of a flat highlight.
   The idle drift animates the standalone `translate` property while hover animates
   `scale` - keeping them on separate properties is what stops the animation from
   stomping the hover transform (one shared `transform` makes it snap). */
/* Eric 2026-08-17: drift must wander in ALL directions around itself, not just up
   and down, and nothing may share a path/duration/phase with its neighbour. Five
   different closed paths, each assigned a different duration and a negative delay
   so every element starts mid-path. Prime-ish durations keep them from re-syncing. */
@keyframes drift1{0%{translate:0 0}20%{translate:7px -6px}40%{translate:-3px -10px}60%{translate:-8px -2px}80%{translate:2px 6px}100%{translate:0 0}}
@keyframes drift2{0%{translate:0 0}25%{translate:-9px -4px}50%{translate:4px -9px}75%{translate:8px 4px}100%{translate:0 0}}
@keyframes drift3{0%{translate:0 0}30%{translate:5px 7px}55%{translate:-6px 3px}80%{translate:-2px -8px}100%{translate:0 0}}
@keyframes drift4{0%{translate:0 0}18%{translate:-5px 6px}42%{translate:7px 2px}66%{translate:3px -7px}88%{translate:-6px -3px}100%{translate:0 0}}
@keyframes drift5{0%{translate:0 0}22%{translate:6px 5px}48%{translate:-7px -6px}72%{translate:2px -3px}100%{translate:0 0}}

/* Eric 2026-08-17: the support tiles were "stupid silly moving" - tamed to a
   3-4px wander (was 7-10px) on slower cycles. Still omni-directional, still
   desynced, just far less freedom. */
@keyframes tame1{0%{translate:0 0}25%{translate:3px -2px}50%{translate:-1px -4px}75%{translate:-3px 1px}100%{translate:0 0}}
@keyframes tame2{0%{translate:0 0}30%{translate:-4px 2px}60%{translate:2px 3px}85%{translate:3px -2px}100%{translate:0 0}}
@keyframes tame3{0%{translate:0 0}22%{translate:2px 3px}55%{translate:-3px 1px}80%{translate:-1px -3px}100%{translate:0 0}}
.tile{background:var(--pill);border-radius:var(--bubble);padding:24px;text-align:center;display:flex;flex-direction:column;gap:10px;
  animation:tame1 13.4s ease-in-out infinite;will-change:translate,scale;
  transition:scale .34s cubic-bezier(.34,1.56,.64,1),box-shadow .3s ease,background .3s ease}
.tile:nth-of-type(3n+2){animation-name:tame2;animation-duration:16.1s;animation-delay:-4.3s}
.tile:nth-of-type(3n+3){animation-name:tame3;animation-duration:11.9s;animation-delay:-8.6s}
.tile:hover{scale:1.05;background:var(--accent-soft);box-shadow:0 18px 44px rgba(0,0,0,.5),0 0 0 5px var(--accent-soft)}
.tile .ico,.tile .n{transition:scale .34s cubic-bezier(.34,1.56,.64,1),background .3s ease}
.tile:hover .ico,.tile:hover .n{scale:1.14}
.tile p{font-size:14.4px;margin:0;flex:1 1 auto}
.tile .n{width:42px;height:42px;margin:0 auto;display:grid;place-items:center;border-radius:50%;background:var(--accent-soft);color:var(--accent-on-dark);font-weight:700}
.tile .ico{width:48px;height:48px;margin:0 auto;display:grid;place-items:center;border-radius:50%;background:var(--accent-soft);color:var(--accent-on-dark)}
.btn{display:inline-block;background:var(--accent);color:var(--accent-text);font-size:14.4px;font-weight:700;padding:11px 24px;border-radius:999px;text-align:center;border:0;cursor:pointer;font-family:inherit;
  transition:background .22s ease,scale .3s cubic-bezier(.34,1.56,.64,1),box-shadow .25s ease}
.btn:hover{background:var(--accent-deep);color:#fff;scale:1.04;box-shadow:0 10px 26px rgba(124,0,255,.32)}
.btn:active{scale:.97}
.btn.ghost{background:var(--pill);color:var(--text)}
.btn.ghost:hover{background:var(--accent-soft);color:var(--accent-on-dark)}
.btn.off{background:var(--pill);color:var(--text);opacity:.4;pointer-events:none;text-decoration:line-through}
.btn.block{display:block;width:100%}

/* ---------- faq / team ---------- */
.faq-item{border-bottom:1px solid var(--hair)}
.faq-item:last-child{border-bottom:0}
.faq-q{width:100%;text-align:left;background:none;border:0;color:var(--text-strong);font-family:inherit;font-size:16px;font-weight:600;padding:15px 0;cursor:pointer;display:flex;gap:12px}
.faq-q:hover{color:var(--accent-on-dark)}
.faq-q .c{margin-left:auto;color:var(--accent-on-dark);transition:transform .2s ease;display:flex}
.faq-item.open .faq-q .c{transform:rotate(180deg)}
.faq-a{display:none;padding:0 0 16px}
.faq-item.open .faq-a{display:block}
.team-group{margin-bottom:30px}
.team-group h4{text-align:center;letter-spacing:.14em;text-transform:uppercase;font-size:15px;margin-bottom:16px}
.team-row{display:flex;flex-wrap:wrap;gap:16px;justify-content:center}
.member{width:150px;background:var(--pill);border-radius:var(--bubble);padding:20px 12px;text-align:center}
.member .face{width:64px;height:64px;margin:0 auto 10px;border-radius:50%;background:var(--accent-soft)}
.member .nm{font-size:15px;font-weight:700;color:var(--text-strong);word-break:break-all}
.member .rk{font-size:12px;font-weight:700;text-transform:uppercase;margin-top:3px;color:var(--accent-on-dark)}

/* ---------- server IP: a glow that walks the address one character at a time ----------
   Eric 2026-08-17: "one letter lights up purpleishpink (a hair lighter then our
   websites current) and one letter lights up at a time and moves down the ip".
   Every character gets the same short pulse; the per-character delay is what makes
   it travel instead of flashing all at once. */
.ip-sweep{display:inline-flex;flex-wrap:wrap}
.ip-sweep .ipc{display:inline-block;white-space:pre;will-change:color,scale;
  animation:ip-pulse 3.2s linear infinite;animation-delay:calc(var(--i) * .085s)}
@keyframes ip-pulse{
  0%,100%{color:var(--text-strong);text-shadow:none;scale:1}
  4%     {color:var(--ip-glow);text-shadow:0 0 12px var(--ip-glow),0 0 4px var(--ip-glow);scale:1.24}
  10%    {color:var(--text-strong);text-shadow:none;scale:1}
}

/* ---------- vote: a field of floating bubbles ----------
   Eric 2026-08-17: the whole vote section is a bubble field (cryptobubbles-style),
   ten of them, smaller so they fit, and on mobile they are just named circles. */
/* Long, wandering paths. The tile drifts move ~8px; these move 40-90px so the field
   visibly roams the way cryptobubbles does. Five paths, all closed loops. */
@keyframes swim1{0%{translate:0 0}18%{translate:52px -34px}37%{translate:18px -74px}56%{translate:-46px -48px}74%{translate:-62px 14px}88%{translate:-16px 46px}100%{translate:0 0}}
@keyframes swim2{0%{translate:0 0}22%{translate:-58px 26px}44%{translate:-24px 68px}63%{translate:44px 52px}81%{translate:66px -18px}100%{translate:0 0}}
@keyframes swim3{0%{translate:0 0}16%{translate:34px 56px}33%{translate:-28px 72px}52%{translate:-70px 12px}71%{translate:-30px -52px}87%{translate:40px -30px}100%{translate:0 0}}
@keyframes swim4{0%{translate:0 0}25%{translate:-44px -58px}48%{translate:36px -70px}67%{translate:74px -10px}85%{translate:28px 44px}100%{translate:0 0}}
@keyframes swim5{0%{translate:0 0}20%{translate:64px 22px}40%{translate:30px 66px}60%{translate:-42px 58px}80%{translate:-66px -26px}100%{translate:0 0}}

/* The field is a physics arena now, not a flex row. Positions come from the
   simulation in site.js (bounce off the walls and off each other, draggable), so
   nothing here may animate or lay out the bubbles. */
/* Eric 2026-09-02 "fix the bubbles" (phone): touch-action:none sat on the WHOLE arena, so a swipe
   anywhere in this 70vh block did nothing - the page would not scroll through it. Now the arena
   lets a vertical swipe scroll (pan-y) and only the bubbles themselves are touch-action:none, so
   a drag that starts ON a bubble is never turned into a page scroll. */
.bubble-field{position:relative;width:100%;height:620px;margin:10px 0 4px;touch-action:pan-y;overflow:hidden}
/* Eric 2026-08-17: "crypto bubble section can be widened for max fun" - the VOTE
   page's content column opens up to 1560px so the arena has real room to roam.
   Scoped to main.section so the navbar container keeps its normal 1100px and the
   header does not go wide on this one page. Bubble size is derived from the arena,
   so a bigger arena means more space between them, not bigger bubbles. */
body[data-page="vote"] main.section > .container{max-width:1560px}

/* Glass bubbles. Eric 2026-08-17: "more transparent (barely any of our website
   colors)" - so the fill is a faint tint at ~14% and the read comes from the rim
   light and the specular highlight, not from a solid colour. */
.vbub{position:absolute;left:0;top:0;width:var(--size,140px);height:var(--size,140px);border-radius:50%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:12px;
  cursor:grab;user-select:none;-webkit-user-select:none;
  touch-action:none;-webkit-touch-callout:none;-webkit-user-drag:none;-webkit-tap-highlight-color:transparent;
  background:
    radial-gradient(circle at 30% 24%,rgba(255,255,255,.34),rgba(255,255,255,.06) 34%,rgba(255,255,255,0) 56%),
    radial-gradient(circle at 50% 118%,rgba(255,255,255,.14),rgba(255,255,255,0) 62%),
    radial-gradient(circle at 50% 50%,color-mix(in srgb,var(--fill) 14%,transparent),color-mix(in srgb,var(--fill) 5%,transparent));
  border:1px solid color-mix(in srgb,var(--fill) 42%,transparent);
  box-shadow:inset 0 0 26px color-mix(in srgb,var(--fill) 16%,transparent),inset 0 -8px 20px rgba(255,255,255,.06),0 10px 34px rgba(0,0,0,.34);
  backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);
  color:#fff;
  will-change:transform;transition:box-shadow .3s ease,border-color .3s ease}
.vbub.grabbed{cursor:grabbing;z-index:5;box-shadow:inset 0 0 30px color-mix(in srgb,var(--fill) 30%,transparent),0 24px 54px rgba(0,0,0,.6)}
.vbub .vname{font-weight:800;font-size:13.5px;line-height:1.16;text-shadow:0 1px 4px rgba(0,0,0,.8)}
.vbub .vcd{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;opacity:.75;margin-top:7px;text-shadow:0 1px 4px rgba(0,0,0,.8)}
/* Hover = the bubble goes TRANSPARENT and says "Click Me !" (Eric 2026-08-17)
   - the name and cooldown fade out, the fill drops to a ghost of itself, and
   the invitation fades in. Points at the same click that opens the vote link. */
.vbub .vgo{position:absolute;inset:0;display:grid;place-items:center;
  font-weight:800;font-size:14.5px;letter-spacing:.04em;color:#fff;
  text-shadow:0 1px 6px rgba(0,0,0,.9);opacity:0;transition:opacity .22s ease;pointer-events:none}
/* Mouse only. A phone has no hover, but a tap still sets :hover and it STICKS after the finger
   lifts - the bubble stayed scaled up, name hidden, reading "Click Me !" until something else
   was touched. On touch this block never applies (Eric 2026-09-02, "fix the bubbles"). */
@media (hover:hover){
.vbub:hover{scale:1.06;
  background:radial-gradient(circle at 50% 50%,color-mix(in srgb,var(--fill) 5%,transparent),transparent 72%);
  border-color:color-mix(in srgb,var(--fill) 30%,transparent);
  box-shadow:inset 0 0 18px color-mix(in srgb,var(--fill) 8%,transparent),0 12px 34px rgba(0,0,0,.35)}
.vbub:hover .vname,.vbub:hover .vcd{opacity:0;transition:opacity .18s ease}
.vbub:hover .vgo{opacity:1}
}
/* while actually dragging, keep the label - you are steering it, not clicking it */
.vbub.grabbed .vgo{opacity:0}
.vbub.grabbed .vname{opacity:1}
/* a bubble with no link yet still drags - the cursor must not claim otherwise */
.vbub.off{opacity:.7;cursor:grab}
.vbub.off.grabbed{cursor:grabbing}

/* NOTE: the ambient hero bubbles were REMOVED on Eric's order 2026-08-17 -
   "Make sure the crypto bubbles do not go in the top header animation (take these
   out)". The header keeps the node network only. Do not reintroduce them. */

/* ---------- rules as hover bubbles ----------
   Eric 2026-08-17: like cosmicencore's rules list, but HOVER instead of click
   ("clicking each rule can get annoying"), and the rule itself is a bubble that
   moves a little more while you are on it. Small title + short line always
   visible; the long explanation opens on hover. No tiers - flat list, as now. */
.rule-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:18px;counter-reset:rb}
/* touch-action:pan-y - a vertical swipe still scrolls the page, a sideways drag
   nudges the card. cursor:grab because it IS grabbable. The nudge writes
   `transform`; the idle wander animates `translate`; they compose. */
.rule-bub{counter-increment:rb;position:relative;background:var(--pill);border:1px solid transparent;border-radius:var(--bubble);
  padding:15px 20px 15px 56px;cursor:grab;touch-action:pan-y;
  animation:tame3 15.2s ease-in-out infinite;will-change:translate,transform,scale;
  transition:scale .3s cubic-bezier(.34,1.56,.64,1),border-color .25s ease,background .25s ease,box-shadow .3s ease,
             transform .55s cubic-bezier(.22,1.35,.36,1)}
.rule-bub.dragging{cursor:grabbing;z-index:3;
  transition:scale .2s ease,border-color .2s ease,background .2s ease,box-shadow .2s ease}
.rule-bub.dragging{box-shadow:0 20px 46px rgba(0,0,0,.55)}
.rule-bub:nth-of-type(3n+2){animation-name:tame1;animation-duration:17.6s;animation-delay:-5.2s}
.rule-bub:nth-of-type(3n+3){animation-name:tame2;animation-duration:13.1s;animation-delay:-9.4s}
.rule-bub::before{content:counter(rb);position:absolute;left:18px;top:15px;width:26px;height:26px;display:grid;place-items:center;
  border-radius:50%;background:var(--accent-soft);color:var(--accent-on-dark);font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.rule-bub:hover,.rule-bub:focus-within{scale:1.015;border-color:var(--accent-line);background:var(--accent-soft);
  box-shadow:0 14px 34px rgba(0,0,0,.4);animation-duration:4.6s}
.rule-title{font-size:16.5px;font-weight:700;color:var(--text-strong);line-height:1.3}
.rule-short{font-size:13px;color:var(--text);opacity:.7;margin-top:3px}
.rule-detail{max-height:0;overflow:hidden;opacity:0;font-size:14.4px;color:var(--text);
  transition:max-height .34s ease,opacity .26s ease,margin-top .3s ease}
.rule-bub:hover .rule-detail,.rule-bub:focus-within .rule-detail{max-height:400px;opacity:1;margin-top:11px}

/* ---------- confirm dialog ---------- */
.modal-back{position:fixed;inset:0;z-index:400;background:rgba(6,4,12,.74);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  display:grid;place-items:center;padding:20px}
.modal{background:var(--card);border:1px solid var(--accent-line);border-radius:var(--bubble);max-width:470px;width:100%;padding:26px 26px 24px;
  box-shadow:0 30px 80px rgba(0,0,0,.6)}
.modal h4{margin:0 0 10px;font-size:20px}
.modal p{font-size:14.6px}
.modal .row{display:flex;gap:12px;margin-top:22px}
.modal .row .btn{flex:1 1 0}
.vote-actions{display:flex;justify-content:center;margin:4px 0 18px}

/* ---------- vote: the plain click-each-card list ----------
   Eric 2026-08-17: "a clean click each card list for the vote links like the
   appeals section but with the cards side by side". Whole card is the link.
   auto-fit keeps them side by side and wraps rather than squashing. */
/* Eric 2026-08-17: "a lot bigger and on desktop 2 rows only". The column count
   is NOT hardcoded to 5 - site.js sets --vcols to ceil(cards / 2), so it stays
   exactly two rows whatever number of vote sites end up on the list. */
.vote-cards{display:grid;grid-template-columns:repeat(var(--vcols,5),1fr);gap:14px}
/* Redesigned 2026-08-17 - Eric: "cards look like shit ... we gotta leave the ai
   look". The centred chip-over-name-over-pill stack is the stock generated-card
   shape, so it is gone. This is a server-list panel instead: the rank number is
   a big ghost numeral bled into the corner, the name is left-aligned and does
   the talking, and VOTE is a full-width bar along the bottom. Glass fill and a
   square-ish radius so it sits on the night sky like a game panel rather than a
   rounded content box. Still five across and two rows, still big - his last two
   orders both stand. */
.vcard{position:relative;overflow:hidden;isolation:isolate;
  display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-end;
  gap:2px;min-height:186px;padding:18px 16px 16px;
  border-radius:14px;background:rgba(16,13,24,.52);
  border:1px solid rgba(255,255,255,.07);
  border-left:3px solid var(--fill,var(--accent));
  backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);
  color:var(--text);text-decoration:none;
  transition:transform .28s cubic-bezier(.34,1.56,.64,1),background .22s ease,border-color .22s ease,box-shadow .28s ease}
/* a faint wash of the site's own colour, strongest at the top-left corner */
.vcard::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(120% 90% at 0% 0%,color-mix(in srgb,var(--fill,var(--accent)) 26%,transparent) 0%,transparent 62%)}
.vcard .n{position:absolute;top:-16px;right:2px;z-index:-1;
  font-size:82px;line-height:1;font-weight:900;font-variant-numeric:tabular-nums;
  color:color-mix(in srgb,var(--fill,var(--accent)) 30%,transparent);
  letter-spacing:-.04em;user-select:none}
.vcard .nm{font-size:19px;font-weight:800;color:var(--text-strong);line-height:1.16;text-align:left}
.vcard .cd{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;opacity:.5;margin-bottom:12px}
.vcard .go{width:100%;text-align:center;font-size:13px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;
  color:#fff;background:var(--fill,var(--accent));padding:11px 10px;border-radius:8px;
  transition:filter .2s ease}
.vcard:hover{transform:translateY(-4px);background:rgba(26,20,40,.66);
  border-color:color-mix(in srgb,var(--fill,var(--accent)) 45%,transparent);
  box-shadow:0 18px 40px rgba(0,0,0,.5)}
.vcard:hover .go{filter:brightness(1.14)}
.vcard.off{opacity:.42;cursor:not-allowed}
.vcard.off:hover{transform:none;background:rgba(16,13,24,.52);box-shadow:none}
/* Two rows is a DESKTOP instruction. Five big cards across a tablet or a phone
   would be unreadable, so below 991px the count goes back to what fits. */
@media (max-width:991px){
  .vote-cards{grid-template-columns:repeat(3,1fr);gap:12px}
  .vcard{min-height:168px}
  .vcard .n{font-size:70px}
  .vcard .nm{font-size:17px}
}
@media (max-width:600px){
  .vote-cards{grid-template-columns:repeat(2,1fr);gap:10px}
  .vcard{min-height:142px;padding:14px 12px 12px}
  .vcard .n{font-size:56px;top:-10px}
  .vcard .nm{font-size:15px}
  .vcard .cd{font-size:10px;margin-bottom:9px}
  .vcard .go{font-size:12px;padding:9px 8px}
}

/* owner vote-link editor rows */
.vrow{display:grid;grid-template-columns:1.1fr 2fr 1fr .7fr auto;gap:8px;align-items:center;margin-bottom:9px}
.vrow input{width:100%;padding:9px 12px;background:var(--card-2);border:1px solid var(--hair);border-radius:var(--radius);color:#fff;font-family:inherit;font-size:14px}
.vrow input:focus{outline:0;border-color:var(--accent-on-dark)}
@media (max-width:760px){.vrow{grid-template-columns:1fr 1fr;gap:6px}}

/* ==========================================================================
   CONTROL PANEL
   Sidebar + work area. Full width on its own page, its own surface tokens so it
   reads as a tool rather than as another marketing card.
   ========================================================================== */
body[data-page="panel"] main.section > .container{max-width:1660px}
.panel{--pane:#141019;--pane-2:#191420;--line:rgba(255,255,255,.07);
  display:grid;grid-template-columns:236px minmax(0,1fr);gap:18px;align-items:start}
.panel-side{background:var(--pane);border:1px solid var(--line);border-radius:18px;padding:14px;position:sticky;top:78px}
.panel-brand{font-size:12px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--text);opacity:.5;padding:6px 10px 12px}
.panel-nav{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:3px}
.panel-nav button{width:100%;display:flex;align-items:center;gap:11px;background:none;border:0;cursor:pointer;
  padding:11px 12px;border-radius:11px;color:var(--text);font:inherit;font-size:14.6px;font-weight:600;text-align:left;
  transition:background .18s ease,color .18s ease}
.panel-nav button:hover{background:rgba(255,255,255,.045);color:var(--text-strong)}
.panel-nav button.on{background:var(--accent);color:var(--accent-text);box-shadow:0 8px 22px rgba(124,0,255,.34)}
.panel-nav button.on svg{opacity:1}
.panel-nav svg{opacity:.7;flex:0 0 auto}
.panel-nav .tag{margin-left:auto;font-size:11px;font-weight:800;background:rgba(255,255,255,.1);padding:2px 8px;border-radius:999px}
.panel-nav button.on .tag{background:rgba(0,0,0,.25)}
.panel-whoami{margin-top:14px;padding:11px 12px;border-top:1px solid var(--line);font-size:13px;color:var(--text)}
.panel-whoami b{display:block;color:var(--text-strong);font-size:14.5px}

.panel-main{min-width:0;background:var(--pane);border:1px solid var(--line);border-radius:18px;overflow:hidden}
.panel-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:18px 20px;border-bottom:1px solid var(--line);background:var(--pane-2)}
.panel-head h3{margin:0;font-size:19px;color:var(--text-strong)}
.panel-head .sub{font-size:13px;color:var(--text);opacity:.65}
.panel-head .spacer{margin-left:auto}
.panel-body{padding:18px 20px 22px}

/* stat tiles */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:12px}
.stat{background:var(--pane-2);border:1px solid var(--line);border-radius:14px;padding:15px 16px}
.stat .k{font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--text);opacity:.6}
.stat .v{font-size:31px;font-weight:800;color:var(--text-strong);line-height:1.15;margin-top:5px;font-variant-numeric:tabular-nums}
.stat .n{font-size:12.5px;color:var(--text);opacity:.6;margin-top:2px}
.stat.hot .v{color:var(--accent-on-dark)}
.spark{display:flex;align-items:flex-end;gap:3px;height:44px;margin-top:12px}
.spark i{flex:1 1 0;background:var(--accent);border-radius:3px 3px 0 0;min-height:2px;opacity:.75}
.spark i:last-child{opacity:1}

/* filter bar */
.tk-filters{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
.pill{background:var(--pane-2);border:1px solid var(--line);color:var(--text);border-radius:999px;padding:7px 15px;
  font:inherit;font-size:13.4px;font-weight:700;cursor:pointer;transition:background .18s ease,color .18s ease,border-color .18s ease}
.pill:hover{border-color:var(--accent-line);color:var(--text-strong)}
.pill.on{background:var(--accent);border-color:var(--accent);color:var(--accent-text)}
.panel input[type=search],.panel input[type=text],.panel select,.panel textarea{
  background:var(--pane-2);border:1px solid var(--line);border-radius:10px;color:#fff;font:inherit;font-size:14px;padding:9px 13px}
.panel input:focus,.panel select:focus,.panel textarea:focus{outline:0;border-color:var(--accent-on-dark)}
.panel .grow{flex:1 1 220px;min-width:0}

/* rows */
.tk-list{display:flex;flex-direction:column;gap:9px}
.tk-row{background:var(--pane-2);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:12px;
  padding:13px 15px;cursor:pointer;transition:border-color .2s ease,background .2s ease,transform .2s ease}
.tk-row:hover{background:#1e1728;border-color:var(--accent-line)}
.tk-row.closed{border-left-color:rgba(120,224,160,.6);opacity:.75}
.tk-row .top{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.tk-row .sub2{font-size:12.6px;color:var(--text);opacity:.62;margin-top:4px}
.tk-row .s{font-weight:700;color:var(--text-strong);font-size:15px}
.tk-open{margin-top:10px;background:var(--pane-2);border:1px solid var(--accent-line);border-radius:12px;padding:15px 16px}
.tk-open .body{white-space:pre-wrap;word-break:break-word;font-size:14.4px;margin:10px 0 0}
.tk-open .acts{display:flex;gap:9px;flex-wrap:wrap;margin-top:14px}
.tk-open textarea{width:100%;min-height:88px;margin-top:10px;resize:vertical}
.readonly-note{font-size:13px;color:#ffc65c;background:rgba(255,176,32,.1);border-radius:9px;padding:9px 13px;margin-top:12px}

/* ---------- Panel > Resources ---------- */
.res-row{display:flex;align-items:center;gap:13px;cursor:default}
.res-main{flex:1;min-width:0}
.res-swatch{flex:none;width:28px;height:28px;border-radius:7px;border:1px solid var(--line)}
.res-thumb{flex:none;width:40px;height:40px;border-radius:8px;background:var(--pill);
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.res-thumb img{width:100%;height:100%;object-fit:contain}
.res-copy{flex:none;font-size:12.5px;padding:7px 13px;white-space:nowrap}
.res-nocmd{flex:none;font-size:11.5px;opacity:.5;white-space:nowrap}
/* every row opens a detail card, so it has to read as clickable */
.res-row{cursor:pointer}
/* Colour Codes copies on row click - a flash so the copy is visibly acknowledged, since there is
   no button state to change like the other categories have */
.res-row.res-copied{border-color:var(--accent);background:var(--accent-soft)}
.res-row.res-copied::after{content:"Copied";position:absolute;right:96px;
  font-size:11.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--accent-bright)}
.res-row{position:relative}
.res-row:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px}
/* game art is pixel art - the browser's default smoothing turns a 16x16 texture
   into mush at this size, and it is worse again on the big card */
.res-thumb.pixel img,.res-bigart.pixel img{image-rendering:pixelated}

/* Resources > detail card */
/* The detail card reuses the Explore card's own .xc-* rules (Eric: "like a profile card just like
   our explore section"), so there is almost nothing to style here - only the two places this card
   legitimately differs: it is wider, and its artwork is shown larger, because seeing the thing
   clearly is the whole reason he asked for the card. */
.modal.res-detail{max-width:600px;max-height:86vh;overflow-y:auto}
.res-detail .xc-art{height:auto;background:var(--pane-2);border:1px solid var(--line);
  border-radius:14px;padding:20px;margin:10px 0 16px}
/* the source art is 16x16 game pixels scaled 4x - capping by max-height leaves it tiny, so the
   height is SET and the browser is told not to smooth it back into mush */
.res-detail .xc-art img{height:160px;width:auto;max-width:80%;max-height:none}
/* the Explore card runs two stat columns because its values are short ("350 heart", "700").
   These values are registry ids and texture paths - in two columns they were being clipped, with
   the label showing and the value cut off. One column, value under nothing, nothing lost. */
.res-detail .xc-stats{grid-template-columns:1fr;gap:0}
.res-detail .xc-stat{display:flex;justify-content:space-between;gap:16px;
  padding:7px 0;border-bottom:1px solid rgba(255,255,255,.05)}
.res-detail .xc-stat:last-child{border-bottom:0}
.res-detail .xc-sv{text-align:right;word-break:break-word;max-width:66%}
.res-detail .res-cmd{margin-top:2px}
/* the wiki's in-game shot - a real screenshot, so it is NOT pixelated like the sprite above it */
.res-ingame{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:var(--pane-2)}
.res-ingame img{display:block;width:100%;height:auto;max-height:230px;object-fit:cover}
.res-cmd{display:flex;gap:10px;align-items:center;background:var(--pane-2);
  border:1px solid var(--accent-line);border-radius:12px;padding:11px 13px}
.res-cmd code{flex:1;min-width:0;font-size:13px;color:var(--text-strong);
  overflow-x:auto;white-space:nowrap}
.res-cmd .btn{flex:none}

/* members table */
.mtable{width:100%;border-collapse:collapse;font-size:14px}
.mtable th{text-align:left;font-size:11.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--text);opacity:.55;padding:0 10px 9px;font-weight:800}
.mtable td{padding:11px 10px;border-top:1px solid var(--line);vertical-align:middle}
.mtable tr:hover td{background:rgba(255,255,255,.02)}
.mtable .who{font-weight:700;color:var(--text-strong)}
.mtable .mail{font-size:12.5px;opacity:.6}
.rolechip{font-size:11px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;padding:4px 10px;border-radius:999px;
  background:rgba(255,255,255,.07);color:var(--text)}
.rolechip.owner{background:rgba(196,107,255,.18);color:#dcb3ff}
.rolechip.admin{background:rgba(124,0,255,.2);color:var(--accent-on-dark)}
.rolechip.staff{background:rgba(80,220,130,.13);color:#78e0a0}
.panel-empty{text-align:center;padding:44px 18px;color:var(--text);opacity:.65}

@media (max-width:900px){
  .panel{grid-template-columns:1fr}
  .panel-side{position:static}
  .panel-nav{flex-direction:row;overflow-x:auto;gap:6px}
  .panel-nav button{white-space:nowrap;padding:9px 13px}
  .panel-nav .tag{margin-left:6px}
  .panel-whoami{display:none}
  .mtable thead{display:none}
  .mtable td{display:block;border-top:0;padding:4px 0}
  .mtable tr{display:block;border-top:1px solid var(--line);padding:12px 0}
}

/* ---------- footer logo ---------- */
.footer-brandmark{width:210px;max-width:70%;height:auto;display:block;margin:0 0 12px}

/* ---------- staff ladder ---------- */
.team-group.rung{margin-bottom:26px;padding-bottom:22px;border-bottom:1px solid var(--hair)}
.team-group.rung:last-child{border-bottom:0;padding-bottom:0;margin-bottom:0}
.team-group.rung h4{display:flex;align-items:center;justify-content:center;gap:10px}
.rung-n{width:26px;height:26px;flex:0 0 26px;display:grid;place-items:center;border-radius:50%;background:var(--accent-soft);color:var(--accent-on-dark);font-size:12px;font-weight:800;letter-spacing:0}
/* Eric 2026-08-31: "I want those sections clickable, not the apply
   buttons" - the whole rung (not a small bolt-on link) opens the
   pre-filled staff-application ticket; Owner has none, stays a plain div. */
a.team-group.rung.rung-clickable{display:block;color:inherit;text-decoration:none;cursor:pointer;
  border-radius:var(--radius);transition:background .15s ease}
a.team-group.rung.rung-clickable:hover{background:var(--card-2)}
/* The rank's real in-game tag. --rc is that rank's own colour, straight from
   the server's rank config. White on the solid fill so the darker ranks stay
   readable on the near-black page - the same trick the vote pill uses. */
.rank-tag{display:inline-block;padding:3px 11px;border-radius:999px;background:var(--rc,var(--accent));
  color:var(--rc-fg,#fff);font-size:11.5px;font-weight:800;letter-spacing:.1em;line-height:1.5}
.member .rk .rank-tag{margin-top:2px}
.member.empty{opacity:.48}
.member.empty .nm{font-weight:600;color:var(--text)}
.member.empty .face{background:var(--hair)}

/* ---------- forms ---------- */
.form-wrap{max-width:430px;margin:0 auto}
.form-wrap.wide{max-width:620px}
.field{margin-bottom:16px}
.field label{display:block;font-size:13px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--text);opacity:.75;margin-bottom:7px}
.field input,.field textarea,.field select{width:100%;padding:12px 16px;background:var(--card-2);border:1px solid var(--hair);border-radius:var(--radius);color:#fff;font-family:inherit;font-size:15px}
.field textarea{min-height:150px;resize:vertical;line-height:1.55}
.field select{appearance:none;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--accent-on-dark) 50%),linear-gradient(135deg,var(--accent-on-dark) 50%,transparent 50%);
  background-position:calc(100% - 20px) 50%,calc(100% - 14px) 50%;background-size:6px 6px,6px 6px;background-repeat:no-repeat;padding-right:44px}
.field input:focus,.field textarea:focus,.field select:focus{outline:0;border-color:var(--accent-on-dark)}
.field .hint{font-size:12.5px;color:var(--text);opacity:.6;margin-top:6px;text-transform:none;letter-spacing:0}
/* "Remember me" style checkbox - the native box cannot be themed reliably, so the
   real input is hidden and a .box is drawn next to it (still keyboard-focusable). */
.check{display:flex;align-items:center;gap:10px;margin:0 0 18px;font-size:14.4px;color:var(--text);cursor:pointer;user-select:none}
.check input{position:absolute;opacity:0;width:0;height:0}
.check .box{width:20px;height:20px;flex:0 0 20px;border-radius:6px;background:var(--card-2);border:1px solid var(--accent-line);position:relative;
  transition:background .2s ease,border-color .2s ease,scale .3s cubic-bezier(.34,1.56,.64,1)}
.check .box::after{content:"";position:absolute;left:6.5px;top:2.5px;width:5px;height:10px;border:solid #fff;border-width:0 2.5px 2.5px 0;transform:rotate(45deg);opacity:0;transition:opacity .16s ease}
.check input:checked + .box{background:var(--accent);border-color:var(--accent)}
.check input:checked + .box::after{opacity:1}
.check:hover .box{scale:1.12;border-color:var(--accent)}
.check input:focus-visible + .box{outline:2px solid var(--accent);outline-offset:2px}
.msg{border-radius:var(--radius);padding:12px 16px;font-size:14.4px;margin-bottom:16px}
.msg.err{background:rgba(255,85,85,.08);color:#ff8a8a}
.msg.ok{background:var(--accent-soft);color:var(--accent-on-dark)}
.form-alt{text-align:center;margin-top:18px;font-size:14.4px}

/* ---------- discord: nav icon + hero box ---------- */
.nav-social{display:flex;align-items:center;gap:8px;margin:0;padding:0;list-style:none}
.nav-social a{width:40px;height:40px;display:grid;place-items:center;border-radius:50%;background:var(--pill);border:1px solid var(--accent-line);color:var(--text-strong);transition:background .2s ease,transform .2s ease,color .2s ease}
.nav-social a:hover{background:var(--accent);border-color:var(--accent);color:var(--accent-text);transform:translateY(-2px)}
.info-box.discord .ico{background:rgba(88,101,242,.18);color:#a8b2ff}
.info-box.discord:hover{background:rgba(88,101,242,.14)}

/* ---------- tickets / appeals ---------- */
.ticket-list{display:flex;flex-direction:column;gap:12px}
.ticket{background:var(--pill);border-radius:var(--radius);padding:14px 18px;border-left:3px solid var(--accent)}
.ticket .th{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.ticket .subj{font-weight:700;color:var(--text-strong)}
.ticket .meta{font-size:13px;color:var(--text);opacity:.7;margin-top:4px}
.ticket .body{margin-top:10px;font-size:14.4px;white-space:pre-wrap;word-break:break-word}
.badge{font-size:11.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;padding:4px 11px;border-radius:999px;background:var(--accent-soft);color:var(--accent-on-dark)}
.badge.open{background:rgba(255,176,32,.14);color:#ffc65c}
.badge.closed{background:rgba(80,220,130,.12);color:#78e0a0}
.ref{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:13px;color:var(--accent-on-dark);background:var(--pill);padding:3px 9px;border-radius:6px}

/* ---------- footer socials ---------- */
.footer-social{display:flex;gap:10px;margin-top:16px}
.footer-social a{width:38px;height:38px;display:grid;place-items:center;border-radius:50%;background:var(--pill);color:var(--text);transition:background .2s ease,color .2s ease}
.footer-social a:hover{background:var(--accent);color:var(--accent-text)}

/* ---------- footer ---------- */
.footer-top{background:var(--panel);border-top:1px solid var(--hair);padding:44px 0 32px}
.footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:30px}
.footer h5{font-size:14px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--text-strong);margin:0 0 14px}
.footer-about p{font-size:14.4px;max-width:420px;margin:0}
.footer-brand{color:var(--accent-on-dark);font-size:20px;font-weight:800;letter-spacing:.04em;margin-bottom:10px}
.footer-col ul{list-style:none;margin:0;padding:0}
.footer-col li{margin-bottom:9px}
.footer-col a{color:var(--text);font-size:14.4px}
.footer-col a:hover{color:var(--accent-on-dark)}
.footer-col a.struck{text-decoration:line-through;opacity:.5}
.footer-bottom{background:var(--panel);border-top:1px solid var(--hair);padding:22px 0}
.footer-bottom .container{display:flex;align-items:center;flex-wrap:wrap;gap:10px}
.chip{background:var(--hair);border-radius:999px;padding:5px 14px;font-size:13.6px;font-weight:500;color:var(--text)}

/* ==========================================================================
   RESPONSIVE - no empty cells, no blank gaps
   ========================================================================== */
@media (max-width:991px){
  .nav-list,.nav-auth{display:none}
  .nav-toggle{display:flex}
  /* Eric 2026-08-17: on mobile the SUPPORT US bubble gives way to the SERVER IP
     bubble - the address is the thing a phone visitor actually needs - and the gap
     above the logo closes up. */
  .hero-wrap{padding:44px 0 22px}
  /* SUPPORT US is dropped and the SERVER IP bubble takes its slot BELOW the logo,
     so the logo/title sits tight under the header instead of being pushed down by
     the address bubble. */
  .cell-right{display:none}
  .cell-logo{order:1}
  .cell-left{order:2}
  /* stack the hero row; nothing is left as an empty column */
  .logo-row{flex-direction:column;align-items:center;gap:10px}
  .logo-row>*{width:100%;flex:0 0 auto;justify-content:center}
  .brandmark{width:min(340px,78vw)}
  .info-box{width:100%;max-width:340px;justify-content:center}
  .info-box.right{flex-direction:row}
  .post-card{flex-direction:column}
  .post-card .art{flex:0 0 200px;min-height:200px}
  .g3,.g4{grid-template-columns:repeat(2,1fr)}
  .footer-grid{grid-template-columns:1fr 1fr}
  /* bubble size is computed in JS from the arena; nothing here may fix it */
  .bubble-field{height:480px}
}
@media (max-width:600px){
  body{font-size:15px}
  .hero-wrap{padding:38px 0 18px}
  .brandmark{width:min(300px,84vw)}
  .bubble-field{height:440px}
  .card{border-radius:18px}
  .card-header{font-size:18px;padding:16px 18px 0}
  .card-body{padding:12px 18px 20px}
  .post-card{border-radius:18px}
  .post-title{font-size:21px;line-height:29px}
  .post-card .side{padding:18px}
  .post-foot{flex-wrap:wrap;gap:10px}
  .readtime{margin-left:0}
  .g3,.g4,.footer-grid{grid-template-columns:1fr}
  .member{width:calc(50% - 8px)}
  h2{font-size:25px}
  /* Mobile vote field. Eric 2026-08-17: it looked "crammed" - so the arena breaks
     out to the full card width (the card body pads 18px each side), gets more
     height, and the bubbles shrink because JS sizes them from the arena.
     Name only, no cooldown line, per his earlier order. */
  .bubble-field{margin-left:-18px;margin-right:-18px;width:calc(100% + 36px);height:min(70vh,560px)}
  .vbub{padding:6px}
  .vbub .vname{font-size:11px;line-height:1.1}
  .vbub .vcd{display:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ==========================================================================
   PER-PLAYER WEBSITE SETTINGS (Settings page). Classes go on <html> and are
   applied from the cached copy before first paint, so "reduce motion" is
   honoured on frame one instead of after the animation has already started.
   ========================================================================== */
html.pref-reduce-motion *{animation:none!important;transition:none!important}
html.pref-no-network .hero-net{display:none}
html.pref-compact .main{padding:20px 0 36px}
html.pref-compact .card-body{padding:11px 18px 16px}
html.pref-compact .card{margin-bottom:13px}
html.pref-compact .hero-wrap{padding-top:46px;padding-bottom:24px}
html.pref-compact .rule-list{gap:11px}
html.pref-compact .panel-body{padding:13px 16px 16px}

/* ==========================================================================
   POST BODY BLOCKS
   Emitted by api/_blocks.php for news posts. These had NO styles - the
   renderer shipped before the stylesheet caught up.
   ========================================================================== */
.post-body>*:last-child{margin-bottom:0}
.post-body h2,.post-body h3,.post-body h4{margin-top:28px}
.post-body h2:first-child,.post-body h3:first-child{margin-top:0}

/* ---------- article layout (Eric 2026-08-31: more formal, more spacing) ----------
   The old post page was a plain card with the image in a card of its own above
   it, which stretched a 936x120 banner strip across the full width. This is a
   real article: a fixed-ratio hero that CROPS rather than stretches whatever
   image the post carries, the title over it, and a measured column under it. */
.post{max-width:860px;margin:30px auto 0}
.post-hero{position:relative;border-radius:var(--bubble);overflow:hidden;
  border:1px solid var(--accent-line);aspect-ratio:21/9;background:var(--pane-2)}
.post-hero>img{width:100%;height:100%;object-fit:cover;display:block}
.post-hero-text{position:absolute;left:0;right:0;bottom:0;padding:34px 34px 26px;
  background:linear-gradient(to top,rgba(6,4,12,.92) 12%,rgba(6,4,12,.62) 52%,rgba(6,4,12,0))}
.post-head{padding:4px 0 6px}
.post-hero-text h1,.post-head h1{margin:0 0 8px;font-size:clamp(26px,4.2vw,42px);
  line-height:1.12;letter-spacing:-.015em;color:#fff}
.post-meta{font-size:13px;letter-spacing:.06em;text-transform:uppercase;opacity:.72}
.post-sheet{background:var(--card);border:1px solid var(--accent-line);
  border-radius:var(--bubble);padding:38px 40px 34px;margin-top:20px}
/* the opening line Eric said he likes, given its own room */
.post-lead{margin:0 0 30px;font-size:19.5px;line-height:1.72;color:var(--text-strong);
  font-weight:600}
.post-sheet .post-body{font-size:16.4px;line-height:1.86}
.post-sheet .post-body p{margin:0 0 22px}
.post-sheet .post-body h2{font-size:23px;letter-spacing:-.01em;margin:38px 0 14px}
.post-back{margin-top:22px}
@media (max-width:640px){
  .post-hero{aspect-ratio:16/10}
  .post-hero-text{padding:22px 20px 18px}
  .post-sheet{padding:26px 22px 24px}
  .post-lead{font-size:17.5px;margin-bottom:24px}
}

/* ---------- per-letter highlight sweep on the ticket CTA ----------
   Eric: same idea as the minigames GUI menu - the white highlight travels
   THROUGH the letters. Hover on desktop; on a touch screen it just loops,
   because there is no hover to trigger it. The hidden bold twin under each
   glyph reserves the bold width so going bold cannot reflow the line. */
.sweep{display:inline;font-weight:700;text-decoration:none;color:var(--accent-bright);
  border-bottom:1px solid rgba(164,77,255,.45)}
.sweep .sw-ch{position:relative;display:inline-block}
.sweep .sw-ch b{font-weight:900;visibility:hidden}
.sweep .sw-ch i{position:absolute;left:0;top:0;width:100%;text-align:center;
  font-style:normal;font-weight:700;transition:color .12s linear,font-weight .12s linear}
.sweep .sw-sp{display:inline-block;white-space:pre}
@keyframes sw-lit{
  0%,100%{color:var(--accent-bright);font-weight:700;text-shadow:none}
  22%,52%{color:#fff;font-weight:900;text-shadow:0 0 10px rgba(255,255,255,.55)}
}
.sweep:hover:not(.sweep-delayed) .sw-ch i,
.sweep:focus-visible:not(.sweep-delayed) .sw-ch i,
/* .sweep-delayed waits for a 3s STILL hover; JS adds .sw-run when the dwell timer fires */
.sweep.sweep-delayed.sw-run .sw-ch i{
  animation:sw-lit .62s linear infinite;
  animation-delay:calc(var(--i) * .045s)}
@media (hover:none){
  /* no hover on touch, so the post link just loops; the dwell buttons need a held cursor
     that a phone does not have, so they stay as they are rather than half-working */
  .sweep:not(.sweep-delayed) .sw-ch i{animation:sw-lit .62s linear infinite;
    animation-delay:calc(var(--i) * .045s)}
}
/* the support channel buttons keep their own button colours - the sweep only lifts the
   letters to white, it must not repaint the button into a link */
.btn.sweep{border-bottom:0}
.btn.sweep .sw-ch i{color:inherit}
.btn.sweep.sw-run .sw-ch i{color:var(--accent-bright)}
@media (prefers-reduced-motion:reduce){
  .sweep .sw-ch i{animation:none !important}
  .sweep:hover .sw-ch i{color:#fff;font-weight:900}
}
.post-body blockquote{margin:22px 0;padding:14px 20px;border-left:3px solid var(--accent);
  background:var(--pill);border-radius:0 10px 10px 0}
.post-body blockquote p{margin:0}
.post-body blockquote cite{display:block;margin-top:8px;font-size:13.6px;font-style:normal;opacity:.62}
.post-body pre{margin:20px 0;padding:16px 18px;background:rgba(0,0,0,.36);border-radius:10px;
  border:1px solid var(--hair);overflow-x:auto}
.post-body pre code{background:none;padding:0;color:var(--text);font-size:13.6px;line-height:1.55}
.pb-img,.pb-video{margin:24px 0}
.pb-img img{width:100%;border-radius:14px}
.pb-img figcaption,.pb-video figcaption{margin-top:9px;font-size:13.6px;opacity:.6;text-align:center}
/* 16:9 without a wrapper script - padding-top is the only thing that works for
   an iframe whose height the browser cannot know */
.pb-embed{position:relative;width:100%;padding-top:56.25%;border-radius:14px;overflow:hidden;
  background:rgba(0,0,0,.4)}
.pb-embed iframe,.pb-embed video{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}

/* ==========================================================================
   PAGE TREE  (visual site editor)
   Primitives for the section -> column -> widget markup that api/_tree.php
   renders. Deliberately thin: per-node colour, spacing and size come from the
   generated stylesheet page.php inlines, so these only set STRUCTURE. Anything
   opinionated here would fight the editor.
   ========================================================================== */
.e-page{width:100%}
.e-section{position:relative}
/* Gutters are column PADDING, not flex gap. Percentage widths plus a gap add up
   to more than 100% and the row wraps - which is exactly what happened the first
   time. --e-gutter is what the section's "gap" setting writes. */
.e-row{display:flex;flex-wrap:wrap;align-items:stretch;--e-gutter:24px;
  margin-inline:calc(var(--e-gutter) / -2)}
.e-section>.container.e-row{width:100%;max-width:var(--max);margin:0 auto;
  padding-inline:calc(16px - var(--e-gutter) / 2)}
.e-col{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;
  padding-inline:calc(var(--e-gutter) / 2)}
/* a column is a container for its widgets, so widgets can respond to the column
   they are in rather than only to the page width */
.e-col{container-type:inline-size}
.e-heading{margin:0 0 12px}
.e-text>*:last-child{margin-bottom:0}
.e-image{margin:0 0 16px}
.e-image img{border-radius:14px}
.e-image figcaption{margin-top:8px;font-size:13.6px;opacity:.6}
.e-video{margin:0 0 16px}
.e-button{margin:0 0 16px}
.e-spacer{width:100%;flex:0 0 auto}
.e-divider{margin:0 0 16px}
.e-divider hr{margin:0;border-top:1px solid var(--e-rule,var(--hair))}
.e-fixed{width:100%}
@media (max-width:991px){
  /* The stacking of a column that carries an explicit width is emitted by
     _tree.php as an #id rule - a class rule here could never outrank it. This
     only covers columns with no width set at all. */
  .e-col{flex-basis:100%}
}
.draft-banner{position:sticky;top:0;z-index:80;text-align:center;
  background:var(--accent);color:var(--accent-text);
  font-size:13px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;padding:9px 14px}

/* ==========================================================================
   NEWS BLOCK EDITOR (panel > News)
   A post is a list of section blocks. Each block is a small card with its own
   controls; the server renders them, so nothing here styles post output - that
   is the .post-body rules above.
   ========================================================================== */
.nb-bar{display:flex;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap}
.nb-title{font-weight:700;color:var(--text-strong)}
.nb-status{font-size:10.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  padding:3px 9px;border-radius:999px;background:var(--pill);color:var(--text)}
.nb-status.published{background:rgba(120,224,160,.16);color:#8fe6b4}
.nb-status.scheduled{background:rgba(255,196,107,.16);color:#ffc46b}
.nb-status.draft{background:rgba(255,255,255,.07);color:var(--text)}

.nb-blocks{display:flex;flex-direction:column;gap:12px}
.nb-block{background:var(--pill);border:1px solid var(--hair);border-radius:12px;overflow:hidden}
.nb-head{display:flex;align-items:center;gap:10px;padding:9px 14px;background:rgba(0,0,0,.18)}
.nb-type{font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--accent-on-dark)}
.nb-ops{margin-left:auto;display:flex;gap:6px}
.nb-ops button{background:transparent;border:1px solid var(--hair);color:var(--text);
  font:inherit;font-size:11.5px;padding:4px 10px;border-radius:7px;cursor:pointer;
  transition:background .18s ease,border-color .18s ease,color .18s ease}
.nb-ops button:hover:not(:disabled){background:var(--accent-soft);border-color:var(--accent-line);color:var(--text-strong)}
.nb-ops button:disabled{opacity:.3;cursor:not-allowed}
.nb-body{padding:12px 14px;display:flex;flex-direction:column;gap:9px}
/* Reuse the SAME control styling the rest of the site's forms use (.field
   input/textarea/select, line 596). Without this the inputs fall back to the
   browser default and render as white boxes on the dark panel - which is
   exactly what shipped the first time. */
.nb-body input,.nb-body textarea,.nb-body select,
.nb-file input{width:100%;padding:11px 14px;background:var(--card-2);
  border:1px solid var(--hair);border-radius:var(--radius);color:#fff;
  font-family:inherit;font-size:14.6px}
.nb-body textarea{resize:vertical;line-height:1.55;min-height:84px}
.nb-body select{appearance:none;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--accent-on-dark) 50%),linear-gradient(135deg,var(--accent-on-dark) 50%,transparent 50%);
  background-position:calc(100% - 18px) 50%,calc(100% - 12px) 50%;background-size:6px 6px,6px 6px;background-repeat:no-repeat;padding-right:40px}
.nb-body input:focus,.nb-body textarea:focus,.nb-body select:focus,
.nb-file input:focus{outline:0;border-color:var(--accent-on-dark)}
.nb-body .hint{font-size:12.4px;color:var(--text);opacity:.6}
.nb-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px}
.nb-lvl{max-width:170px}
.nb-add{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.nb-add .btn{padding:8px 16px;font-size:13.5px}
.nb-file{display:flex;gap:8px;align-items:center}
.nb-file input{flex:1 1 auto;min-width:0}
.nb-file .btn{flex:0 0 auto;padding:8px 16px;font-size:13.5px}
.nb-thumb img{max-height:150px;width:auto;border-radius:10px;border:1px solid var(--hair)}
.nb-preview{margin-top:30px;border-top:1px solid var(--hair);padding-top:8px}
@media (max-width:600px){
  .nb-ops{width:100%;margin-left:0}
  .nb-head{flex-wrap:wrap}
}

/* ==========================================================================
   PROFILE  (reorganised 2026-08-17 - Eric: "organize our profile layout
   better ... more organized"). Structure follows his reference: account rail
   on the left, header strip, details grid, ticket table. Visuals stay ours.
   ========================================================================== */
.pf{display:grid;grid-template-columns:250px 1fr;gap:20px;align-items:start}
.pf-side{position:sticky;top:84px;display:flex;flex-direction:column;gap:14px}
.pf-id{background:rgba(20,17,28,.6);border:1px solid rgba(255,255,255,.055);
  backdrop-filter:blur(11px);-webkit-backdrop-filter:blur(11px);
  border-radius:var(--bubble);padding:26px 16px;text-align:center}
.pf-face{width:76px;height:76px;margin:0 auto 12px;border-radius:18px;
  display:grid;place-items:center;font-size:30px;font-weight:800;color:#fff;
  background:linear-gradient(150deg,var(--accent-bright),var(--accent-deep))}
/* the NameMC-style standing character: no tile behind it, the skin IS the
   avatar. It stands on a soft floor shadow so it is not floating in the rail. */
.pf-face.pf-skin{width:auto;height:186px;border-radius:0;background:none;
  position:relative;padding-bottom:10px}
.pf-face.pf-skin img{height:100%;width:auto;display:block;margin:0 auto;
  image-rendering:pixelated;filter:drop-shadow(0 10px 14px rgba(0,0,0,.6))}
.pf-face.pf-skin .pf-fallback{display:none}
.pf-face.pf-skin::after{content:"";position:absolute;left:50%;bottom:0;
  width:78px;height:12px;margin-left:-39px;border-radius:50%;
  background:radial-gradient(ellipse at 50% 50%,rgba(0,0,0,.55),rgba(0,0,0,0) 70%)}
.pf-inline-rank{margin-left:8px}
.pf-h-mail .game-rank{font-size:14px}
.pf-face.sm{width:52px;height:52px;margin:0;font-size:21px;border-radius:13px;flex:0 0 auto}
.pf-name{font-weight:800;color:var(--text-strong);font-size:17px;word-break:break-all}
.pf-rank{margin-top:8px}
.pf-role{display:inline-block;padding:3px 12px;border-radius:999px;background:var(--pill);
  font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--text)}
.pf-nav{background:rgba(20,17,28,.6);border:1px solid rgba(255,255,255,.055);
  backdrop-filter:blur(11px);-webkit-backdrop-filter:blur(11px);
  border-radius:var(--bubble);padding:10px;display:flex;flex-direction:column}
.pf-navh{font-size:10.5px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text);opacity:.55;padding:10px 12px 6px}
.pf-nav a{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:10px;
  color:var(--text);font-size:14.2px;font-weight:600;transition:background .18s ease,color .18s ease}
.pf-nav a:hover{background:var(--accent-soft);color:var(--text-strong)}
.pf-nav a.on{background:var(--accent);color:var(--accent-text)}
.pf-nav svg{flex:0 0 auto;opacity:.8}

.pf-main{display:flex;flex-direction:column;min-width:0}
.pf-head{display:flex;align-items:center;gap:16px;padding:20px 24px}
.pf-h-name{font-weight:800;color:var(--text-strong);font-size:19px}
.pf-h-mail{font-size:13.4px;opacity:.6}
.pf-edit{margin-left:auto;padding:9px 20px;font-size:13.5px}
.pf-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px 26px}
.pf-cell{display:flex;gap:12px;align-items:flex-start}
.pf-cell>svg{flex:0 0 auto;margin-top:3px;color:var(--accent-on-dark)}
.pf-k{font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;opacity:.55}
.pf-v{font-size:14.8px;color:var(--text-strong);margin-top:2px;word-break:break-word}
.pf-tickets-h{display:flex;align-items:center}
.pf-viewall{margin-left:auto;font-size:13px;font-weight:600}
.pf-tablewrap{overflow-x:auto}
.pf-table{width:100%;border-collapse:collapse;font-size:14.2px;min-width:520px}
.pf-table th{text-align:left;font-size:11px;font-weight:800;letter-spacing:.12em;
  text-transform:uppercase;opacity:.55;padding:8px 12px;border-bottom:1px solid var(--hair)}
.pf-table td{padding:11px 12px;border-bottom:1px solid var(--hair);vertical-align:middle}
.pf-table tr:last-child td{border-bottom:0}
@media (max-width:900px){
  .pf{grid-template-columns:1fr}
  .pf-side{position:static;flex-direction:column-reverse}
  .pf-grid{grid-template-columns:1fr}
}
/* the standing character and the identity header, sized for a phone: a shorter
   render so it does not eat the fold, and a header that wraps instead of
   squeezing the Edit button off the edge */
@media (max-width:560px){
  .pf-face.pf-skin{height:140px}
  .pf-head{flex-wrap:wrap;gap:12px;padding:16px 18px}
  .pf-head .pf-edit{margin-left:0;width:100%;text-align:center}
  .pf-h-name{font-size:17px}
}

/* ==========================================================================
   EXPLORE  (Eric 2026-08-17: learning hub for events / PVE / systems)
   Tiles carry each item's AUTHORED colour as --xc.
   ========================================================================== */
/* EXPLORE TILES v4 - LittleBigPlanet pop bubbles + real minecraft.wiki art.
   Eric: "a very short string bubble pop up (reference little big planet menu
   open for this specifically)". LBP's menu bubbles spring out with overshoot,
   are fat rounded capsules with a chunky outline, and point back at the thing
   they belong to. The bubble is ABSOLUTELY positioned, so hovering still moves
   nothing on the page - his hard rule from earlier. */
.xp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px;margin-top:6px;
  /* room for a bubble to pop above the first row without being clipped */
  padding-top:8px}
.xp-tile{position:relative;border-radius:18px;height:158px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;
  padding:12px;text-align:center;cursor:default;outline:0;
  background:radial-gradient(120% 100% at 50% 0%,color-mix(in srgb,var(--xc) 16%,transparent) 0%,transparent 58%),
             rgba(14,11,22,.62);
  border:1px solid color-mix(in srgb,var(--xc) 30%,transparent);
  backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  transition:transform .32s cubic-bezier(.34,1.56,.64,1),border-color .24s ease,box-shadow .3s ease;
  z-index:0}
.xp-tile:hover{transform:translateY(-5px) scale(1.04);z-index:5;
  border-color:color-mix(in srgb,var(--xc) 62%,transparent);
  box-shadow:0 18px 40px rgba(0,0,0,.5),0 0 22px color-mix(in srgb,var(--xc) 22%,transparent),
             inset 0 1px 0 rgba(255,255,255,.08)}

/* the artwork - real wiki renders, transparent PNG */
/* A FIXED box the art is fitted INTO. max-width/max-height with auto sizing did
   not hold: a tall full-body render (the hanging reaper is 211x476) came out
   64x145 and spilled straight through the bottom of the card. object-fit does
   the clamping the max-* pair was failing to do, on every render regardless of
   aspect, and the box is tall enough that a standing mob is not shrunk to
   nothing to make room for a hanging one. */
.xp-pic{position:relative;width:74px;height:86px;display:grid;place-items:center;
  filter:drop-shadow(0 6px 12px rgba(0,0,0,.55))}
/* ABSOLUTE, not a grid item: as a grid item `height:100%` resolved against an
   indefinite basis, so it fell back to aspect sizing and the 211x476 reaper came
   out 74x167 inside a 74x86 box - measured live, not assumed. Pinning to the
   padding box gives the percentage a definite basis, so object-fit can do its
   job on every render whatever its aspect. */
.xp-art{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:block;
  transition:transform .34s cubic-bezier(.34,1.56,.64,1)}
.xp-tile:hover .xp-art{transform:translateY(-4px) scale(1.08)}
/* the Upside Down mobs hang the way they arrive - obvious now that these are
   full-body renders; the old symmetric drawn faces hid the same rotation */
.xp-pic.xp-flip .xp-art{transform:rotate(180deg)}
.xp-tile:hover .xp-pic.xp-flip .xp-art{transform:rotate(180deg) translateY(-4px) scale(1.08)}
.xp-orb span{display:block;width:40px;height:40px;border-radius:50%;
  background:radial-gradient(circle at 34% 30%,rgba(255,255,255,.75),color-mix(in srgb,var(--xc) 80%,#000 0%) 46%,color-mix(in srgb,var(--xc) 38%,#000) 100%);
  box-shadow:0 0 18px color-mix(in srgb,var(--xc) 45%,transparent)}
.xp-name{position:relative;z-index:1;font-weight:800;color:var(--text-strong);font-size:14px;line-height:1.2}
/* ---- My Account panes: Settings / Control panel open BESIDE the rail ----
   The pane is swapped in place of .pf-main, so the rail never moves. The fade is
   short on purpose: a long one reads as a page load, which is the thing this
   replaces. */
@keyframes pf-fade-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.pf-main.pf-fade{animation:pf-fade-in .22s ease-out}
/* Settings and the panel each draw their own breadcrumb for their standalone
   page. Inside a pane the page already has one above the whole layout, so the
   inner one is hidden. A CSS rule, not a JS removal: those renderers are async
   and a removal would race the markup it is trying to remove. */
.pf-main .breadcrumb{display:none}
/* The control panel ships its own vertical sidebar. Inside a pane there is
   already a rail on the left, so its nav lays out as a horizontal tab strip
   instead - one rail, not two. */
.pf-main .panel{display:block}
.pf-main .panel-side{width:auto;min-height:0;border-right:0;border-bottom:1px solid var(--line);
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 4px 12px;background:none}
.pf-main .panel-brand,.pf-main .panel-whoami{display:none}
.pf-main .panel-nav{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none;width:100%}
.pf-main .panel-nav li{margin:0}
.pf-main .panel-nav button,.pf-main .panel-nav .panel-leave{width:auto;padding:8px 13px;border-radius:11px;font-size:13px}
.pf-main .panel-side{position:static;border-radius:14px 14px 0 0}
.pf-main .panel-main{margin-top:14px}
@media (prefers-reduced-motion:reduce){.pf-main.pf-fade{animation:none}}

/* ---- tickets on the profile: rows, not a five-column table ----
   Subject is the headline because that is what you scan for; ref/category/date
   drop to quiet meta underneath. Reads on a phone without sideways scrolling,
   which the table never did. */
.tix{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.tix-row{display:flex;align-items:center;gap:13px;padding:13px 15px;border-radius:14px;
  background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.07);
  transition:background .18s ease,border-color .18s ease,transform .18s ease}
.tix-row:hover{background:rgba(255,255,255,.06);border-color:color-mix(in srgb,var(--accent) 40%,transparent);
  transform:translateX(2px)}
.tix-dot{flex:none;width:9px;height:9px;border-radius:50%;background:#4ade80;
  box-shadow:0 0 0 4px rgba(74,222,128,.14)}
.tix-row.done .tix-dot{background:#6b7280;box-shadow:0 0 0 4px rgba(107,114,128,.14)}
.tix-main{flex:1;min-width:0}
.tix-subj{font-weight:800;color:var(--text-strong);font-size:14.6px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tix-meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:3px;font-size:11.5px;opacity:.55;font-weight:600}
.tix-form{margin-bottom:16px;padding:16px;border-radius:14px;
  background:rgba(0,0,0,.24);border:1px solid rgba(255,255,255,.07)}
.tix-2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.btn.tiny{padding:6px 13px;font-size:12.5px;border-radius:10px}
@media (max-width:600px){.tix-2{grid-template-columns:1fr}}

/* ==========================================================================
   EXPLORE CARDS — the stringed bubble GROWS into the card
   Eric: "I want the small stringed info bubble to turn into A MOVING ANIMATED
   STRINGED CARD. SO MAKE THE CARD ATTACHED TO THE STRING."
   So this is the SAME .xp-pop that already hangs on the string - it just gets
   bigger. No overlay, nothing to click out of, and it keeps the kite sway and
   the pointer-follow because it never stopped being the thing on the string.
   ========================================================================== */
.xp-tile.xp-open{z-index:400}
.xp-tile.xp-open .xp-pop{
  opacity:1;pointer-events:auto;width:min(600px,92vw);max-width:none;
  transform:translateX(calc(-50% + var(--sx,0px) + var(--edge,0px))) translateY(0) scale(1);
  transition:width .34s cubic-bezier(.2,1.25,.4,1),opacity .18s ease;
  /* NO ROTATION while open. The kite tilt made every line of text crooked and
     unreadable - it still hangs and still sways, but on `translate` only. */
  animation:xp-bob 4.2s ease-in-out infinite;rotate:0deg}
@keyframes xp-bob{0%,100%{translate:0 0}50%{translate:0 -5px}}
/* not enough room above -> hang below the tile, string running upward */
.xp-tile.xp-open .xp-pop{z-index:400}
/* ABOVE THE MOUSE. ALWAYS. Eric 2026-08-17, for about the tenth time: "make
   sure that the pop ups actually appear above the mouse instead of underneath".
   The pointer is on the TILE, so the card hangs UP from the tile and can never
   sit under the cursor. There is no flip-below case any more - fitting is
   handled without moving it: open() scrolls the page up when there is not enough
   headroom, then caps --cardmax so the body scrolls INSIDE the card instead of
   the card running off the top of the screen.
   Geometry: this is simply the BASE .xp-pop position restored - hanging above
   with the same 18px stand-off the little hover bubble always used, which is
   also what .xp-str (top:100% of the pop) and transform-origin were built for.
   Flipping it below had left the string dangling off the bottom of the card. */
.xp-tile.xp-open .xp-pop{top:auto;bottom:calc(100% + 18px)}
.xp-tile.xp-open .xp-pop > span{display:none}
.xp-pop{transition:opacity .2s cubic-bezier(.3,.8,.3,1),transform .34s cubic-bezier(.2,1.35,.42,1)}
.xp-tile{transition:transform .3s cubic-bezier(.34,1.5,.64,1),border-color .22s ease,box-shadow .28s ease}

/* SCROLLING STAYS - the SCROLLBAR goes. Eric: "I said get rid of the scroll
   wheel, not get rid of the fucking scrolling." So the card scrolls when it has
   to; the bar itself is simply not painted. */
.xp-cardbody{display:block;text-align:left;padding:20px 22px 18px;border-radius:20px;
  background:linear-gradient(168deg,rgba(20,16,30,.95) 0%,rgba(11,9,17,.95) 100%);
  border:1px solid color-mix(in srgb,var(--xc) 40%,transparent);
  box-shadow:0 26px 64px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.06);
  max-height:min(64vh,470px,var(--cardmax,470px));overflow-y:auto;
  scrollbar-width:none;-ms-overflow-style:none}
.xp-cardbody::-webkit-scrollbar{width:0;height:0;display:none}

/* Eric 2026-08-31: variant switcher for a colour-reskin family (Cosmo/Ceres/
   Corona Cow, ...) collapsed onto one tile - "there's only the one cow...
   click it, it'll scroll through the potential kinds". A horizontal strip of
   short pills, one per real variant name (already trimmed of the shared
   trailing word by variantLabel() in site.js); the active one is filled. */
.xc-variants{display:flex;gap:7px;overflow-x:auto;padding:2px 2px 12px;margin:-4px 0 2px;
  scrollbar-width:none;-ms-overflow-style:none}
.xc-variants::-webkit-scrollbar{width:0;height:0;display:none}
.xc-vbtn{flex:none;appearance:none;border:1px solid color-mix(in srgb,var(--xc) 45%,transparent);
  background:rgba(255,255,255,.04);color:var(--text);border-radius:999px;
  padding:6px 13px;font:700 12px/1 Montserrat,system-ui,sans-serif;cursor:pointer;
  transition:background .16s ease,border-color .16s ease,color .16s ease}
.xc-vbtn:hover{border-color:var(--xc)}
.xc-vbtn.on{background:var(--xc);border-color:var(--xc);color:#0b0912}
/* "it'll have animated movement" - a quick fade+slide every time the card
   repaints for a different variant, restarted via a classList remove/reflow/
   add in paintCardBody() so picking the same kind of transition twice in a
   row still plays it. */
@keyframes xc-swap-in{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}
.xc-swap{animation:xc-swap-in .22s cubic-bezier(.2,.8,.3,1)}
@media (prefers-reduced-motion:reduce){.xc-swap{animation:none}}

/* one clean column. No pills, no chips - Eric: "I don't like the bubbles in the
   cards". Labels are small caps on the left, values plain on the right. */
.xc-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:2px}
/* big authored artwork at the top of the card */
.xc-art{height:96px;margin:6px 0 12px;display:flex;align-items:center;justify-content:center}
.xc-art img{max-height:96px;max-width:60%;image-rendering:pixelated;
  filter:drop-shadow(0 8px 14px rgba(0,0,0,.55))}
.xc-flip img{transform:rotate(180deg)}
.xc-kind{font-size:9.5px;font-weight:800;letter-spacing:.2em;text-transform:uppercase;opacity:.45}
.xc-name{margin:0 0 12px;font-size:25px;line-height:1.1;white-space:pre-wrap;color:var(--text-strong);
  letter-spacing:-.4px;font-weight:900}
.xc-desc{border-left:2px solid color-mix(in srgb,var(--xc) 55%,transparent);padding-left:12px;margin-bottom:4px}
.xc-desc p{margin:0 0 5px;font-size:13.6px;line-height:1.55;opacity:.86}
/* the ONE timer, kept - his words: the only thing done right */
.xc-timer{font-size:11.5px;font-weight:800;font-variant-numeric:tabular-nums;
  letter-spacing:.04em;text-transform:uppercase;color:var(--xc);opacity:.9;white-space:nowrap}
.xc-timer.is-live{color:var(--xc);opacity:1}

.xc-sec{margin-top:16px;padding-top:13px;border-top:1px solid rgba(255,255,255,.08)}
.xc-sec h4{margin:0 0 9px;font-size:9.5px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;opacity:.42}
.xc-sec h4 em{font-style:normal;opacity:.7;letter-spacing:.06em;margin-left:6px}
/* stats as label/value rows, aligned - reads as a spec sheet, not confetti */
.xc-stats{display:grid;grid-template-columns:1fr 1fr;gap:0 22px;margin-top:2px}
.xc-stat{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  padding:6px 0;border-bottom:1px solid rgba(255,255,255,.055)}
.xc-sk{font-size:11px;letter-spacing:.04em;opacity:.5;font-weight:700}
.xc-sv{font-weight:800;color:var(--text-strong);font-size:13.5px;text-align:right}
.wh{margin:0;font-size:13px;font-weight:700;color:var(--text-strong);text-transform:capitalize}
.wh-h{margin:5px 0 0;font-size:12.4px;opacity:.66;line-height:1.5}
/* LOOT = A SIMPLE LIST. Eric 2026-08-17: "clean the fuck up out of the loot
   section. I don't want bubbles. Make it simple ... Make a simple list. We don't
   need the whole bubble system for the loot ... There's gonna be a lot of
   wording. We don't need loot levels every single time."
   So: no pills, no chips, no per-row tag. The source kind is said ONCE as a
   small heading and the rows under it are plain text - it used to be reprinted
   on all 49 lines. Nothing is truncated; the list stays complete. */
.lt-box{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  font-size:13.5px;font-weight:800;padding-bottom:9px;margin-bottom:9px;
  border-bottom:1px solid rgba(255,255,255,.08)}
.lt-picks{font-size:10.5px;font-weight:700;opacity:.55;white-space:nowrap;text-transform:uppercase;letter-spacing:.08em}
.lt{list-style:none;margin:0;padding:0}
.lt-grp{margin-top:11px}
.lt-grp:first-of-type{margin-top:3px}
.lt-gh{font-size:9.5px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;
  opacity:.42;margin-bottom:3px}
.lt-cols{columns:2;column-gap:26px}
.lt-i{font-size:12.8px;padding:2px 0;line-height:1.4;break-inside:avoid;
  text-transform:capitalize;opacity:.95;color:var(--text-strong)}
.xc-src{display:none}
@media (max-width:600px){
  /* max-width:100% keeps the card inside its tile. The base open rule sets
     max-width:none for the 600px desktop card, and on a phone the card is a
     shrink-to-fit flex item in a 128px tile - so without this its content width
     (212px for Brood Mother's loot list, MEASURED at 375px) leaked off the edge.
     There is no transform to clamp it back with here; the card is in normal flow. */
  .xp-tile.xp-open .xp-pop{position:static;width:auto;max-width:100%;transform:none;animation:none;margin-top:10px}
  /* Eric 2026-08-30: "mob cards look god awful on mobile". Root cause - the open
     card was confined to its OWN 128px grid cell (one column of the 2-up phone
     grid), so a 2-column stat sheet / 2-column loot list had to squeeze into a
     column narrower than either. Instead of shrinking the card's CONTENTS to fit
     a single cell, the OPENED TILE now spans the full grid row, so the card gets
     the whole ~300px+ row width to lay out in - same phone, an actually readable
     card. Everything else about the tile (image, name, string) is unaffected;
     only its own row spans wider while it is open. */
  .xp-tile.xp-open{grid-column:1/-1}
  .xc-stats{grid-template-columns:1fr}
  /* MEASURED on a 375px viewport: the card is a shrink-to-fit flex item, so a
     2-column loot list sets its width to 2 columns + the 26px gap - 227px inside
     a 128px tile, which pushed the card off the left edge. One column on a
     phone. */
  .lt-cols{columns:1}
}

/* ---- live countdown pushed by the game server (api/events.php) ----
   A tile that carries a real event id is TALLER and the timer sits absolutely
   at its foot, so the number appearing, changing width or disappearing when the
   server goes quiet moves nothing else on the page. Two-line names like "Black
   Market Auction" and "Random Warzone Boss" are why the extra height is real
   space rather than padding tricks - a centred flex column does not shrink. */
.xp-tile[data-eid]{height:178px;padding-bottom:28px}
.xp-timer{position:absolute;left:0;right:0;bottom:9px;z-index:1;
  font-size:12.5px;font-weight:800;letter-spacing:.06em;font-variant-numeric:tabular-nums;
  color:var(--text-strong);opacity:.86}
.xp-timer.live{color:var(--xc);opacity:1;text-shadow:0 0 12px color-mix(in srgb,var(--xc) 45%,transparent)}
.xp-livenote{margin:0 0 8px;font-size:12.5px}
@media (max-width:600px){.xp-tile[data-eid]{height:auto;min-height:168px}}
/* ---- Java / Bedrock picker on the account-link form ---- */
.mc-plat{display:flex;gap:10px;flex-wrap:wrap}
.mc-plat-opt{display:flex;align-items:center;gap:8px;cursor:pointer;
  padding:9px 14px;border-radius:12px;font-size:13.5px;font-weight:700;
  border:1px solid var(--line);background:rgba(255,255,255,.03);transition:border-color .2s ease,background .2s ease}
.mc-plat-opt:hover{border-color:color-mix(in srgb,var(--accent) 55%,transparent)}
.mc-plat-opt:has(input:checked){border-color:var(--accent);background:color-mix(in srgb,var(--accent) 16%,transparent)}
.mc-plat-opt input{accent-color:var(--accent);margin:0}

/* ---- THE LBP POPIT BUBBLE, ON A STRING ----
   Eric 2026-08-17: "think of the 3rd game and the menu you opened from your
   avatar and he held a little string ... the bubble and string are kind of
   moving like a kite type thing". So the bubble is NOT glued to the tile - it
   hangs off a short string anchored at the tile, and the whole assembly sways
   from that anchor like a kite on a line.

   CLIPPING FIX (he could not see them at all in Boss Eggs): the bubble escapes
   the tile, but .card carries overflow:hidden, so anything leaving the card was
   cut off. The explore cards now let it through - see the .card override below. */
.xp-pop{position:absolute;left:50%;bottom:calc(100% + 18px);z-index:30;
  width:max-content;max-width:220px;pointer-events:none;
  transform:translateX(-50%) translateY(10px) scale(.55);
  /* pivot at the ANCHOR (the tile end of the string), not at the bubble */
  transform-origin:50% calc(100% + 18px);
  opacity:0;transition:opacity .16s ease,transform .16s ease}
.xp-pop > span{display:block;padding:9px 15px;border-radius:999px;
  background:linear-gradient(180deg,#fffdf6,#ffe9bd);
  color:#2a1c05;font-size:12.6px;font-weight:800;line-height:1.35;
  box-shadow:0 0 0 3px #2a1c05,0 12px 26px rgba(0,0,0,.55),inset 0 2px 0 rgba(255,255,255,.9)}
/* the string. Eric 2026-08-17: "shouldnt be a straight line, itll be a curvy one
   that actually moves in the wind with the bubble, and the line string will
   follow your mouse to the side of the event icon bubble".

   So it is an SVG path, not a div: its `d` is rewritten every frame by
   mountLbpStrings() in site.js. The BOTTOM end stays pinned to the tile, the TOP
   end slides toward the pointer (clamped well inside the bubble), and the two
   control points breathe on two out-of-phase sines so the slack ripples like a
   kite line instead of pivoting like a rod. The knot is a real <circle>. */
.xp-str{position:absolute;left:50%;top:100%;width:96px;height:20px;
  transform:translateX(-50%);overflow:visible;pointer-events:none}
/* BLACK, and built like real twine rather than drawn as a line (Eric 2026-08-17:
   "Make it BLACK with a little more of the actual HIGH DEFINITION little big
   planet 3 String they had"). All four paths carry the SAME `d`; the look comes
   from stacking them. #07070a rather than #000 so the highlight has something to
   sit against - a pure-black cord with a grey edge reads as a mistake. */
.xp-str path{fill:none;stroke-linecap:round}
.xp-str .s-sh{stroke:rgba(0,0,0,.55);stroke-width:4.6;transform:translate(.7px,.7px);filter:blur(1.1px)}
.xp-str .s-core{stroke:#07070a;stroke-width:2.7}
/* the twist. Short dashes across a slightly lighter grey = two strands winding.
   Round caps keep each tick soft instead of a row of tally marks. */
.xp-str .s-braid{stroke:#33333c;stroke-width:1.5;stroke-dasharray:1.5 2.3;opacity:.9}
/* the fibre catching light, half a pixel off the core's left edge */
.xp-str .s-lit{stroke:#6b6b78;stroke-width:.7;opacity:.5;transform:translateX(-.55px)}
.xp-str .s-knot{fill:#07070a}
.xp-str .s-tip{fill:#07070a;stroke:#33333c;stroke-width:.7}
/* NO hover bubble. Eric: "I hate the bubbly pop ups showing up under my mouse".
   The little info bubble used to appear and follow the cursor on hover; it does
   NOT any more. Nothing shows until you CLICK a tile, and then it is the full
   card (.xp-open) - never a bubble under the mouse. */
.xp-str{opacity:0;transition:opacity .18s ease}
.xp-tile.xp-open .xp-str{opacity:1}
@keyframes xp-kite{
  0%   {rotate:-2.6deg}
  35%  {rotate: 1.8deg}
  60%  {rotate:-1.2deg}
  100% {rotate:-2.6deg}
}
.xp-tile.xp-open .xp-pop{animation:xp-kite 3.4s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){
  .xp-tile.xp-open .xp-pop{animation:none}
}

@media (hover:none){
  .xp-str{display:none}
  .xp-tile{height:auto;min-height:150px}
  /* MEASURED on a 392px viewport: the base rule's `width:max-content` made this
     409px wide - wider than the phone. Static flow needs an auto width, not a
     content-driven one, and max-width:none was letting it run. This is the
     CLOSED-state small caption only now (see the .xp-tile.xp-open override
     below) - it stays tiny and in normal flow, harmless. */
  .xp-pop{position:static;opacity:1;transform:none;width:auto;max-width:100%;margin-top:8px}
  .xp-pop > span{background:none;color:var(--text);box-shadow:none;padding:0;font-weight:500;font-size:12.6px;
    overflow-wrap:anywhere}
  .xp-pop::after{display:none}
  /* Eric 2026-08-31: "opening the cards on the explore page DOES NOT adjust
     your screen ... the pop ups should fit within your screen and not have to
     readjust" - a static, in-flow card pushed every tile below it down the
     page the moment it opened, which IS a readjustment even with no explicit
     scroll call. Fixed + centred instead: the card becomes a self-contained
     overlay clamped to the viewport, so opening or closing it moves nothing
     else on the page, ever - same intent as the desktop fix in fitAbove()
     (site.js). Targets `.xp-pop-detached` (a plain class, toggled by
     site.js's open()/close()), NOT a `.xp-tile.xp-open .xp-pop` descendant
     selector - open() reparents the popup onto <body> so that `position:fixed`
     centres against the real VIEWPORT rather than the `.card` ancestor
     (backdrop-filter on `.card` makes IT the containing block for a fixed
     descendant otherwise - a real Chromium quirk, not a guess, see the
     comment in site.js's open()). Once reparented it is no longer a
     descendant of the tile at all, so only a standalone class still matches
     it. */
  .xp-pop.xp-pop-detached{
    position:fixed;top:50%;left:50%;bottom:auto;margin-top:0;
    transform:translate(-50%,-50%);
    width:min(92vw,420px);max-width:92vw;
    opacity:1;pointer-events:auto;
    animation:none;z-index:600;
  }
  .xp-pop.xp-pop-detached > span{display:none}
  /* Eric 2026-09-02: a clean X, top-right, on any card opened on a phone.
     44x44 is the real tappable box (Apple/Google minimum) while the glyph
     itself stays 16px, so it is easy to hit without looking heavy. Absolute
     inside the fixed card, and the card gets matching top-right padding so no
     content can ever slide underneath it - "don't make it interfere". */
  /* The pop itself is TRANSPARENT - the opaque surface is the .xp-cardbody
     inside it. Padding the pop pushed that surface down and left a 44px
     see-through strip behind the X (page content showed through it - a real
     glitch, caught on a 375px screenshot). Pad the OPAQUE BODY instead, so the
     X always sits on solid card and nothing shows through. MEASURED: body
     padding-top 20 -> 54 clears the 44px button with 0 content overlaps. */
  .xp-pop.xp-pop-detached .xp-cardbody{padding-top:54px}
  .xp-pop.xp-pop-detached .xp-x{
    /* 10px, not 4px: MEASURED a 6px gap between the transparent pop's top and
       the opaque .xp-cardbody, so a 4px inset left the button floating 2px proud
       of the card. At 10px it sits fully on solid surface, clear of the 20px
       corner radius, with 0 content overlaps. */
    position:absolute;top:10px;right:10px;
    width:44px;height:44px;display:flex;align-items:center;justify-content:center;
    background:rgba(20,16,30,.72);border:1px solid var(--accent-line);
    border-radius:12px;color:var(--text);cursor:pointer;padding:0;
    -webkit-tap-highlight-color:transparent;z-index:2;
    transition:background .18s ease,border-color .18s ease}
  .xp-pop.xp-pop-detached .xp-x:active{background:var(--accent-soft);border-color:var(--accent-on-dark)}
  /* the tile itself no longer needs to widen for the card's sake - the card
     is detached from the grid now, so the row-span hack from the max-width:
     600px breakpoint below would only reflow neighbouring tiles for nothing */
  .xp-tile.xp-open{grid-column:auto}
}

/* MEASURED on a 392px viewport: the card leaves ~305px, and auto-fill needs
   2*150+14=314 to seat two columns - so every tile fell to its own row and the
   30-mob section became a very long single file. A smaller minimum and a
   tighter gap fit two columns in the same 305px (2*128+10=266). */
@media (max-width:560px){
  .xp-grid{grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:10px}
  .xp-tile{padding:10px}
  .xp-pic{width:64px;height:74px}
}

/* ==========================================================================
   MOBILE FIT - stop the phone needing a pinch-zoom
   Eric 2026-09-02: "when I go on my resources page, I have to zoom in and out
   just to kinda get stuff fitted properly. It should be smart in the sense of
   fitting for mobile ... on mobile it could auto adjust and be proper."

   MEASURED on a real 375px viewport, the chain that ate the width:
       375 viewport
       -32  .container padding (16 + 16)
       - 2  .card border
       -36  .card-body padding (18 + 18)
       =305 left for .xp-grid  -> two columns of 147.5px, captions 126px wide

   70px - nearly a fifth of the screen - was going to nested padding, so every
   tile, table and caption was squeezed for no benefit. On a 1100px desktop that
   padding is a rounding error; on a phone it is the whole problem.

   The column COUNT is deliberately left alone. The comment above records why
   two columns were chosen (one column turned the 30-mob section into "a very
   long single file"), and that decision still stands - he asked for the page to
   FIT, not to become one column. Widening the columns is what he asked for.

       375 - 20 - 2 - 20 = 333  ->  two columns of 161.5px (+14px each),
                                    captions ~140px instead of 126px. */
@media (max-width:560px){
  .container{padding:0 10px}
  .card-body{padding:10px}
}

/* The caption under each tile was 12.6px in a 126px-wide box - a very narrow
   measure at a size below the ~13px floor where phone text stays comfortable,
   which is exactly what makes you zoom in to read it. Raised here rather than
   in the (hover:none) block so it is scoped to phone WIDTH, not merely to a
   touch device - a touch laptop keeps the small caption it was designed with. */
@media (max-width:560px){
  .xp-pop > span{font-size:14px;line-height:1.45}
}

/* ==========================================================================
   ARTEMIS BANNER - THE 3D STAGE  (empty-news slot)
   Eric 2026-08-17: "get rid of the Posts purple/black bubble background - just
   have the page background", "more stretched out", "MORE 4k HD", "maybe try
   bringing in a 3d experience on our site from the banner".

   So there is NO card here: no panel, no fill, no border. The banner sits on
   the page background and is built as a real CSS 3D scene - the artwork lies on
   a plane, depth layers sit in front of and behind it, and site.js tilts the
   whole thing toward the pointer. Everything animates by transform only.
   ========================================================================== */
.ab-stage-host{background:none;border:0;padding:0}
.ab-stage{position:relative;display:flex;flex-direction:column;align-items:center;gap:16px;
  padding:10px 0 18px;perspective:1200px;perspective-origin:50% 50%}
.ab-scene{position:relative;width:100%;transform-style:preserve-3d;
  transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))}

/* the artwork plane - stretched WIDE, which is what he asked for */
/* MEASURED FIX 2026-08-17 (Eric: "wayy out of format and too large").
   TWO causes, both measured, not guessed:
   1. it was min(1600px,96vw) = 1194px while the site content column is 1100px;
   2. PERSPECTIVE MAGNIFICATION - a plate on translateZ(40px) under
      perspective:1200px renders 1200/(1200-40) = 1.0345x its layout width, so
      even at exactly 1100px it still drew 1138px and overhung the grid.
   So the layout width is pre-divided by that scale: --ab-target is what you
   want to SEE, and the element is sized smaller so the 3D push lands on it.
   Target 920px keeps it a banner rather than a billboard (it was 31% of the
   viewport height). The art's 7.8:1 aspect was always correct. */
.ab-plate{position:relative;
  --ab-target:min(920px,88vw);
  --ab-zscale:1.0345;                       /* 1200 / (1200 - 40) */
  width:calc(var(--ab-target) / 1.0345);margin:0 auto;
  transform:translateZ(40px) translate3d(var(--px,0),var(--py,0),0);
  border-radius:14px;overflow:hidden;
  /* Eric 2026-08-17: "I dont want it to have anything for background other
     then the site." So NO violet bloom here - only a neutral drop shadow so
     the plane still reads as lifted. */
  box-shadow:0 26px 70px rgba(0,0,0,.55)}
.ab-media{display:block;width:100%;height:auto}
/* The HD video is rendered at 1872x240 NATIVE - it is not an upscale, so it
   wants normal smooth sampling. Only the 468x60 GIF fallback needs
   nearest-neighbour, or it turns to mush at this width. */
.ab-fallback{display:none;image-rendering:pixelated}

/* a light sweep travelling with the pointer, so the plane reads as a surface */
.ab-sheen{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.10) 50%,transparent 62%);
  background-size:220% 100%;background-position:var(--sx,50%) 0}

/* floor reflection under the plane - 3D depth without a second copy of the art */
.ab-reflect{position:absolute;left:50%;top:100%;width:calc(min(920px,88vw) / 1.0345);height:52px;
  transform:translateX(-50%) translateZ(20px) rotateX(74deg);transform-origin:top center;
  /* neutral shadow, not a violet wash - it is a floor shadow, not a colour */
  background:linear-gradient(180deg,rgba(0,0,0,.45),transparent 72%);
  filter:blur(16px);pointer-events:none}

/* parallax depth: a far starfield behind, a soft haze in front */
.ab-layer{position:absolute;inset:-14% -6%;pointer-events:none;border-radius:20px}
.ab-far{transform:translateZ(-140px) translate3d(calc(var(--px,0) * -1.6),calc(var(--py,0) * -1.6),0);
  background-image:radial-gradient(1.7px 1.7px at 12% 28%,#fff,transparent),
    radial-gradient(1.3px 1.3px at 63% 16%,#dbe7ff,transparent),
    radial-gradient(2px 2px at 84% 61%,#fff,transparent),
    radial-gradient(1.2px 1.2px at 33% 74%,#cddcff,transparent),
    radial-gradient(1.5px 1.5px at 47% 44%,#fff,transparent),
    radial-gradient(1.2px 1.2px at 74% 33%,#eaf1ff,transparent);
  opacity:.55}
/* The near layer used to paint a violet vignette around the banner - THAT is
   the purple Eric saw swinging around as the scene tilted. It now carries no
   paint at all; it stays only as an empty parallax plane so the depth maths is
   unchanged. Do not put a colour back in here. */
.ab-near{transform:translateZ(120px) translate3d(calc(var(--px,0) * 1.5),calc(var(--py,0) * 1.5),0);
  background:none}

.ab-cta{position:relative;z-index:2;transform:translateZ(60px)}

@media (max-width:700px){
  .ab-stage{perspective:none;padding:16px 0 22px}
  .ab-scene{transform:none}
  .ab-plate{transform:none;width:100%}
  .ab-reflect,.ab-layer{display:none}
}
@media (prefers-reduced-motion:reduce){
  .ab-scene,.ab-plate,.ab-layer{transform:none}
  .ab-sheen{display:none}
}

/* ---- mob head portraits + the Upside Down flip (Eric 2026-08-17) ---- */
.xp-head{width:48px;height:48px;image-rendering:pixelated;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.55))}
/* the four mobs listed in upsidedown.yml hang the way they arrive */
.xp-pic.xp-flip .xp-head{transform:rotate(180deg)}
.xp-tile:hover .xp-pic.xp-flip{animation:xp-bob-flip 1.6s ease-in-out infinite}
@keyframes xp-bob-flip{0%,100%{transform:translateY(0)}50%{transform:translateY(5px)}}

/* ---- profile dropdown: game-format rank + language row ---- */
.nav-menu .who{padding:11px 16px 9px}
.game-rank{font-weight:800;font-size:13.2px;letter-spacing:.02em}
.game-rank i{font-style:normal;color:#fff}
/* ONE seamless segmented control with a sliding thumb - Eric called the old
   pair of boxed buttons "gradeschool ai bullshit". The thumb travels via --i so
   the motion is continuous, not two things blinking. */
.lang-row{display:flex;align-items:center;gap:10px;padding:9px 16px 12px}
.lang-l{font-size:12.4px;opacity:.55;margin-right:auto}
.lang-seg{--n:2;position:relative;display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  background:rgba(255,255,255,.045);border-radius:999px;padding:3px;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.06)}
.lang-thumb{position:absolute;top:3px;bottom:3px;left:3px;width:calc((100% - 6px) / var(--n));
  border-radius:999px;background:var(--accent);box-shadow:0 3px 10px rgba(124,0,255,.45);
  transform:translateX(calc(var(--i,0) * 100%));
  transition:transform .34s cubic-bezier(.34,1.4,.5,1)}
.lang-o{position:relative;z-index:1;background:none;border:0;cursor:pointer;font:inherit;
  font-size:11.4px;font-weight:800;letter-spacing:.08em;padding:4px 13px;border-radius:999px;
  color:var(--text);transition:color .24s ease}
.lang-o.on{color:var(--accent-text)}
.lang-o:not(.on):hover{color:var(--text-strong)}
body.lang-swap{transition:opacity .24s ease;opacity:.35}

/* a panel nav row that leaves the panel (the site editor) */
.panel-nav .panel-leave{display:flex;align-items:center;gap:11px;width:100%;
  padding:11px 14px;border-radius:10px;color:var(--text);font-size:14.4px;
  font-weight:600;text-decoration:none;transition:background .18s ease,color .18s ease}
.panel-nav .panel-leave:hover{background:var(--accent-soft);color:var(--text-strong)}

/* ---- vote memory (Eric: a link already voted shows no CLICK ME) ---- */
.vbub.voted{opacity:.62}
.vbub.voted .vdone{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) scale(.9);
  font-size:12px;font-weight:900;letter-spacing:.1em;text-transform:uppercase;color:#fff;
  background:rgba(0,0,0,.55);padding:5px 12px;border-radius:999px;
  opacity:0;transition:opacity .2s ease,transform .3s cubic-bezier(.34,1.56,.64,1);pointer-events:none}
.vbub.voted:hover .vdone{opacity:1;transform:translate(-50%,-50%) scale(1)}
.vbub.voted:hover{filter:none}
.vcard.voted{opacity:.66}
.vcard.voted .go{background:rgba(255,255,255,.12);color:var(--text)}

/* The explore cards must NOT clip - the popit bubble hangs above its tile and
   .card is overflow:hidden by default, which is why the Boss Eggs bubbles were
   invisible entirely. Scoped to cards that actually contain a tile grid. */
.card:has(.xp-grid){overflow:visible}
.card:has(.xp-grid) .card-body{overflow:visible}
/* headroom so a top-row bubble has somewhere to go */
.card:has(.xp-grid) .xp-grid{padding-top:34px}

/* ---- menu header editor (Panel > Site) ---- */
.navrow{display:grid;grid-template-columns:1.1fr 1.3fr .7fr auto auto;gap:8px;align-items:center;margin-bottom:8px}
.navrow input[type=text],.navrow input:not([type]){width:100%}
.navchk{margin:0;white-space:nowrap;font-size:12.5px}
@media (max-width:760px){.navrow{grid-template-columns:1fr 1fr;}}


/* ==========================================================================
   POST EDITOR - rich text (Eric 2026-08-31: "like a Word document, but for
   editing the post. Microsoft Word quality.")
   ========================================================================== */
.rt-bar{display:flex;flex-wrap:wrap;align-items:center;gap:5px;padding:7px 8px;
  background:var(--pane-2,rgba(255,255,255,.03));border:1px solid var(--line);
  border-bottom:0;border-radius:10px 10px 0 0}
.rt-bar button{appearance:none;border:1px solid transparent;background:transparent;
  color:var(--text);font:inherit;font-size:13px;line-height:1;padding:6px 9px;
  border-radius:7px;cursor:pointer;min-width:30px}
.rt-bar button:hover{background:var(--accent-soft);border-color:var(--accent-line);color:var(--text-strong)}
.rt-bar button:active{transform:translateY(1px)}
.rt-sep{width:1px;align-self:stretch;background:var(--line);margin:0 3px}
.rt-col{width:22px;height:22px;min-width:22px;padding:0;border-radius:50%;
  border:1px solid rgba(255,255,255,.28)}
.rt-col:hover{outline:2px solid var(--accent-line);outline-offset:1px}
.rt-none{background:transparent;color:var(--text);font-weight:800}
.rt-edit{min-height:150px;padding:14px 16px;background:var(--card-2);
  border:1px solid var(--line);border-radius:0 0 10px 10px;color:var(--text);
  font-size:15px;line-height:1.7;outline:none;overflow-wrap:break-word}
.rt-edit:focus{border-color:var(--accent-line)}
.rt-edit:empty::before{content:attr(data-placeholder);opacity:.45}
.rt-edit p{margin:0 0 12px}
.rt-edit ul,.rt-edit ol{margin:0 0 12px;padding-left:22px}
.rt-edit a{color:var(--accent-on-dark)}

/* the post text colours. ONE definition, used by the editor preview and by the published post,
   so what he picks is what readers get. Values are the site's own :root tokens. */
.tc-violet{color:var(--accent-on-dark)}
.tc-bright{color:var(--accent-bright)}
.tc-deep{color:var(--accent)}
.tc-glow{color:var(--ip-glow)}
.tc-white{color:var(--text-strong)}
.tc-muted{color:var(--text)}

/* post page footer row - Back to news sits beside Edit post for staff */
.post-foot{display:flex;gap:10px;align-items:center;margin-top:22px}
.post-foot .post-back{margin-top:0}


/* ==========================================================================
   PANEL > ANALYTICS (owner only) - Eric 2026-08-31, "mainly for SEO"
   ========================================================================== */
.an-sec{margin-bottom:26px}
.an-sec>h4{margin:0 0 12px;font-size:11px;font-weight:800;letter-spacing:.18em;
  text-transform:uppercase;opacity:.5}
.an-tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:12px}
.an-table{width:100%;border-collapse:collapse;font-size:13.5px;min-width:620px}
.an-table th{text-align:left;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--text);opacity:.55;padding:11px 12px;font-weight:800;white-space:nowrap}
.an-table td{padding:11px 12px;border-top:1px solid var(--line);vertical-align:top}
.an-table .sub2{font-size:12px;opacity:.55;margin-top:2px}
.an-ok{color:#7fe0a0;font-weight:700}
.an-no{color:#ff8b8b;font-weight:700}
.an-urls{list-style:none;margin:12px 0 0;padding:0;display:grid;gap:5px}
.an-urls a{font-size:13px;color:var(--accent-on-dark);word-break:break-all}
.an-pre{margin:12px 0 0;padding:13px 15px;background:rgba(0,0,0,.34);border-radius:10px;
  font-size:12.6px;line-height:1.6;color:var(--text);overflow-x:auto;white-space:pre-wrap}
.an-link{display:flex;align-items:center;gap:12px;padding:11px 14px;margin-bottom:8px;
  background:var(--pane-2,rgba(255,255,255,.03));border:1px solid var(--line);border-radius:12px}
.an-lmain{flex:1;min-width:0}
.an-link a{color:var(--text-strong);font-weight:700;text-decoration:none}
.an-link a:hover{color:var(--accent-on-dark)}
.an-link .badge{flex:none}


/* Analytics v2 - one card per page, scannable. Eric: "clean organized CRM type setup". */
/* Traffic tiles - Eric 2026-09-01 "site views, page views, page statistics".
   Same card surface as the rest of the analytics panel; no new palette. */
.an-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:10px;margin-bottom:12px}
.an-tile{padding:14px 16px;border:1px solid var(--line);border-radius:12px;background:rgba(0,0,0,.18)}
.an-tile-n{font-size:26px;font-weight:900;line-height:1.1;letter-spacing:-.02em;color:var(--text-strong)}
.an-tile-l{font-size:10.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;opacity:.6;margin-top:4px}
.an-tile-s{margin-top:3px}
.an-hero{display:flex;align-items:center;gap:20px;padding:20px 22px;margin-bottom:26px;
  background:var(--card);border:1px solid var(--accent-line);border-radius:var(--bubble)}
.an-herobig{font-size:44px;font-weight:900;line-height:1;letter-spacing:-.02em}
.an-herolabel{font-size:11px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;opacity:.55}
.an-worst{margin-top:8px;font-size:13px;color:#ffb27f}
.an-worst.good{color:#7fe0a0}
.an-page{border:1px solid var(--line);border-radius:14px;margin-bottom:12px;overflow:hidden}
.an-ptop{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--pane-2,rgba(255,255,255,.03))}
.an-ptop strong{color:var(--text-strong);font-size:15px}
.an-ptop .sub2{flex:1;font-size:12.5px;opacity:.5}
.an-score{font-weight:900;font-size:14px;padding:3px 10px;border-radius:999px}
.an-score.good{color:#7fe0a0;background:rgba(127,224,160,.12)}
.an-score.mid{color:#ffcf7f;background:rgba(255,207,127,.12)}
.an-score.bad{color:#ff8b8b;background:rgba(255,139,139,.12)}
.an-herobig.good{color:#7fe0a0}.an-herobig.mid{color:#ffcf7f}.an-herobig.bad{color:#ff8b8b}
.an-checks{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:1px;
  background:var(--line)}
.an-chk{display:flex;justify-content:space-between;gap:10px;padding:9px 14px;
  background:var(--card);font-size:13px}
.an-chk .ck{opacity:.62}
.an-chk .cv{font-weight:700;text-align:right}
.an-chk.ok .cv{color:#7fe0a0}
.an-chk.warn .cv{color:#ffcf7f}
.an-chk.no .cv{color:#ff8b8b}

/* ---------- home: gap between the header and the first post ----------
   Eric asked for this twice and both times I "fixed" the POST page, which
   already had 178px of air. The home page is where the two actually touch:
   .hero-wrap ends at 184px and .post-card starts at 184px - a measured 0px
   gap, so the first news card butts straight into the header.
   30px is not a number I picked: it is the same margin post.html already
   uses for exactly this relationship (.post{margin:30px auto 0}). */
/* 2026-09-01 second pass: raised 30 -> 56 so the header/first-post gap is the SAME rhythm as the
   new gap between sections. At 30 it was the odd one out on the page - every section boundary
   below it had 56px of room and the header had half that. */
body[data-page="home"] main.section{padding-top:var(--section-gap)}

/* ---------- particle animation ----------
   Eric 2026-08-31: "Not one particle has a moving gif." Now they genuinely move.
   The image is a horizontal strip of N real frames from the client jar, each --pcell wide.
   steps(N) advances exactly one frame at a time and the final background-position is
   -(N * cell), so the last step lands on the last frame and loops cleanly.
   Duration scales with the frame count (~80ms a frame) instead of being fixed, so a 26-frame
   enchantment table plays at the same PACE as a 4-frame splash rather than 6x faster. */
.pstrip{
  width:var(--pcell,64px);height:var(--pcell,64px);
  background-repeat:no-repeat;background-position:0 0;
  /* the strip is scaled off --pcell too, so the frame width and the step offset can never
     disagree - that is what makes one --pcell change resize the whole thing safely */
  background-size:calc(var(--pframes,1) * var(--pcell,64px)) var(--pcell,64px);
  image-rendering:pixelated;image-rendering:crisp-edges;
  animation:pstrip calc(var(--pframes,1) * 80ms) steps(var(--pframes,1)) infinite;
}
@keyframes pstrip{ to{ background-position:calc(var(--pframes,1) * var(--pcell,64px) * -1) 0 } }
/* the detail card exists so he can "see it way more clearly" - same art, twice the size */
.res-bigart-anim{--pcell:128px;margin:0 auto}
/* His reduced-motion preference stops it on frame 1 - the art is still there, it just holds. */
html.pref-no-motion .pstrip{animation:none}
@media (prefers-reduced-motion:reduce){ .pstrip{animation:none} }

/* ---------- custom enchant rarity chip (Resources > Enchantments) ----------
   The rarity is the whole point of the list, so it reads as a chip next to the
   name in that rarity's own colour rather than as another grey detail line.
   Colours come from EliteEnchantments' groups.yml, not from a guess. */
.ench-rar{display:inline-block;font-size:10.5px;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;padding:2px 7px;margin-left:9px;border-radius:999px;
  border:1px solid currentColor;opacity:.92;vertical-align:2px}

/* ==========================================================================
   FAQ PAGE
   Search first, because 78 questions is too many to scroll: the box is the
   widest thing on the page and the category chips sit under it as a coarse
   filter. Answers are <details> so the page opens as a scannable list of
   questions rather than a wall.

   EVERY class here is `faqp-` (FAQ page), NOT `faq-`. Eric 2026-09-01: "FAQs are not loading
   or working." The Support page has had its own FAQ accordion since long before this page
   existed, built on .faq-item / .faq-q / .faq-a (see the top of this file). Naming this page's
   blocks .faq-item and .faq-a meant MY rules - being later in the file - repainted his Support
   accordion as flat black boxes with the wrong padding. Same-named classes for two different
   components was the whole bug.
   ========================================================================== */
.faqp-top{margin-bottom:26px}
.faqp-search{position:relative;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.faqp-search input{flex:1 1 320px;min-width:0;padding:15px 18px;font:inherit;font-size:16.5px;
  color:var(--text-strong);background:var(--pane-2);border:1px solid var(--accent-line);
  border-radius:var(--bubble);outline:none;transition:border-color .18s,box-shadow .18s}
.faqp-search input:focus{border-color:var(--accent);box-shadow:0 0 0 4px var(--accent-soft)}
.faqp-search input::placeholder{color:rgba(255,255,255,.42)}
.faqp-count{white-space:nowrap}
.faqp-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}

.faqp-cat{margin:0 0 30px}
.faqp-cat-h{display:flex;align-items:center;gap:10px;margin:0 0 12px;font-size:14px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--accent-on-dark)}
.faqp-cat-n{font-size:11.5px;font-weight:800;letter-spacing:.06em;padding:2px 8px;
  border-radius:999px;background:var(--accent-soft);color:var(--accent-on-dark)}

.faqp-item{background:var(--card);border:1px solid var(--accent-line);
  border-radius:var(--bubble);margin-bottom:10px;overflow:hidden}
.faqp-item summary{cursor:pointer;list-style:none;padding:16px 20px;font-weight:600;
  font-size:16.5px;color:var(--text-strong);display:flex;align-items:center;gap:12px;
  transition:background .16s}
.faqp-item summary::-webkit-details-marker{display:none}
/* the chevron is drawn, not an icon font, so it rotates with the open state */
.faqp-item summary::after{content:"";margin-left:auto;flex:0 0 auto;width:8px;height:8px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;opacity:.55;
  transform:rotate(45deg) translate(-2px,-2px);transition:transform .18s}
.faqp-item[open] summary::after{transform:rotate(225deg) translate(-2px,-2px)}
.faqp-item summary:hover{background:var(--accent-soft)}
.faqp-a{padding:0 20px 18px;color:var(--text);line-height:1.68}
.faqp-a p{margin:0}
.faqp-cmds{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.faqp-cmd{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;
  padding:6px 11px;border-radius:8px;cursor:pointer;color:var(--accent-on-dark);
  background:var(--accent-soft);border:1px solid var(--accent-line);transition:all .16s}
.faqp-cmd:hover{background:var(--accent);color:var(--accent-text);border-color:var(--accent)}

@media (max-width:640px){
  .faqp-search input{flex:1 1 100%;font-size:16px}
  .faqp-item summary{font-size:15.5px;padding:14px 16px}
  .faqp-a{padding:0 16px 16px}
}

/* ---------- screen-reader-only heading ----------
   Every page was shipping with NO <h1>, which the Analytics SEO audit flags and which crawlers
   treat as weak structure. The hero is a brandmark SVG and two info bubbles by design, and Eric's
   #1 complaint is invented visual changes - so the heading is added semantically and clipped out
   of the layout entirely. It moves zero pixels; screen readers and crawlers still read it.
   Not `display:none` and not `visibility:hidden` - both of those hide it from assistive tech and
   from some crawlers too, which would defeat the point. */
.sr-only{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- Support > FAQ: link out to the full FAQ page ----------
   Eric 2026-09-01: the FAQ page is reached from the FAQ section, not from a nav tab. It sits
   under the last question as a plain link, in the site's accent, with the arrow nudging on
   hover - the same quiet treatment the rest of the page uses for "there is more this way". */
.faq-more{display:inline-flex;align-items:center;gap:8px;margin-top:16px;
  font-size:15px;font-weight:700;color:var(--accent-on-dark);text-decoration:none}
.faq-more span{transition:transform .18s ease}
.faq-more:hover{color:#fff}
.faq-more:hover span{transform:translateX(4px)}

/* ---------- header: centre the nav on the page, not on the leftover space ----------
   Eric 2026-09-01: "when I look on my desktop, the header looks off center compared to the
   ArtemisPvP Factions logo thing. Let's get the whole website centered."
   MEASURED at 1440px before the fix: the logo, the hero row and every content container all
   centred on 720, but .nav-list centred on 671 - 49px left. `.navbar .container` is a flex row of
   [Discord] [nav] [auth], and `margin:0 auto` only centres the nav inside whatever space the two
   side groups leave. Those groups are different widths (and the right one changes width again
   once you are logged in), so the middle was never on the page's centre line.
   Giving both side groups an equal flex share makes the middle column land on the true centre and
   stay there whether you are logged in or out. */
.navbar .container > .nav-social,
.navbar .container > .nav-auth{flex:1 1 0;min-width:0}
.navbar .container > .nav-auth{justify-content:flex-end}
.navbar .container > .nav-list{flex:0 0 auto}

/* ---------- room between sections ----------
   Eric 2026-09-01: "make sure that we have room between sections or blank space."
   MEASURED before: every card sat 20px from the next, which reads as one continuous slab rather
   than separate sections. 34px is not a number picked out of the air - it is the hero's own
   bottom padding, the site's existing "this block has ended" spacing.
   The compact display preference still overrides this (html.pref-compact .card above). */
/* 2026-09-01, second pass. He asked for the spacing again after the 34px landed, so 34 was not
   enough room: on this site a "section" is a .card, and the tall ones run 500-850px, against which
   34px reads as a seam rather than a gap. 56px is the same step up 34 was from 20, and it is the
   hero padding doubled less the border - one rhythm, not a new arbitrary number.
   MEASURED on the live explore page: gaps went 34 -> 56 across all 7 section boundaries. */
.card{margin-bottom:var(--section-gap)}
.xp-sec + .xp-sec{margin-top:var(--section-gap)}

/* The last section used to butt straight into the footer: main.section had padding-bottom 0, so
   the bottom of the page had no room at all while every gap above it did. */
main.section{padding-bottom:var(--section-gap)}
/* Compact display preference scales it down with everything else. NOTE: the neighbouring
   `html.pref-compact .main{...}` rule targets the CLASS .main, but the element on these pages is
   `<main class="section">` - so that rule has never applied here. Flagged, not "fixed": it is
   pre-existing and changing it would alter compact mode beyond what was asked. */
html.pref-compact main.section{padding-bottom:24px}

/* ---------- centre the site on tall screens ----------
   Eric 2026-09-01: "we can center the website."
   It was already centred HORIZONTALLY (.container is max-width:1100px with margin:0 auto, measured
   at x=250 w=1100 in a 1600px viewport). What was NOT centred was vertically: a short page like the
   home page ended at 881px inside a 1000px viewport, leaving 119px of bare backdrop under the
   footer so the whole site sat shoved up against the top of the screen.
   Body becomes a flex column and main takes the slack, which pins the footer to the bottom of the
   viewport on short pages and changes nothing on long ones. MEASURED after: dead space 119px -> 0.
   Safe for the fixed layers - .navbar, h1.sr-only and aside.drawer are all out of flow, and the
   backdrop layers are position:fixed, so none of them become flex items. */
body{min-height:100vh;min-height:100dvh;display:flex;flex-direction:column}
body > main{flex:1 0 auto}
body > .footer{flex:none}
