@charset "utf-8";

/* ─────────────────────────────────────────────────────────────────
   Crystaria Online — Crystal Dark Theme
   Fonts : Cinzel (headings/buttons) · IM Fell English (body)
   Palette: deep void bg · crystal-grey text · gold & amethyst accents
   ───────────────────────────────────────────────────────────────── */

/* Typography now matches the mockup's design system with SYSTEM fonts (no webfont
   load): Georgia serif (display) · system sans-serif (body) · monospace (labels +
   numbers).  The old Cinzel / IM Fell English / Exo 2 @import was removed, so every
   'Cinzel,...,serif' rule falls back to Georgia and 'Exo 2,...,sans' to the system
   sans; the MOCKUP TYPOGRAPHY block at the very end of this file finishes the map
   (body -> sans, all stat/combat labels + numbers -> tabular mono). */

/* Two-column list layout for content panels (Classes, Attunements, Titles,
   Magick, Quests, Objectives, Waystones). Wrap the item list in class="p2col".
   MUST stay AFTER the @import above — a style rule before @import makes the
   browser drop the @import and the webfonts never load. Degrades to a single
   column if this sheet hasn't refreshed yet, and on narrow viewports. */
.p2col{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 10px;align-items:start;}
.p2col > *{margin-bottom:0 !important;}
@media (max-width:640px){.p2col{grid-template-columns:1fr;}}

/* ───── Reset / Base ───── */
* {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
}

body {
    color: #cccccc;
    background-color: #080a16;
    background-image:
        radial-gradient(ellipse 130% 50% at 50% 0%,   #10103c 0%, transparent 65%),
        radial-gradient(ellipse 60%  30% at 85% 100%, #080f1c 0%, transparent 60%);
    font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    font-size: 88%;
    line-height: 1.55;
}

/* ───── Links ───── */
a {
    color: var(--mkgd);
    text-decoration: none;
}

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

a:active {
    color: #f0d060;
}

/* ───── Headings ───── */
h1, h2, h3, h4 {
    font-family: var(--mk-display, Georgia, serif);
    color: var(--mkgd);
    font-weight: 600;
    letter-spacing: 0.05em;
    margin: 0.4em 0 0.2em;
}

/* ───── Tables ───── */
table {
    background-color: #1a2036;   /* amethyst-dark grid lines */
    border: 1px solid var(--brd-soft);
    border-collapse: separate;
    border-spacing: 1px;
}

td {
    background-color: #0d0d0d;
    color: #cccccc;
    padding: 3px 6px;
    vertical-align: top;
}

td:hover {
    background-color: #0f0c1a;
}

/* ───── Typography ───── */
strong, b {
    color: var(--mkgd);
    font-family: var(--mk-display, Georgia, serif);
}

em, i {
    color: #9090a8;
    font-style: italic;
}

u {
    text-decoration: underline;
}

/* Legacy <font> tags — inherit colour="" overrides */
font {
    font-family: inherit;
    font-size: inherit;
}

/* ───── Form Controls — Text / Password ───── */
input,
input[type="text"],
input[type="password"] {
    color: #aab0cc;
    background-color: #090b16;
    border: 1px solid var(--brd-soft);
    padding: 2px 6px;
    font-family: 'IM Fell English', Georgia, serif;
    font-size: 1em;
    outline: none;
    -webkit-transition: border-color 0.15s ease, box-shadow 0.15s ease;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

input:focus,
input[type="text"]:focus,
input[type="password"]:focus {
    border-color: var(--mkgd);
    -webkit-box-shadow: 0 0 5px rgba(var(--mkgd-rgb),0.35);
    box-shadow: 0 0 5px rgba(var(--mkgd-rgb),0.35);
}

/* ───── Buttons ───── */
input[type="button"],
input[type="submit"] {
    color: #cccccc;
    background-color: #0d0a18;
    background-image: -webkit-linear-gradient(top, #111834 0%, #0a0d1a 100%);
    background-image: linear-gradient(to bottom, #111834 0%, #0a0d1a 100%);
    border: 1px solid var(--mkgd);
    padding: 3px 12px;
    cursor: pointer;
    font-family: var(--mk-display, Georgia, serif);
    font-size: 0.85em;
    letter-spacing: 0.04em;
    outline: none;
    -webkit-transition: all 0.15s ease;
    transition: all 0.15s ease;
}

input[type="button"]:hover,
input[type="submit"]:hover {
    background-image: -webkit-linear-gradient(top, #1a2048 0%, #101632 100%);
    background-image: linear-gradient(to bottom, #1a2048 0%, #101632 100%);
    border-color: var(--mkg);
    color: var(--mkg);
}

input[type="button"]:active,
input[type="submit"]:active {
    background-image: -webkit-linear-gradient(top, #0a0d1a 0%, #111834 100%);
    background-image: linear-gradient(to bottom, #0a0d1a 0%, #111834 100%);
}

/* ───── Select ───── */
select {
    color: #aab0cc;
    background-color: #090b16;
    border: 1px solid var(--brd-soft);
    padding: 2px 5px;
    font-family: 'IM Fell English', Georgia, serif;
    font-size: 1em;
    outline: none;
    cursor: pointer;
    -webkit-transition: border-color 0.15s ease;
    transition: border-color 0.15s ease;
}

select:focus {
    border-color: var(--mkgd);
}

/* ───── Textarea ───── */
textarea {
    color: #aab0cc;
    background-color: #090b16;
    border: 1px solid var(--brd-soft);
    padding: 5px 7px;
    font-family: 'IM Fell English', Georgia, serif;
    font-size: 1em;
    line-height: 1.5;
    outline: none;
    resize: vertical;
    -webkit-transition: border-color 0.15s ease, box-shadow 0.15s ease;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

textarea:focus {
    border-color: var(--mkgd);
    -webkit-box-shadow: 0 0 5px rgba(var(--mkgd-rgb),0.35);
    box-shadow: 0 0 5px rgba(var(--mkgd-rgb),0.35);
}

/* ───── Main wrapper ───── */
#wrapper {
    position: absolute;
    top: 5px;
    width: 100%;
}

/* ───── Navigation bar (top of game UI) ───── */
#navBar {
    width: 100%;
    background: linear-gradient(to bottom, #110e20 0%, #0c0f20 100%);
    border-bottom: 1px solid #2a1e4a;
    padding: 4px 8px;
    text-align: center;
    box-sizing: border-box;
    line-height: 1.6;
    /* Dropdown menus need to escape the bar's vertical extent.  Without
       this the .nav-menu would be clipped behind the wrapper boundary.
       STICKY (2026-08-26, bug #60): in a small Steam window the shell is
       taller than the viewport, so the document scrolls — and the nav used
       to slide up out of reach entirely, recoverable only by minimising and
       maximising. Sticky pins it to the top of the scroll instead. It still
       establishes a containing block, so the dropdowns escape exactly as
       they did under position:relative.
       ⚠ Needs no ancestor with overflow:hidden — #wrapper is absolute with
         no clip, so the sticky context is the viewport. Check that before
         adding any overflow rule to #wrapper or body. */
    position: -webkit-sticky;
    position: sticky;
    top: 0;
    overflow: visible;
    z-index: 100;
}

/* ═══ Topbar (mockup): menu on the LEFT · location/coords + day-time-weather clock on the RIGHT ═══ */
#navBar .mk-topbar { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -ms-flex-wrap: wrap; flex-wrap: wrap; gap: 4px 14px; text-align: left; }
#navBar .mk-nav { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -ms-flex-wrap: wrap; flex-wrap: wrap; gap: 1px; -webkit-box-flex: 1; -ms-flex: 1 1 auto; flex: 1 1 auto; min-width: 0; }
#navBar .mk-topright { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 14px; white-space: nowrap; font-size: 0.78em; padding-right: 4px; }
/* .mk-loc / .mk-coord removed 2026-09-01 (report #106): the navbar no longer
   carries location, so nothing emits either class. The readout lives above
   the map (#moveMenu #locInfo) and, on mobile, in #m-header-top #locInfo. */
#navBar .mk-topclock { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 6px; font-size: 0.94em; color: #9cb8d4; letter-spacing: 0.03em; }
#navBar .mk-cdot { width: 9px; height: 9px; border-radius: 50%; background: radial-gradient(circle at 35% 30%, #ffe9a0, var(--mkg)); -webkit-box-shadow: 0 0 7px var(--mkg); box-shadow: 0 0 7px var(--mkg); -webkit-box-flex: 0; -ms-flex: 0 0 auto; flex: 0 0 auto; }
/* Zone controller (guild that owns this area) — sits just right of the location. */
#navBar .mk-zone { color: #a679d6; letter-spacing: 0.02em; }
/* Server time (UTC) — muted mono, rides at the end of the cluster. */
#navBar .mk-stime { font-family: var(--mk-mono, ui-monospace, monospace); font-size: 0.88em; color: #7a7088; letter-spacing: 0.02em; }
@media (max-width: 900px) { #navBar .mk-topright { font-size: 0.72em; gap: 10px; } }

/* ── Under-map bars retired 2026-07-10 ──────────────────────────────────────
   Location + coords + zone controller + day/time/weather + server time all moved
   into the navbar cluster; hiding these frees the vertical space for a taller map
   (maprender zoom-1 is now 11x13). Applies across all four game-frame layouts.

   ⚠ #worldClockBar STAYS HIDDEN — the navbar owns day/time/weather/server time
     and a second copy under the map would just be noise. */
#worldClockBar, #locInfo { display: none !important; }

/* ── The location readout, restored above the map (report #106, 2026-09-01) ──
   "Place Grid Location right above the Map on the Right — where one's eyes go
   to look for it naturally."  It already WAS there structurally: #locInfo is
   the last child of #moveMenu, and #moveMenu sits immediately above #mapMenu in
   the right pane. It was only hidden. So this shows it rather than moving it —
   overworld (move_dpad_helper), dungeon and raid all emit it into #moveMenu.

   ⚠⚠ SCOPED TO #moveMenu, NOT bare #locInfo. mobile.html carries a SECOND
      <span id="locInfo"> in its header that nothing has ever populated; the
      blanket rule above keeps that empty element hidden. Un-hiding #locInfo
      globally would reveal it. (Mobile's own #moveMenu lives inside the 1x1
      clipped #m-sinks box, so this rule is invisible there too — mobile keeps
      showing location in the navbar.) */
/* ── Styling lives HERE, not in the emitters (2026-09-01) ──────────────────
   The three renderers (move_dpad_helper, dungeon_game, raid_game) each carried
   their own copy of an inline style string, and they had ALREADY drifted — two
   different border-top colours. Worse, an inline style cannot follow a theme:
   the hard-coded #665580 / #1e1a2a stayed violet on all eight themes.
   ⚠ Everything below is built from tokens the themes actually override
     (--mkp2, --mkg, --mkg-rgb, --mkgd-rgb, --brd, --brd-gold), so the readout
     retints itself — red under Bloodrite, green under Wildheart, and so on.
   ⚠⚠ `display` keeps !important: the blanket `#worldClockBar, #locInfo
      { display:none !important }` above hides it otherwise, and specificity
      alone loses to !important. */
#moveMenu #locInfo {
  display: block !important;
  margin-top: 6px;
  padding: 5px 8px;
  text-align: center;
  font-size: 0.78em;
  letter-spacing: 0.03em;
  font-family: var(--mk-mono, ui-monospace, monospace);
  color: var(--mkg);
  background:
    linear-gradient(180deg, rgba(var(--mkg-rgb), .13), rgba(var(--mkgd-rgb), .05)),
    var(--mkp2);
  border: 1px solid var(--brd);
  border-top-color: var(--brd-gold);
  border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(var(--mkg-rgb), .10);
}

/* ═══ Arena foe list (mockup): sprite · name/Lv/HP · engage button, per-threat left border ═══ */
.mkfoes { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; gap: 8px; }
/* Hide the tile enemy strip while a fight runs — fightenemy/attackenemy toggle
   body.in-combat; setgame removes it on any tile render (move/flee/revive). */
body.in-combat .mkfoes { display: none; }
.mkfoe { position: relative; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 11px; padding: 9px 11px 9px 13px; background: linear-gradient(180deg,#151125,#100c1a); border: 1px solid var(--brd); border-left: 3px solid #5aa06a; }
.mkfoe.rare { border-left-color: #e0a13a; -webkit-box-shadow: 0 0 13px rgba(224,161,58,.16), inset 0 0 0 1px rgba(224,161,58,.14); box-shadow: 0 0 13px rgba(224,161,58,.16), inset 0 0 0 1px rgba(224,161,58,.14); }
.mkfoe.nem { border-left-color: #e0564a; -webkit-box-shadow: 0 0 13px rgba(224,86,74,.16), inset 0 0 0 1px rgba(224,86,74,.14); box-shadow: 0 0 13px rgba(224,86,74,.16), inset 0 0 0 1px rgba(224,86,74,.14); }
.mkfoe .fspr { width: 46px; height: 46px; -o-object-fit: contain; object-fit: contain; -webkit-box-flex: 0; -ms-flex: 0 0 auto; flex: 0 0 auto; -webkit-filter: drop-shadow(0 2px 3px rgba(0,0,0,.6)); filter: drop-shadow(0 2px 3px rgba(0,0,0,.6)); background: #000; border: 1px solid var(--brd); }
.mkfoe .fi { -webkit-box-flex: 1; -ms-flex: 1; flex: 1; min-width: 0; }
.mkfoe .fn { font-family: var(--mk-display,Georgia,serif); color: #eaf3fb; font-size: 1.02em; letter-spacing: .02em; }
.mkfoe .fn.gold { color: var(--mkg); } .mkfoe .fn.red { color: #ff6f5c; }
.mkfoe .fn .fl { font-family: var(--mk-mono,ui-monospace,monospace); font-size: .68em; color: #7fa0c0; letter-spacing: .06em; margin-left: 6px; }
.mkfoe .sub2 { font-size: .72em; color: #cc9a86; margin: 2px 0 1px; font-style: italic; }
.mkfoe .fhp { position: relative; height: 7px; background: #070c16; -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd); margin: 5px 0 3px; -webkit-clip-path: polygon(3px 0,100% 0,calc(100% - 3px) 100%,0 100%); clip-path: polygon(3px 0,100% 0,calc(100% - 3px) 100%,0 100%); }
.mkfoe .fhp i { display: block; height: 100%; background: linear-gradient(90deg,#5a1810,#e0563a 80%,#ff8a5a); }
.mkfoe.rare .fhp i { background: linear-gradient(90deg,#5a3010,#ff8833 80%,#ffbb66); }
.mkfoe .fhpn { font-family: var(--mk-mono,ui-monospace,monospace); font-size: .72em; color: #7fa0c0; }
.mkfoe .badge2 { position: absolute; top: -8px; left: 12px; font-family: var(--mk-mono,ui-monospace,monospace); font-size: .62em; letter-spacing: .1em; text-transform: uppercase; padding: 1px 7px; background: #0a1424; color: #a0c0dc; border: 1px solid var(--brd); }
.mkfoe.rare .badge2 { color: #e8b45a; border-color: #7a5a1a; }
.mkfoe .badge2.nb { color: #ff6f5c; border-color: #7a2a24; }

/* ═══ Mockup button — uppercase gold, bordered, dark, hover glow (arena + reused globally) ═══ */
/* Canonical game button. Colour is driven by three custom props so any button
   can be tinted to what it does (gold=action, blue=talk, green=guild/confirm,
   purple=party/mystic, red=danger…): text, border and hover-glow all follow --btn. */
.mkbtn { --btn: var(--mkg); --btn-edge: var(--brd-gold); --btn-bg: var(--mkbtn-bg); display: inline-block; font-family: var(--mk-mono,ui-monospace,monospace); font-size: .72em; letter-spacing: .08em; text-transform: uppercase; padding: 5px 13px; text-decoration: none; cursor: pointer; color: var(--btn); border: 1px solid var(--btn-edge); background: var(--btn-bg); white-space: nowrap; -webkit-transition: color .12s, border-color .12s, -webkit-box-shadow .12s; transition: color .12s, border-color .12s, box-shadow .12s; }
.mkbtn:hover { border-color: var(--btn); -webkit-box-shadow: 0 0 10px color-mix(in srgb, var(--btn) 42%, transparent); box-shadow: 0 0 10px color-mix(in srgb, var(--btn) 42%, transparent); }
/* Colour variants — each just sets the three props. */
.mkbtn.gold   { --btn:var(--mkg); --btn-edge:var(--brd-gold); --btn-bg:var(--mkbtn-bg); }
.mkbtn.blue   { --btn:#5aa6e6; --btn-edge:#25436e; --btn-bg:#0a1220; }
.mkbtn.green  { --btn:#5ad06a; --btn-edge:#245c2c; --btn-bg:#0a1a0d; }
.mkbtn.purple { --btn:#b98ce6; --btn-edge:#3f2d63; --btn-bg:#120c1e; }
.mkbtn.orange { --btn:#e0913a; --btn-edge:#5e3c14; --btn-bg:#1a0f04; }
.mkbtn.eng { -ms-flex-item-align: center; -ms-grid-row-align: center; align-self: center; -webkit-box-flex: 0; -ms-flex: 0 0 auto; flex: 0 0 auto; }
.mkbtn.rare { --btn:#e8b45a; --btn-edge:#7a5a1a; }
.mkbtn.nem { --btn:#ff6f5c; --btn-edge:#7a2a24; --btn-bg:#170a09; }
.mkbtn.ghost { --btn:#a0c0dc; --btn-edge:#2a2440; --btn-bg:transparent; }
.mkbtn.good { --btn:#8fe08f; --btn-edge:#2f5a2f; --btn-bg:#0d160d; }
.mkbtn.danger { --btn:#ee8888; --btn-edge:#6a2626; --btn-bg:#160b0b; }
.mkbtn.sm { padding: 3px 8px; font-size: .64em; }   /* compact — tight rows (online-list, steppers, etc.) */
.mkbtn.off { opacity: .38; cursor: default; pointer-events: none; }   /* disabled/inactive */
/* Layout helpers — restore intent lost when a button's inline style is swapped for .mkbtn. */
.mkbtn.block { display: block; width: 100%; text-align: center; box-sizing: border-box; }   /* full-width primary action */
.mkbtn.grow  { -webkit-box-flex: 1; -ms-flex: 1; flex: 1; text-align: center; }               /* fill a flex row */
.mkbtn.rt    { margin-left: auto; }                                                            /* push to the right in a flex row */
/* Native <input type=button>/<button> adopt the same box. The global input[type=button] rule
   (0,1,1) beats .mkbtn's base (0,1,0) on colour/border/bg/font, so re-assert them here from the
   custom props at matching specificity (placed later → wins the tie). Kill the OS gradient too. */
input.mkbtn, button.mkbtn { -webkit-appearance: none; -moz-appearance: none; appearance: none; color: var(--btn); background: var(--btn-bg); background-image: none; border: 1px solid var(--btn-edge); padding: 5px 13px; font-family: var(--mk-mono,ui-monospace,monospace); font-size: .72em; letter-spacing: .08em; text-transform: uppercase; line-height: normal; margin: 0; }
input.mkbtn:hover, button.mkbtn:hover { color: var(--btn); border-color: var(--btn); background-image: none; -webkit-box-shadow: 0 0 10px color-mix(in srgb, var(--btn) 42%, transparent); box-shadow: 0 0 10px color-mix(in srgb, var(--btn) 42%, transparent); }
/* Fold the shared action-button classes into the mockup button language (uppercase,
   letter-spaced, subtle hover glow) — colours/sizes kept so meaning + tight rows survive. */
.mkq .bbtn, .mkbag .bbtn, .mkeq .eq-btn, .craft-btn, .craft-btn-locked { text-transform: uppercase; letter-spacing: .05em; }
.mkq .bbtn:hover, .mkbag .bbtn:hover, .mkeq .eq-btn:hover, .craft-btn:hover { -webkit-box-shadow: 0 0 8px rgba(var(--mkg-rgb),.26); box-shadow: 0 0 8px rgba(var(--mkg-rgb),.26); }

/* ───── Game UI areas ───── */

#statMenu, #equipMenu {
    padding: 2px;
}

#displayArea {
    padding: 2px;
}

#infoArea {
    padding: 2px;
}

#moveMenu {
    padding: 2px;
}

/* ── Chat dock toggle: hide chat → expand the three game panes ───────────
   #chatToggleBtn (floating) flips body.chat-hidden, which collapses #chatDock
   and stretches .game-pane to fill the reclaimed height. Remembered per
   browser via localStorage (gamefunctions.js: toggleChatDock/initChatDock). */
/* ⚠⚠ overflow IS LOAD-BEARING and lives here, not inline. The old table shell
   put style='overflow-y:auto;overflow-x:hidden;' on each pane <div>; the grid
   rewrite (2026-08-25) dropped it, so any panel taller than the pane — an
   auction list, a long shop — spilled OUT of its box and painted BEHIND the
   chat dock, where it could be neither scrolled nor clicked. Owner report:
   "things load behind other UI stuff". min-height:0 lets a grid item actually
   scroll instead of growing to fit its content. */
.game-pane { height: 100%; overflow-y: auto; overflow-x: hidden; min-height: 0; }
/* Chat hidden (5 Oct 2026): Hide Chat now hides the Chat and Who's Online PANELS,
   wherever a player put them (gamefunctions.js panelShown). When the bottom row is
   left empty, panelsApply() sets body.lay-no-bottom and the row collapses — see
   "Panels that move, hide and fold" below. body.chat-hidden is only a marker now:
   it no longer hides #chatDock, which may be holding other panels. */

#chatToggleBtn {
    position: fixed; right: 16px; bottom: 14px; z-index: 900;
    background: linear-gradient(180deg,#141a30,#0d1120);
    border: 1px solid var(--brd); color: #b9a8e8;
    font-family: var(--mk-display, Georgia, serif); font-size: 0.78em; letter-spacing: 0.05em;
    padding: 7px 13px; border-radius: 20px; cursor: pointer;
    -webkit-user-select: none; user-select: none;
    box-shadow: 0 3px 12px rgba(0,0,0,0.5);
    transition: border-color .12s, color .12s, background .12s;
}
#chatToggleBtn:hover {
    border-color: var(--mkgd); color: #ffe06a;
    background: linear-gradient(180deg,#1a2340,#120c28);
}

#mapMenu {
    padding: 4px;
    font-family: monospace;
    font-size: 0.85em;
    /* cosmetic frame — display stays block so it fills its container on mobile */
    background: #050410;
    /* Square single-line frame. The old 6px radius + the extra `0 0 0 1px` ring
       read as a double frame; the ::before inner rule below is now the shoulder. */
    border: 1px solid var(--brd);
    border-radius: 0;
    box-shadow:
        0 0 16px rgba(40,25,80,0.5),
        inset 0 0 24px rgba(0,0,0,0.6);
    position: relative;
}

/* ─────────────────────────────────────────────────────────────────
   Chatroom — Exo 2 typeface, message-row layout
   ───────────────────────────────────────────────────────────────── */

#chatRoom {
    /* Fills the remainder of the dock — the DOCK's height is what the
       horizontal handle moves now, so this must not be pinned. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 3px 2px 4px;
    font-family: var(--mk-body);
    font-size: 0.84em;
    font-weight: 400;
    line-height: 1.52;
    scroll-behavior: smooth;
}

/* ── Individual message rows ──────────────────────────────────── */
.chat-msg {
    display: block;
    padding: 3px 8px 3px 7px;
    border-left: 2px solid transparent;
    border-radius: 2px;
    -webkit-transition: background 0.08s ease;
    transition: background 0.08s ease;
    word-break: break-word;
}

.chat-msg:hover {
    background: rgba(255, 255, 255, 0.028);
}

/* System events: achievements, PvP kills, announcements */
.chat-msg-system {
    border-left-color: #252a4c;
    background: rgba(40, 25, 70, 0.12);
}

.chat-msg-system:hover {
    background: rgba(55, 35, 90, 0.22);
}

/* Highlighted announcements (rare/legendary achievements, tournament wins) */
.chat-msg-announce {
    border-left-color: var(--mkgd);
    background: rgba(var(--mkgd-rgb),0.06);
}

.chat-msg-announce:hover {
    background: rgba(var(--mkgd-rgb),0.10);
}

/* ── Timestamp (player messages only — system rows omit it) ── */
.chat-msg-time {
    display: inline-block;
    min-width: 34px;   /* fits HH:MM */
    /* ⚠ #2e2848 sat at roughly 1.4:1 against the chat ground — technically
       present, practically invisible (player report 2026-08-26). This is the
       recessive-but-readable step up, ~3.4:1: still clearly secondary to the
       message text, but actually legible. Do not take it back down. */
    color: #6a6280;
    font-size: 0.74em;
    font-weight: 300;
    font-family: var(--mk-mono);
    letter-spacing: 0.01em;
    margin-right: 6px;
    user-select: none;
    -webkit-user-select: none;
    vertical-align: baseline;
}

/* ── Usernames within messages ─────────────────────────────── */
#chatRoom strong,
#chatRoom b {
    font-family: 'Exo 2', 'Segoe UI', sans-serif;
    font-weight: 600;
    color: inherit;        /* respect the font colour="" attribute  */
}

/* Legacy <font> tags keep their colour attribute */
#chatRoom font {
    font-family: inherit;
    font-size: inherit;
}

/* ── Empty / placeholder state ─────────────────────────────── */
/* Target only direct-child <em> tags (the "no messages yet" placeholders).
   Nested <em> inside message rows (e.g. rare monster titles) must stay inline. */
#chatRoom > em {
    color: #3a3258;
    font-family: 'Exo 2', sans-serif;
    font-style: normal;
    font-size: 0.88em;
    padding: 8px;
    display: block;
}

/* ── Chat input box ────────────────────────────────────────── */
#chatBox {
    padding: 4px 2px 3px;
}

#chatBox form {
    display: flex;
    align-items: center;
    gap: 4px;
}

#chatBox input[type="text"]#chatmessage {
    flex: 1;
    min-width: 0;
    background: var(--mkv);
    border: 1px solid var(--mke);
    color: var(--mkink);
    font-family: var(--mk-body);
    font-size: 0.82em;
    font-weight: 400;
    padding: 6px 9px;
    outline: none;
    -webkit-transition: border-color 0.15s, box-shadow 0.15s;
    transition: border-color 0.15s, box-shadow 0.15s;
    /* override the global width:70% inline style if present */
    width: auto !important;
}

#chatBox input[type="text"]#chatmessage:focus {
    border-color: var(--mkgd);
    box-shadow: 0 0 6px rgba(var(--mkg-rgb),0.22);
}

#chatBox input[type="button"]#sendmessage {
    background: #160f08;
    border: 1px solid #6a5a1a;
    color: var(--mkg);
    font-family: var(--mk-mono);
    font-size: 0.68em;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 6px 14px;
    cursor: pointer;
    -webkit-transition: box-shadow 0.12s, border-color 0.12s;
    transition: box-shadow 0.12s, border-color 0.12s;
}

#chatBox input[type="button"]#sendmessage:hover {
    border-color: var(--mkg);
    color: var(--mkg);
    box-shadow: 0 0 10px rgba(var(--mkg-rgb),0.3);
}

/* ── Chat tabs ──────────────────────────────────────────────── */
/* (overrides the .chat-tab rule defined later; specificity wins) */
#chatTabs {
    border-bottom: 1px solid #1a1530;
    margin-bottom: 4px;
    padding: 2px 0 5px;
    display: flex;
    gap: 3px;
    flex-wrap: wrap;
}

/* ── Online list ────────────────────────────────────────────── */
/* The roster absolutely FILLS its dock cell (2026-08-21): the dock is a
   table row whose height is set by the LEFT column (input + tabs +
   #chatRoom), so the cell — and therefore the list — is always exactly
   that tall at every resolution, bottoms aligned by construction. The
   list scrolls internally past ~15 players instead of stretching the
   page (the pre-fix bug at 20+ online). No per-breakpoint numbers to
   keep in sync with #chatRoom any more. */
#chatDock td:last-child { position: relative; }
/* ⚠⚠ DO NOT REINTRODUCE `position:absolute; top/right/bottom/left:0` HERE.
   It lived here until 2026-08-31 as a leftover from the old <td> layout, and
   it broke every container that was not #chatDock, twice:
     • guest_resume.php's legacy table shell — no positioned ancestor, so it
       resolved against the VIEWPORT and covered the whole page as an invisible
       layer. Reported as "the whole screen locks, no links work" (2026-08-30).
     • mobile's <details> roster — reported as "Online Players is bust"
       (2026-08-31).
   Each was patched by a container-scoped `position:static` reset, which fixed
   the symptom and left the trap armed for the next container. This element is
   now a plain in-flow block; every container sizes and scrolls it POSITIVELY
   (see #chatDock > #onlineList, mobile.css, and guest_resume.php's wrapper). */
#onlineList {
    font-family: var(--mk-body);
    font-size: 0.80em;
    font-weight: 400;
    line-height: 1.5;
    padding: 2px;
    overflow-y: auto;
}

#onlineList table {
    border: none;
    border-spacing: 0;
    background: transparent;
    width: 100%;
}

#onlineList td {
    background: transparent;
    border: none;
    border-bottom: 1px solid #0e0c18;
    padding: 3px 5px;
    color: #8880a8;
    vertical-align: middle;
}

#onlineList td:hover,
#onlineList tr:hover td {
    background: rgba(255, 255, 255, 0.025);
}

#onlineList b,
#onlineList strong {
    font-family: 'Exo 2', sans-serif;
    font-weight: 600;
}

#onlineList {
    padding: 2px;
}

#combatArea, #pvpArea, #auctionArea {
    padding: 2px;
}

/* ───── Chat tabs (mockup: bordered pills, active = gold underline) ───── */
/* Chat tabs use the same faceted box as .mkbtn, tinted per channel so each reads
   at a glance (Talk=blue, Guild=green, Party=purple, Sales=amber, Staff=orange,
   All=gold). Idle = muted tint; hover/active = full channel colour + underline. */
.chat-tab {
    --tab: var(--mkg); --tab-edge: #5a4a1a;
    display: inline-block;
    padding: 4px 10px;
    border: 1px solid var(--tab-edge);
    background: #0d0b16;
    color: color-mix(in srgb, var(--tab) 60%, #5a5470);
    font-family: var(--mk-mono);
    font-size: 0.68em;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    -webkit-transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
#tab_all     { --tab: var(--mkg); --tab-edge: #5a4a1a; }
#tab_general { --tab: #5aa6e6; --tab-edge: #25436e; }
#tab_guild   { --tab: #5ad06a; --tab-edge: #245c2c; }
#tab_party   { --tab: #b98ce6; --tab-edge: #3f2d63; }
#tab_sales   { --tab: #e0b04a; --tab-edge: #5e4818; }
#tab_staff   { --tab: #e0913a; --tab-edge: #5e3c14; }

.chat-tab:hover {
    color: var(--tab);
    border-color: var(--tab);
    text-decoration: none;
}

.chat-tab.active {
    color: var(--tab);
    border-color: var(--tab);
    background: color-mix(in srgb, var(--tab) 13%, #0d0b16);
    -webkit-box-shadow: inset 0 -2px 0 var(--tab);
    box-shadow: inset 0 -2px 0 var(--tab);
}

/* ═══ Crystalline chat elements (mockup skin) ═══════════════════════════ */
/* Rotated-diamond identity gem before names (messages + roster).
   Fill colour is set inline per-user (chat colour). */
.mkic {
    display: inline-block;
    width: 9px; height: 9px;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    margin-right: 5px;
    vertical-align: 0;
    -webkit-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
    border-radius: 1px;
}

/* Bordered rank badges (replaces the old bracketed [GM] / [Admin] text) */
.mktag {
    display: inline-block;
    font-family: var(--mk-mono);
    font-size: 0.82em;
    line-height: 1;
    letter-spacing: 0.05em;
    padding: 1px 4px;
    margin-right: 4px;
    white-space: nowrap;
    text-transform: uppercase;
    vertical-align: 1px;
}
/* Admin badge tracks ADMIN_CHAT_COLOUR (db.php) — #a89d9f since
   2026-08-25, was the gold --mkg token. ⚠ Two files: change the badge HERE
   and the name/message colour in db.php's constant, or they drift apart. */
.mktag.admin { color: #a89d9f;     border: 1px solid #544e4f; }
.mktag.gm    { color: #ff8800;     border: 1px solid #885500; }
.mktag.trial { color: #a89868;     border: 1px solid #55492e; }

/* Online-list roster — .subhead + .row + .nmc + .pv + .cls + .lvl */
#onlineList .subhead {
    font-family: var(--mk-mono);
    font-size: 0.64rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--mkgd);
    margin: 6px 0 8px;
    display: flex;
    align-items: center;
    gap: 9px;
}
#onlineList .subhead::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--mke), transparent);
}
/* ⚠⚠ THE ROSTER IS A FOUR-COLUMN TABLE: name | Profile | class | level.
   ⚠ DO NOT put `justify-content: space-between` back on .row. That was the
     original bug: space-between pins only the FIRST and LAST child and spreads
     whatever is between them according to THAT row's leftover space, so the
     Profile button's x tracked the length of the name and the column zigzagged.
   ⚠ Nor subgrid — it was tried on 2026-08-31 and reverted the same day. It is
     correct where it is supported, but the @supports fallback silently returned
     the ragged flex layout for everyone else, which is exactly what the bug
     report showed. One mechanism that works everywhere beats a good one plus a
     broken fallback.
   HOW IT ALIGNS: .who is the only flexible item, so it swallows ALL slack; the
   three items after it are pushed against the right edge at widths that do not
   depend on the row's content, which puts them at the same x on every row. */
#onlineList .row {
    display: flex;
    /* centre, not baseline: .pv is a fixed-height box and baseline alignment
       would sit it low and stretch the row. */
    align-items: center;
    font-size: 0.95em;
    padding: 3px 4px;
    border-bottom: 1px solid var(--mkes);
    gap: 8px;
}
#onlineList .row:hover { background: rgba(255, 255, 255, 0.025); }
/* The ONLY flexible cell. `flex: 1 1 0` (not `auto`) so it sizes from zero and
   takes every spare pixel — that is what pins the three columns after it. */
#onlineList .row .who {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#onlineList .nmc { color: var(--mkmist); }

/* Profile link. Two things make it findable that the old inline diamond was not:
   it is a bordered control rather than a loose glyph, and it is its OWN flex
   item so a long name can never truncate it away. Deliberately echoes the
   "&#9670; Profile" pill in the Players Here panel (setgame.php) at list scale —
   same idea, same colours, less room. The name beside it opens a PM; this opens
   the profile, so the two need to look like different controls. */
#onlineList .pv {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border: 1px solid #2a2240;
    border-radius: 3px;
    background: #0e0b18;
    color: #9a93b4;
    font-size: 0.76em;
    line-height: 1;
    text-decoration: none;
    transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
/* The word is shown WHEN THERE IS ROOM FOR IT. Measured at the default 222px
   dock column: a "Profile" label leaves the name 42px — "Jinobe" truncates —
   because class + level already costs 93px, and in a roster the NAME is the
   primary information. So the label is opt-in on width: the chat column is
   drag-resizable (gsplitC) and mobile is full-bleed, and at ~260px upward the
   word fits without eating the name. Container query, not a media query: what
   matters is how wide THIS COLUMN is, not the viewport. */
#onlineList { container-type: inline-size; }
#onlineList .pv .lbl {
    display: none;
    font-family: var(--mk-mono);
    font-size: 0.86em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
@container (min-width: 260px) {
    #onlineList .pv .lbl { display: inline; }
}
/* No container support: fall back to the icon button, which is the safe end. */
@supports not (container-type: inline-size) {
    #onlineList .pv .lbl { display: none; }
}
#onlineList .pv:hover,
#onlineList .pv:focus-visible {
    color: var(--mkgd);
    border-color: var(--mkgd);
    background: #171129;
}
#onlineList .pv:focus-visible { outline: 2px solid var(--mkgd); outline-offset: 1px; }

/* "Achievement Unlocked" banner — clickable, opens the earner's profile at its
   achievement list. Colours are inline (they vary by rarity); only the
   interaction state lives here. */
.achunlock { transition: filter 0.12s ease, transform 0.12s ease; }
.achunlock:hover,
.achunlock:focus-visible { filter: brightness(1.35); transform: translateX(1px); }
.achunlock:focus-visible { outline: 2px solid var(--mkgd); outline-offset: 2px; }
/* Class column. A FIXED width is the point — sized to content it would vary per
   row and shove the Profile button around again. 12ch fits every class in the
   game except "New Adventurer" (14), which ellipses and keeps its full name in
   the title attribute. ch on a monospace font is exact. */
#onlineList .cls {
    /* Hidden by default. At the 222px default dock width a 12ch class column
       plus the level and the Profile button crushed the NAME to about four
       characters — and the name is what the roster is for. It comes back at the
       same 260px breakpoint that gives the button its label, so widening the
       chat column reveals both together. The class is still on the profile. */
    display: none;
    flex: 0 0 12ch;
    color: #6a5d85;
    font-family: var(--mk-mono);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
@container (min-width: 260px) {
    #onlineList .cls { display: block; }
}
/* Level column. Right-aligned with tabular figures so 5390 / 4758 / 75 stack
   flush instead of drifting. 5ch covers the level cap and the "???" staff mask. */
#onlineList .lvl {
    flex: 0 0 5ch;
    text-align: right;
    color: #7fa0c0;
    font-weight: 600;
    font-family: var(--mk-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ───── Custom scrollbars (WebKit / Blink) ───── */
::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: #080a16;
    border-left: 1px solid #1a2036;
}

::-webkit-scrollbar-thumb {
    background: #26284c;
    border: 1px solid #3a3060;
    border-radius: 2px;
}

::-webkit-scrollbar-thumb:hover {
    background: #3a3060;
    border-color: var(--mkgd);
}

::-webkit-scrollbar-corner {
    background: #080a16;
}

/* ───── Firefox scrollbar ───── */
* {
    scrollbar-width: thin;
    scrollbar-color: #26284c #080a16;
}

/* ═══════════════════════════════════════════════════════════════
   Crystaria — Interactive Visual Enhancements
   ═══════════════════════════════════════════════════════════════ */

/* ── Combat row animations ───────────────────────────────────── */
@keyframes cRiseIn {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes cCritGlow {
    0%   { box-shadow: none; }
    20%  { box-shadow: 0 0 14px rgba(var(--mkg-rgb),0.65), inset 0 0 8px rgba(var(--mkg-rgb),0.12); }
    100% { box-shadow: 0 0 3px rgba(var(--mkg-rgb),0.1); }
}

/* Every combat row fades+rises in; crits also glow gold */
.c-row  { animation: cRiseIn 0.16s ease-out both; }
.c-crit { animation: cRiseIn 0.16s ease-out both, cCritGlow 0.75s ease-out both; }

/* ── HP / MP / XP stat-bar transitions ──────────────────────── */
/* Width animates smoothly when bars are re-rendered each turn   */
.stat-fill {
    -webkit-transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
    transition:         width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Danger pulse — applied to HP bar when below 25 % */
@keyframes hpDanger {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.42; }
}
.stat-fill-danger {
    animation: hpDanger 1.2s ease-in-out infinite;
}

/* ── Redesign: living energy on the vitals bars (glow + shimmer + sparkle) ──
   Effects ride on ::before/::after so they never clash with the width
   transition or the danger flash on .stat-fill itself. Colour-agnostic
   (screen-blended white) so they survive the JS fill recolour each turn.
   Animations are UNCONDITIONAL by design — do NOT gate them behind
   @media (prefers-reduced-motion): Windows sets that flag for players who
   never chose it (see the a11y block's 2026-07-02 lesson further down).
   The game's own body.a11y-reduced-motion panel toggle is the opt-out,
   and its jump-cut rule silences these like every other animation. */
.stat-fill { box-shadow: inset 0 0 7px rgba(255,255,255,0.22); }
.stat-fill::after {          /* liquid — a rolling meniscus + a deeper counter-current */
    content: ""; position: absolute; inset: 0; pointer-events: none; mix-blend-mode: screen;
    background-image:
        radial-gradient(9px 5px at 8px 0px,  rgba(255,255,255,0.45), transparent 70%),   /* surface scallops */
        radial-gradient(26px 11px at 24px 16px, rgba(255,255,255,0.16), transparent 70%); /* deep current */
    background-size: 16px 100%, 48px 100%;
    background-repeat: repeat-x, repeat-x;
    animation: statFlow 2.8s linear infinite;
}
.stat-fill::before {         /* sparkle motes — 7 staggered points of light */
    content: ""; position: absolute; inset: 0; pointer-events: none; mix-blend-mode: screen; opacity: 0.9;
    background-repeat: no-repeat;
    background-image:
        radial-gradient(circle 2.2px at 8%  35%, #fff, transparent 65%),
        radial-gradient(circle 1.5px at 22% 68%, rgba(255,255,255,0.95), transparent 65%),
        radial-gradient(circle 2.4px at 37% 30%, #fff, transparent 65%),
        radial-gradient(circle 1.6px at 52% 62%, rgba(255,255,255,0.90), transparent 65%),
        radial-gradient(circle 2.6px at 67% 40%, #fff, transparent 65%),
        radial-gradient(circle 1.6px at 79% 66%, rgba(255,255,255,0.95), transparent 65%),
        radial-gradient(circle 2.2px at 92% 34%, #fff, transparent 65%);
    animation: statTwinkle 1.8s ease-in-out infinite;
}
/* :not() keeps the low-HP danger flash in charge when it's active */
.stat-fill:not(.stat-fill-danger) { animation: statBreathe 2.6s ease-in-out infinite; }
/* Liquid flow: each layer shifts by an exact multiple of its tile width
   (16px×-2 / 48px×+1) so the loop is seamless; opposite directions sell
   the "sloshing fluid" read. */
@keyframes statFlow    { 0% { background-position: 0 0, 0 0; } 100% { background-position: -32px 0, 48px 0; } }
@keyframes statTwinkle { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@keyframes statBreathe { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.22); } }

/* ═══════════════════════════════════════════════════════════════════════
   MOCKUP SKIN — deep-void faceted-crystal reskin (redesign, comprehensive)
   Ported from the interface mockup: amethyst void + glows, glassy faceted
   panels, brighter gold. Aggressive attribute + !important overrides so it
   beats the game's pervasive inline styles. Scoped to game regions.
   ═══════════════════════════════════════════════════════════════════════ */
:root{
  --mkv:#070c16; --mkp:#0e1626; --mkp2:#101c30; --mkp3:#16263e; --mke:#3a2c62; --mkes:#14243c;
  --mkg:#e8c84a; --mkgd:#c9a020; --mka:#6fb4e8; --mkad:#4a3a7c; --mkink:#eaf3fb; --mkmist:#9cb8d4;
  /* ── Border language (2026-07-30) ──────────────────────────────────────
     Panel frames and buttons were split across three palettes — steel-blue
     (#24406a/#2a4a80), violet (#33205e) and gold — which read as generic UI
     chrome fighting the game's gold-on-violet identity. --mke/--mkad are now
     violet, and these tokens give frames one shared vocabulary:
       --brd      standard panel frame
       --brd-soft quieter inner divisions (nested boxes, list rows)
       --brd-gold active / important / focused elements only            */
  --brd:#3a2c62; --brd-soft:#241a3e; --brd-gold:#6a5a1a;
  /* ── Channel tokens (2026-08-25, for the theme system) ──────────────
     rgba() cannot take a hex custom property, so the accent also exists as
     bare channels. Every rgba(var(--mkg-rgb),…) and rgba(var(--mkgd-rgb),…) in this
     file now reads these, which is what lets a theme retint the glows and
     shoulder gradients instead of leaving gold fringing on a blue UI. */
  --mkg-rgb:232,200,74; --mkgd-rgb:201,160,32; --mka-rgb:111,180,232;
  --mkbtn-bg:#160f08;   /* accent-button ground; themed, default unchanged */
  /* Shoulder = a 1px gold light running along a panel's top and bottom edges,
     fading out before the corners. Applied as a ::after background layer so it
     rides ON the border box without needing a clip-path or extra markup.
     Top edge is the bright one; bottom is a dimmer echo (lit-from-above). */
  --shoulder-t:linear-gradient(90deg,rgba(var(--mkgd-rgb),0) 0,rgba(var(--mkgd-rgb),0) 9px,var(--mkgd) 25%,var(--mkgd) 75%,rgba(var(--mkgd-rgb),0) calc(100% - 9px),rgba(var(--mkgd-rgb),0) 100%);
  --shoulder-b:linear-gradient(90deg,rgba(var(--mkgd-rgb),0) 0,rgba(var(--mkgd-rgb),0) 9px,rgba(var(--mkgd-rgb),.42) 25%,rgba(var(--mkgd-rgb),.42) 75%,rgba(var(--mkgd-rgb),0) calc(100% - 9px),rgba(var(--mkgd-rgb),0) 100%);
  --mkfacet:polygon(0 8px,8px 0,calc(100% - 8px) 0,100% 8px,100% calc(100% - 8px),calc(100% - 8px) 100%,8px 100%,0 calc(100% - 8px));
  --mkfacetsm:polygon(0 4px,4px 0,calc(100% - 4px) 0,100% 4px,100% calc(100% - 4px),calc(100% - 4px) 100%,4px 100%,0 calc(100% - 4px));
}
/* ── Foundation: deep amethyst void with soft gold/amethyst glows ── */
body{
  background:
    radial-gradient(1150px 680px at 84% -10%, rgba(var(--mka-rgb),.15), transparent 60%),
    radial-gradient(900px 560px at -10% 16%, rgba(var(--mkg-rgb),.075), transparent 55%),
    var(--mkv) !important;
  color:var(--mkmist);
}
/* ── Nav bar: faceted glass tabs ── */
#navBar{ background:linear-gradient(180deg,rgba(var(--mka-rgb),.08),rgba(10,8,18,.4)) !important; border-bottom:1px solid var(--mke) !important; }
.nav-btn, .nav-group > .nav-btn{ background:transparent !important; border:1px solid transparent !important; color:var(--mkmist) !important; font-family: var(--mk-display, Georgia, serif) !important; letter-spacing:.04em !important; }
/* Violet frames at rest, GOLD on hover — gold is the interaction signal, not decoration.
   ⚠⚠ :focus-within is NOT optional here. .nav-menu opens on hover OR
   focus-within (see .nav-group:focus-within .nav-menu below), and clicking a
   group header focuses it — so a menu opened by CLICK left its header
   unhighlighted. On touch there is no hover at all, which made that the only
   behaviour mobile players ever saw: the menu drops open and nothing looks
   selected. Reported as "the highlights don't always work". */
.nav-btn:hover, .nav-group:hover > .nav-btn,
.nav-btn:focus-visible, .nav-group:focus-within > .nav-btn{ color:var(--mkg) !important; background:var(--mkp2) !important; border-color:var(--mkgd) !important; box-shadow:0 0 9px rgba(var(--mkg-rgb),.18); }

/* ⚠⚠ The alert style has to repeat !important AND match the group selector's
   specificity. `.nav-btn{...!important}` above sets background, border and
   colour on EVERY nav button, and `.nav-btn-alert` (0,1,0, no !important)
   lost all three to it — only the box-shadow keyframe survived, because that
   property is not declared above. So Objectives and Friends pulsed faintly
   instead of turning gold, which is the highlight players were missing.
   `.nav-group > .nav-btn` is (0,2,0), so a GROUP header additionally needs
   `.nav-group > .nav-btn-alert` or it stays overridden even with !important. */
.nav-btn-alert, .nav-group > .nav-btn-alert{
  border-color:var(--mkgd) !important;
  background:linear-gradient(to bottom,#2a1f0c 0%,#15100a 100%) !important;
  color:#ffe06a !important;
}
.nav-btn-alert:hover, .nav-group:hover > .nav-btn-alert,
.nav-group:focus-within > .nav-btn-alert{
  border-color:#ffd24a !important;
  background:linear-gradient(to bottom,#3a2a14 0%,#1f1808 100%) !important;
  color:#fff0a0 !important;
}
.nav-menu{ background:linear-gradient(180deg,var(--mkp2),var(--mkp)) !important; border:1px solid var(--mke) !important; }
/* ── Panels & cards: remap the game's flat dark inline backgrounds to the glassy panel gradient ── */
[style*="background:#0d0b18"], [style*="background: #0d0b18"], [style*="background:#0e0b18"],
[style*="background:#160e28"], [style*="background:#0d1122"], [style*="background:#0d1018"],
[style*="background:#0c0818"], [style*="background:#17100f"], [style*="background:#0d0d0d"],
[style*="background:#0a0816"], [style*="background:#0e0b1c"], [style*="background:#1a1a2a"],
[style*="background:#0b0807"], [style*="background:#100c1e"], [style*="background:#0c0913"]{
  background:linear-gradient(180deg,var(--mkp2),var(--mkp)) !important;
}
/* Glassy sheen + soft amethyst inner glow on every remapped panel */
#statMenu [style*="background:#0d0b18"], #statMenu [style*="background:#160e28"], #statMenu [style*="linear-gradient(180deg,#160e28"],
#displayArea [style*="background:#0d0b18"], #displayArea [style*="background:#0e0b18"], #displayArea [style*="background:#0d1122"],
#displayArea [style*="background:#17100f"], #displayArea [style*="background:#0e0b1c"], #infoArea [style*="background:#0e0b18"]{
  box-shadow: inset 0 1px 0 rgba(var(--mkg-rgb),.06), inset 0 0 16px rgba(90,50,140,.20), 0 6px 18px -10px rgba(0,0,0,.6) !important;
}
[style*="linear-gradient(180deg,#160e28"], [style*="linear-gradient(180deg,#17100f"], [style*="linear-gradient(180deg,#160f2"]{
  background:linear-gradient(180deg,var(--mkp2),var(--mkp)) !important;
}
/* Amethyst edges everywhere the game uses its dim borders */
[style*="border:1px solid #1e1a2a"], [style*="border:1px solid #2a1e40"], [style*="border:1px solid #1a2036"],
[style*="border:1px solid #3a2420"], [style*="border:1px solid #33205e"], [style*="border:1px solid #4a2a6a"],
[style*="border:1px solid #2b1a50"]{ border-color:var(--mke) !important; }
/* ── Square every rounded card in the game regions (skip circles) ──
   Was `clip-path:var(--mkfacet)` (cut corners). Owner rejected the diamond/faceted
   border look 2026-08-04 — panel frames + buttons are now SQUARE, and carry the
   gold "shoulder" instead (see .mk-shoulder block). Facets survive only on the
   signature elements: hotbar tiles, portraits, the combat-arena viewport. */
#statMenu [style*="border-radius"]:not([style*="50%"]),
#displayArea [style*="border-radius"]:not([style*="50%"]),
#infoArea [style*="border-radius"]:not([style*="50%"]),
#mapMenu [style*="border-radius"]:not([style*="50%"]),
#dungeonEnterArea [style*="border-radius"]:not([style*="50%"]){
  border-radius:0 !important; clip-path:none !important;
}
/* ── Brighter gold for headings/accents ── */
[style*="color:var(--mkgd)"], [style*="color:#FFDD88"], [style*="color:#ffdd88"], [style*="color:#FFDD55"]{ color:var(--mkg) !important; }
/* ── Chat: etched glass ── */
#chatRoom{ background:linear-gradient(180deg,var(--mkp),rgba(10,8,18,.55)) !important; }
.chat-tab.active{ color:var(--tab) !important; -webkit-box-shadow: inset 0 -2px 0 var(--tab); box-shadow: inset 0 -2px 0 var(--tab); }

/* ═══ Stat panel REBUILT to the mockup structure (scoped .mkstat) ═══ */
#statMenu .mkstat{ padding:2px; color:#9cb8d4; }
.mkstat .mk-crest{ display:flex; align-items:center; gap:11px; margin-bottom:14px; }
.mkstat .mk-portrait{ width:48px; height:48px; flex:0 0 auto; display:flex; align-items:center; justify-content:center;
  background:radial-gradient(circle at 50% 40%,rgba(var(--mkg-rgb),.16),transparent 62%),linear-gradient(160deg,#1a1330,#0a1424);
  box-shadow:inset 0 0 0 1px #5a3a7a, 0 0 10px rgba(80,40,120,.35);
  clip-path:polygon(50% 0,100% 26%,100% 74%,50% 100%,0 74%,0 26%); }
.mkstat .mk-portrait img{ width:84%; height:84%; object-fit:contain; }
.mkstat .mk-who{ min-width:0; flex:1; }
.mkstat .mk-name{ font-family: var(--mk-display, Georgia, serif); color:var(--mkg); font-size:1.15em; font-weight:600; letter-spacing:.03em; line-height:1.05; text-shadow:0 0 12px rgba(var(--mkg-rgb),.3); }
.mkstat .mk-sub{ font-size:.72em; color:#7fa0c0; letter-spacing:.05em; text-transform:uppercase; margin-top:3px; line-height:1.3; }
.mkstat .mk-guild{ font-size:.78em; color:#88cc99; margin-top:3px; }
.mkstat .mk-guild .mk-tag{ color:var(--mkgd); }
.mkstat .mk-vitals{ display:flex; flex-direction:column; gap:10px; margin-bottom:13px; }
.mkstat .mkvital .mklab{ display:flex; justify-content:space-between; align-items:baseline; font-size:.7em; letter-spacing:.1em; text-transform:uppercase; color:#7fa0c0; margin-bottom:4px; }
.mkstat .mkvital .mklab span:last-child{ font-family:'IM Fell English',Georgia,serif; color:#cdbff0; letter-spacing:0; }
.mkstat .mktrack{ position:relative; height:14px; background:#070510; overflow:hidden;
  clip-path:polygon(4px 0,100% 0,calc(100% - 4px) 100%,0 100%);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.10), inset 0 0 8px #000; }
.mkstat .mktrack .stat-fill{ position:absolute; top:0; left:0; height:100%;
  clip-path:polygon(4px 0,100% 0,calc(100% - 4px) 100%,0 100%); }
/* Faceted per-vital energy fills (mockup): dark base -> colour -> bright tip + glow,
   same treatment as the enemy HP bar. Class-driven (v-hp/v-mp/v-xp) so the JS
   updateStatBar() width/regen patch can't clobber the gradient (it preserves the class). */
.mkstat .mktrack .stat-fill.v-hp{ background:linear-gradient(90deg,#7a1e18,#e0564a 70%,#ff6f5c) !important; box-shadow:0 0 10px rgba(224,86,74,.5), inset 0 0 7px rgba(255,255,255,.16); }
.mkstat .mktrack .stat-fill.v-mp{ background:linear-gradient(90deg,#123a63,#4aa6e6 70%,#6ac6ff) !important; box-shadow:0 0 10px rgba(74,166,230,.45), inset 0 0 7px rgba(255,255,255,.16); }
.mkstat .mktrack .stat-fill.v-xp{ background:linear-gradient(90deg,#7a5a12,var(--mkg) 70%,#ffe89a) !important; box-shadow:0 0 10px rgba(var(--mkg-rgb),.45), inset 0 0 7px rgba(255,255,255,.16); }
.mkstat .mk-chips{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px; }
.mkstat .mk-chip{ position:relative; font-size:.72em; padding:3px 9px; border:1px solid var(--brd); color:#9cb8d4; background:rgba(20,16,31,.55); white-space:nowrap; }
.mkstat .mk-chip.gold{ color:var(--mkg); border-color:#7a6a2e; }
.mkstat .mk-chip.amx{ color:#9cc8f0; border-color:var(--mkad); }
/* Actionable chip (Consumables, beside Valor): violet at rest, GOLD on hover,
   per the house border language. mobile.html links main.css as well, so this
   one rule covers both shells. */
.mkstat a.mk-chip.act{ cursor:pointer; text-decoration:none; color:#b9a6e6; border-color:var(--brd); transition:color .12s ease, border-color .12s ease, background .12s ease; }
.mkstat a.mk-chip.act:hover{ color:var(--mkg); border-color:var(--mkgd); background:rgba(40,30,10,.55); }
.mkstat .mk-head{ font-family: var(--mk-display, Georgia, serif); font-size:.72em; letter-spacing:.14em; text-transform:uppercase; color:#7fa0c0; margin:0 0 9px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--brd); padding-bottom:6px; }
.mkstat .mk-ministat{ display:grid; grid-template-columns:1fr 1fr; gap:0 18px; font-size:.84em; margin-bottom:14px; }
.mkstat .mk-ministat > div{ display:flex; justify-content:space-between; border-bottom:1px solid #14243c; padding:5px 0; }
.mkstat .mk-ministat .mk-k{ letter-spacing:.07em; font-family: var(--mk-display, Georgia, serif); font-size:.92em; }
.mkstat .mk-ministat .mk-v{ color:#eaf3fb; font-family:'IM Fell English',Georgia,serif; font-variant-numeric:tabular-nums; }
.mkstat .mk-gold{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.mkstat .mk-gold > div{ position:relative; background:linear-gradient(180deg,#101c30,#0e1626); border:1px solid var(--brd); padding:8px 10px; }
.mkstat .mk-gold .mk-k{ display:block; font-size:.62em; letter-spacing:.09em; text-transform:uppercase; color:#7fa0c0; }
.mkstat .mk-gold .mk-v{ display:block; font-size:.98em; color:var(--mkg); font-family:'IM Fell English',Georgia,serif; margin-top:3px; }

/* ── Map — player sprite pulse ───────────────────────────────── */
@keyframes mapPlayerPulse {
    0%, 100% { -webkit-filter: drop-shadow(0 0 3px rgba(255,214,90,0.95)); filter: drop-shadow(0 0 3px rgba(255,214,90,0.95)); }
    50%       { -webkit-filter: drop-shadow(0 0 10px rgba(255,214,90,0.45)); filter: drop-shadow(0 0 10px rgba(255,214,90,0.45)); }
}
.map-player-sprite {
    display: block;
    margin: 0 auto;
    image-rendering: pixelated;          /* map token is the crisp SVG again */
    image-rendering: crisp-edges;
    /* The token is authored at 28px to fill a zoom-1 cell, so it no longer
       needs the 1.3x nudge the old 20px SVG had — that would now overflow.
       max-width keeps it inside a narrower cell on mobile. */
    max-width: 100%;
    height: auto;
    animation: mapPlayerPulse 2s ease-in-out infinite;
}

/* ═══ Minimap → mockup shard-atlas ═══
   Gemstone POI markers, a gold pulsing player shard + ring, and beveled shard
   tiles — the map's mockup identity laid over the live maprender.php tile grid. */
/* Overworld terrain look — SCOPED to .ow-map so it does NOT bleed onto the
   dungeon/raid viewport, which also renders into #mapLayer but needs its own
   flat per-tile colours (floor vs wall) to stay visible. */
#mapLayer.ow-map td { position: relative; -webkit-clip-path: polygon(0 2px,2px 0,calc(100% - 2px) 0,100% 2px,100% calc(100% - 2px),calc(100% - 2px) 100%,2px 100%,0 calc(100% - 2px)); clip-path: polygon(0 2px,2px 0,calc(100% - 2px) 0,100% 2px,100% calc(100% - 2px),calc(100% - 2px) 100%,2px 100%,0 calc(100% - 2px)); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.55); }
/* Dark faceted terrain: a shard gradient laid over the live terrain colour/texture
   so tiles read as bevelled shards (mockup atlas) without a table->grid rewrite. */
#mapLayer.ow-map td::before { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: linear-gradient(150deg, rgba(70,58,92,0.14), rgba(5,4,11,0.6)); }
#mapLayer td > * { position: relative; z-index: 1; }
/* Click-to-move: the 8 tiles around you glow + reveal a directional arrow on hover. */
/* position:relative anchors the ::after arrow to its OWN tile. The overworld gets
   this via "#mapLayer.ow-map td" above, but the Underworld map's #mapLayer has no
   .ow-map class, so without this its arrows anchored to #mapViewport and collapsed
   into one corner. Scoping it to .mvt fixes both worlds without the terrain look. */
#mapLayer td.mvt { position: relative; cursor: pointer; }
/* Always-on directional move arrows — bold gold glyphs so the player sees at a
   glance which way they can step; each is nudged toward its own edge so it reads
   like an outward compass rose and leaves the tile centre (POI gems) clear. */
#mapLayer td.mvt::after { content: attr(data-arrow); position: absolute; inset: 0; z-index: 3; padding: 1px; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; color: #ffe08a; font-size: 13px; font-weight: 700; line-height: 1; opacity: 0.92; text-shadow: 0 0 2px #000, 0 0 3px rgba(0,0,0,0.85), 0 0 6px rgba(var(--mkg-rgb),0.5); pointer-events: none; -webkit-transition: opacity .1s ease, color .1s ease; transition: opacity .1s ease, color .1s ease; }
/* Edge nudges: N=top, S=bottom, E=right, W=left (+ diagonals) */
#mapLayer td.mvt-n::after  { -webkit-box-align: start; -ms-flex-align: start; align-items: flex-start; }
#mapLayer td.mvt-s::after  { -webkit-box-align: end;   -ms-flex-align: end;   align-items: flex-end; }
#mapLayer td.mvt-e::after  { -webkit-box-pack: end;    -ms-flex-pack: end;    justify-content: flex-end; }
#mapLayer td.mvt-w::after  { -webkit-box-pack: start;  -ms-flex-pack: start;  justify-content: flex-start; }
#mapLayer td.mvt-ne::after { -webkit-box-align: start; -ms-flex-align: start; align-items: flex-start; -webkit-box-pack: end;   -ms-flex-pack: end;   justify-content: flex-end; }
#mapLayer td.mvt-nw::after { -webkit-box-align: start; -ms-flex-align: start; align-items: flex-start; -webkit-box-pack: start; -ms-flex-pack: start; justify-content: flex-start; }
#mapLayer td.mvt-se::after { -webkit-box-align: end;   -ms-flex-align: end;   align-items: flex-end;   -webkit-box-pack: end;   -ms-flex-pack: end;   justify-content: flex-end; }
#mapLayer td.mvt-sw::after { -webkit-box-align: end;   -ms-flex-align: end;   align-items: flex-end;   -webkit-box-pack: start; -ms-flex-pack: start; justify-content: flex-start; }
/* Hover = brighter arrow + gold tile outline */
#mapLayer td.mvt:hover { box-shadow: inset 0 0 0 1px var(--mkg), 0 0 9px rgba(var(--mkg-rgb),0.55); }
#mapLayer td.mvt:hover::after { opacity: 1; color: #fff6d8; text-shadow: 0 0 2px #000, 0 0 7px rgba(var(--mkg-rgb),1); }
/* ══════════════════════════════════════════════════════════════════════════
   MAP MARKERS — SHAPE carries the category, colour separates within it

   Every marker on every map used to be the same 11x11 rotated diamond with an
   8px glyph counter-rotated inside it. That put the whole job of telling two
   dozen POI types apart onto COLOUR ALONE, and left the glyph — the only thing
   that actually names a marker — too small to read at a glance. Two fixes:

     • the box grows to 15px and the glyph to 10px, flex-centred, so the letter
       is legible at rest rather than on inspection;
     • a shape modifier encodes the CATEGORY — diamond = quest, rounded square
       = town service, circle = danger, triangle = person, dot = ambient — so
       colour only has to separate the four-to-six markers inside one group.

   ⚠ The BARE .mmk keeps the diamond. Six other renderers (underworld, sky,
     shattered, labyrinth, tutorial, realm events) emit .mmk with no shape
     class, so the default has to stay what they already look like; they get
     the legibility fix for free and can opt into shapes later.
   ⚠ Every modifier is written .mmk.mmk-xxx — two classes, so it beats the
     single-class base regardless of source order.
   ⚠ The counter-rotation has to be undone on BOTH the box and its <b>. A
     modifier that un-rotates the box and forgets the glyph tilts the letter
     45 degrees the other way.                                                */
.mmk { display: -webkit-inline-box; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; width: 15px; height: 15px; vertical-align: middle; -webkit-transform: rotate(45deg); transform: rotate(45deg); }
.mmk b { display: block; text-align: center; color: #070c16; font: 700 10px/1 var(--mk-display,Georgia,serif); -webkit-transform: rotate(-45deg); transform: rotate(-45deg); }
/* Quest — the diamond. Same geometry as the base; named so the markup says so. */
.mmk.mmk-qst { border-radius: 2px; }
/* Town service — upright rounded square. A place you walk into. */
.mmk.mmk-svc { border-radius: 3px; -webkit-transform: none; transform: none; }
.mmk.mmk-svc b { -webkit-transform: none; transform: none; }
/* Danger — circle. Dungeons, party dungeons, raids, bosses, rift portals. */
.mmk.mmk-dgr { border-radius: 50%; -webkit-transform: none; transform: none; }
.mmk.mmk-dgr b { -webkit-transform: none; transform: none; }
/* Person — triangle, and the shape IS the glyph, so no <b> is emitted. The
   clip-path would slice an inset hairline in half, so the emitter sends no
   box-shadow for this shape rather than fighting it with !important. */
.mmk.mmk-ppl { -webkit-clip-path: polygon(50% 4%, 100% 100%, 0 100%); clip-path: polygon(50% 4%, 100% 100%, 0 100%); -webkit-transform: none; transform: none; }
/* Ambient — a quiet dot. Gather nodes outnumber every other marker in view
   roughly twenty to fifteen, and they were shouting exactly as loud as a raid
   entrance while being the least urgent thing on the screen. */
.mmk.mmk-gth { width: 6px; height: 6px; border-radius: 50%; opacity: 0.6; -webkit-transform: none; transform: none; }
#mapLayer .map-player-tile { position: relative; box-shadow: inset 0 0 7px rgba(var(--mkg-rgb),0.5), 0 0 6px rgba(var(--mkg-rgb),0.4); }
#mapLayer .map-player-tile::after { content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 20px; margin: -10px 0 0 -10px; border: 1px solid var(--mkg); border-radius: 50%; opacity: 0.5; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) { #mapLayer .map-player-tile::after { -webkit-animation: mmRing 2.6s ease-out infinite; animation: mmRing 2.6s ease-out infinite; } }
@-webkit-keyframes mmRing { 0% { -webkit-transform: scale(0.6); opacity: 0.7; } 70% { -webkit-transform: scale(1.35); opacity: 0; } 100% { opacity: 0; } }
@keyframes mmRing { 0% { transform: scale(0.6); opacity: 0.7; } 70% { transform: scale(1.35); opacity: 0; } 100% { opacity: 0; } }

/* ══════════════════════════════════════════════════════════════════════════
   STORY QUEST TILES — the loudest thing on the map (2026-08-29)

   The main story chain already had its own purple diamond, but it sat in a
   strip of twenty-odd other markers and read as just one more POI. It is not
   one more POI: it is the single tile a player is most often hunting for.
   So it gets three layers nothing else on the map gets — a lit tile, a halo
   that breathes, and an expanding ring — and it is the only marker that moves.

   ⚠ The tile classes are applied at BOTH zoom 1 (badge cell) and zoom 2
     (flat colour cell). Styling only the badge would leave the mid zoom — the
     one people scan with — showing a plain lilac square. Zoom 3 is a <canvas>
     and cannot be reached from CSS at all; its halo is drawn in
     functions.js's drawFullMap().
   ⚠ Every animation is inside prefers-reduced-motion: no-preference. A
     permanently pulsing element on the main gameplay screen is exactly what
     that setting exists for, and the marker stays perfectly legible without
     it — the glow and the ring are static then, not absent.               */
#mapLayer td.map-story-tile, #mapMenu td.map-story-tile {
    position: relative;
    box-shadow: inset 0 0 0 1px rgba(204,136,255,0.85),
                inset 0 0 10px rgba(204,136,255,0.45),
                0 0 10px rgba(204,136,255,0.7);
    z-index: 3;                      /* the halo must not sit under its neighbours */
}
#mapLayer td.map-story-tile::before, #mapMenu td.map-story-tile::before {
    content: ""; position: absolute; inset: -3px;
    border-radius: 3px; pointer-events: none;
    background: radial-gradient(circle at 50% 50%, rgba(204,136,255,0.55) 0%, rgba(204,136,255,0.16) 45%, transparent 72%);
}
#mapLayer td.map-story-tile::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: 26px; height: 26px; margin: -13px 0 0 -13px;
    border: 1px solid #e8b6ff; border-radius: 50%;
    opacity: 0.55; pointer-events: none;
}
/* The marker itself: bigger, brighter, and haloed rather than flat-filled.
   ⚠ Sized RELATIVE to the base marker — when .mmk grew from 11px to 15px this
     had to grow with it, or the loudest marker on the map would have been the
     smallest one. It rides .mmk-qst, so mmStoryBeat's rotate(45deg) still
     matches the transform the shape system gives it. */
.mmk.mmk-story {
    width: 19px; height: 19px;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.75),
                0 0 8px #cc88ff, 0 0 16px rgba(204,136,255,0.85) !important;
}
.mmk.mmk-story b { font-size: 12px; line-height: 1; color: #1a0426; }

@media (prefers-reduced-motion: no-preference) {
    #mapLayer td.map-story-tile::after { -webkit-animation: mmStoryRing 2.2s ease-out infinite; animation: mmStoryRing 2.2s ease-out infinite; }
    #mapLayer td.map-story-tile::before, #mapMenu td.map-story-tile::before { -webkit-animation: mmStoryBreathe 2.2s ease-in-out infinite; animation: mmStoryBreathe 2.2s ease-in-out infinite; }
    .mmk.mmk-story { -webkit-animation: mmStoryBeat 2.2s ease-in-out infinite; animation: mmStoryBeat 2.2s ease-in-out infinite; }
}
@-webkit-keyframes mmStoryRing { 0% { -webkit-transform: scale(0.55); opacity: 0.8; } 75% { -webkit-transform: scale(1.5); opacity: 0; } 100% { opacity: 0; } }
@keyframes mmStoryRing { 0% { transform: scale(0.55); opacity: 0.8; } 75% { transform: scale(1.5); opacity: 0; } 100% { opacity: 0; } }
@-webkit-keyframes mmStoryBreathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@keyframes mmStoryBreathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@-webkit-keyframes mmStoryBeat { 0%, 100% { -webkit-transform: rotate(45deg) scale(1); } 50% { -webkit-transform: rotate(45deg) scale(1.16); } }
@keyframes mmStoryBeat { 0%, 100% { transform: rotate(45deg) scale(1); } 50% { transform: rotate(45deg) scale(1.16); } }

/* Legend swatch — matches the marker so the key is recognisable. */
.map-legend .story-key {
    color: #cc88ff;
    text-shadow: 0 0 6px #cc88ff, 0 0 12px rgba(204,136,255,0.7);
}

/* ── Map — tile hover tooltip (native title="" is enough;        */
/*    cursor:help signals interactivity)                          */
#mapMenu td[title] { cursor: help; }

/* Pixel-art rendering for all tile background images */
#mapMenu td {
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* Faint inner frame inset on the desktop map container */
/* Gold shoulder on the map frame. This pseudo-element used to draw a faint inner
   ring at inset:5px — that was the second line making the panel look double-framed. */
#mapMenu::before {
    content: '';
    position: absolute;
    inset: -1px;
    pointer-events: none;
    background-image: var(--shoulder-t), var(--shoulder-b);
    background-size: 100% 1px, 100% 1px;
    background-position: 0 0, 0 100%;
    background-repeat: no-repeat;
}

/* ── The shoulder, shared by panel frames + buttons ──────────────────────
   Owner picked this over the faceted/cut-corner border 2026-08-04: square frame,
   a 1px gold light along the top edge and a dimmer echo along the bottom, both
   fading out before the corners so the frame never closes into a hard gold box.
   Drawn on ::after over the border box (inset:-1px) so it sits ON the 1px border
   without needing markup changes. Every selector here has position:relative and a
   free ::after — CHECK BOTH before adding one. */
.mkstat .mk-gold > div::after,
.mkstat .mk-chip::after,
.map-zoom-btn::after,
.mkeq .eqslot::after,
.cbt-card::after,
.tut-card::after {
    content: '';
    position: absolute;
    inset: -1px;
    pointer-events: none;
    background-image: var(--shoulder-t), var(--shoulder-b);
    background-size: 100% 1px, 100% 1px;
    background-position: 0 0, 0 100%;
    background-repeat: no-repeat;
}
/* Hover/active brighten the whole shoulder rather than just recolouring the frame. */
.map-zoom-btn:hover::after { background-image: var(--shoulder-t), var(--shoulder-t); }

/* ── Full-map canvas (zoom 3) ─────────────────────────────────── */
#mapCanvas {
    display: block;
    border-radius: 3px;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* ── Desktop Compass Rose ─────────────────────────────────────── */
.dpad-wrap {
    display: inline-block;
    padding: 4px 4px;
    background: radial-gradient(ellipse 90% 90% at 50% 48%, #19163a 0%, #0e0c24 55%, #08060f 100%);
    border-radius: 50%;
    border: 1px solid #302858;
    box-shadow:
        0 0 0 1px #09071a,
        0 0 18px rgba(55,35,100,0.55),
        inset 0 1px 0 rgba(130,110,210,0.14),
        inset 0 -2px 4px rgba(0,0,0,0.5);
    vertical-align: middle;
}

.dpad-row-d {
    display: flex;
    justify-content: center;   /* centres rows 1 & 3 inside the width set by row 2 */
    align-items: center;       /* vertically centres shorter diagonal buttons in their row */
    gap: 2px;
    margin-bottom: 2px;
}
.dpad-row-d:last-child { margin-bottom: 0; }

/* Cardinal direction buttons */
.dpad-btn-d {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 23px;
    background: linear-gradient(175deg, #201b42 0%, #100d24 60%, #0b0e1e 100%);
    border: 1px solid #3c3468;
    border-bottom: 2px solid #161230;
    border-radius: 7px;
    color: #b0a8dc;
    font-size: 0.82em;
    font-weight: bold;
    text-decoration: none;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(150,130,220,0.16);
    transition: background 0.08s, border-color 0.08s, color 0.08s, transform 0.05s;
    user-select: none;
}
.dpad-btn-d:hover {
    background: linear-gradient(175deg, #2e2258 0%, #1c1640 60%, #100c28 100%);
    border-color: var(--mkgd);
    border-bottom-color: #8a6810;
    color: #f0d060;
    text-decoration: none;
    box-shadow: 0 0 10px rgba(var(--mkgd-rgb),0.3), inset 0 1px 0 rgba(255,220,80,0.1);
}
.dpad-btn-d:active {
    transform: translateY(1px);
    border-bottom-width: 1px;
}

/* Diagonal buttons — recessed, dimmer */
.dpad-diag-d {
    width: 23px;
    height: 20px;
    background: linear-gradient(175deg, #0e0c1e 0%, #080610 100%);
    border-color: #1e1a34;
    border-bottom-color: #0c0f20;
    color: #3e3a60;
    font-size: 0.9em;
    border-radius: 5px;
    box-shadow: none;
}
.dpad-diag-d:hover {
    background: linear-gradient(175deg, #1c1838 0%, #0e0c20 100%);
    border-color: var(--mkgd);
    border-bottom-color: #7a6010;
    color: #d0b840;
}

/* Center refresh button — round green gem */
.dpad-ctr-d {
    width: 26px;
    height: 26px;
    background: radial-gradient(circle at 40% 35%, #1f2f1a 0%, #0e1a0a 60%, #080f06 100%);
    border: 1px solid #2e4e28;
    border-bottom: 2px solid #131e0f;
    border-radius: 50%;
    color: #60b855;
    font-size: 1.1em;
    box-shadow: 0 0 8px rgba(60,160,40,0.2), inset 0 1px 0 rgba(120,220,80,0.14);
}
.dpad-ctr-d:hover {
    background: radial-gradient(circle at 40% 35%, #2a3f22 0%, #162410 60%, #0c1408 100%);
    border-color: #60a050;
    color: #90e870;
    box-shadow: 0 0 12px rgba(80,200,60,0.3);
}

/* ── Map zoom control buttons ─────────────────────────────────── */
.map-zoom-btn {
    display: inline-block;
    position: relative;   /* anchors the ::after shoulder */
    padding: 3px 10px;
    margin: 0 2px;
    background: linear-gradient(180deg, #1a1638 0%, #0e0c22 100%);
    border: 1px solid var(--brd);
    border-bottom: 2px solid #0e0c1c;
    border-radius: 0;
    color: #9088cc;
    font-size: 0.82em;
    font-family: var(--mk-display, Georgia, serif);
    letter-spacing: 0.04em;
    cursor: pointer;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.1s, border-color 0.1s, color 0.1s;
}

.map-zoom-btn:hover {
    background: linear-gradient(180deg, #221c48 0%, #111834 100%);
    border-color: var(--mkgd);
    color: var(--mkg);
    box-shadow: 0 0 9px rgba(var(--mkg-rgb),.18);
    text-decoration: none;
}

.map-zoom-btn:active {
    background: linear-gradient(180deg, #2a2050 0%, #18143a 100%);
    border-color: var(--mkgd);
    border-bottom-width: 1px;
    color: #f0d060;
}

/* ── Global boss notification bar (desktop) ───────────────────── */
#globalBossBar:not(:empty) {
    background: linear-gradient(135deg, #1a0505 0%, #0e0204 100%);
    border: 1px solid #5a1010;
    border-radius: 5px;
    box-shadow: 0 0 12px rgba(180,30,0,0.4), inset 0 0 16px rgba(100,0,0,0.25);
    padding: 5px 10px;
    margin: 4px 0;
    color: #e08070;
    font-size: 0.88em;
    line-height: 1.4;
}

#globalBossBar:not(:empty) a {
    color: #ff9060;
    font-weight: 600;
}

/* ── Map legend ────────────────────────────────────────────────────────────
   Was one undifferentiated run of 24 entries, 18 of them printed whether or
   not the marker was anywhere on screen — so the key advertised a Guild, a
   Raid and a World Boss to a player looking at none of them. maprender.php now
   keeps a set of the markers it actually drew and prints only those, grouped
   under the four shape categories, so the strip reads as four short labelled
   lists that shrink and grow with the view. The category names double as the
   shape key: the things under "Quests" are the diamonds.                    */
.map-legend {
    font-size: 0.92em;
    color: #666677;
    padding: 5px 6px 3px;
}
/* Two tracks: a fixed caption gutter, then the entries. ⚠ The entries need
   their OWN wrapping flex track — with everything in one track a group that
   wraps centres its second row under nothing in particular, instead of lining
   up beneath the first entry. */
.map-legend .lg-grp {
    display: -webkit-box; display: -ms-flexbox; display: flex;
    -webkit-box-align: start; -ms-flex-align: start; align-items: flex-start;
    line-height: 1.75;
}
/* A right-aligned gutter of category names, so the groups read as a column the
   eye can skip down instead of four ragged rows. The captions double as the
   shape key: the things under "Quests" are the diamonds. */
.map-legend .lg-cap {
    -ms-flex: 0 0 auto; flex: 0 0 auto;
    width: 64px; margin-right: 9px; text-align: right;
    color: #8a7a5a; font-size: 0.8em; font-weight: 700;
    letter-spacing: 0.13em; text-transform: uppercase;
}
.map-legend .lg-set {
    -ms-flex: 1 1 auto; flex: 1 1 auto;
    display: -webkit-box; display: -ms-flexbox; display: flex;
    -ms-flex-wrap: wrap; flex-wrap: wrap;
    -webkit-box-align: center; -ms-flex-align: center; align-items: center;
    gap: 1px 10px;
}
.map-legend .lg-item {
    display: -webkit-box; display: -ms-flexbox; display: flex;
    -webkit-box-align: center; -ms-flex-align: center; align-items: center;
    white-space: nowrap;
}
/* Zoom 2 has no marker shapes to show, so its key shows the cell colour. */
.map-legend .lg-sw { display: inline-block; width: 11px; height: 11px; border-radius: 2px; vertical-align: middle; }
/* ⚠ margin, not a leading space — .lg-item is a flex container, so a
   whitespace-only text node between two children is dropped entirely. */
.map-legend .lg-n { color: #55555f; font-size: 0.88em; margin-left: 3px; }

/* ── Class portrait in stat panel ───────────────────────────── */
.stat-portrait {
    display: block;
    margin: 0 auto 5px;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    -webkit-filter: drop-shadow(0 0 6px rgba(var(--mkgd-rgb),0.5));
    filter:         drop-shadow(0 0 6px rgba(var(--mkgd-rgb),0.5));
}

/* ── Keyboard nav hint (injected by JS) ─────────────────────── */
#keyHintBar {
    text-align: center;
    font-size: 0.70em;
    color: #2a2a3a;
    padding: 1px 0 3px;
    letter-spacing: 0.04em;
    -webkit-user-select: none;
    user-select: none;
}

/* ───── Crystalline combat enemy card — shared across all combat modes ───────
   Matches the faceted stat-panel language (.mkstat). Per-mode accent rides on
   an inline border-left-color; everything else is fixed here. */
.cbt-card { position: relative; background: linear-gradient(180deg,#101c30,#120e1c); border: 1px solid var(--brd); border-left: 3px solid #b03a3a; padding: 7px 11px 8px; margin-bottom: 6px; -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.05),0 2px 10px rgba(0,0,0,.5); box-shadow: inset 0 1px 0 rgba(255,255,255,.05),0 2px 10px rgba(0,0,0,.5); }
.cbt-card .cbt-top { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -webkit-box-align: baseline; -ms-flex-align: baseline; align-items: baseline; gap: 8px; -ms-flex-wrap: wrap; flex-wrap: wrap; }
.cbt-card .cbt-name { font-family: var(--mk-display, Georgia, serif); font-size: 1.12em; letter-spacing: .03em; text-shadow: 0 1px 2px #000; }
.cbt-card .cbt-lv { color: #9a8cb8; font-family: var(--mk-display, Georgia, serif); font-size: .82em; letter-spacing: .03em; white-space: nowrap; margin-left: auto; }
.cbt-card .cbt-hp { color: #a89cc0; font-size: .78em; letter-spacing: .04em; margin: 6px 0 3px; font-variant-numeric: tabular-nums; }
.cbt-card .cbt-track { position: relative; height: 10px; background: #070510; overflow: hidden; -webkit-box-shadow: inset 0 0 0 1px var(--brd),inset 0 1px 2px rgba(0,0,0,.8); box-shadow: inset 0 0 0 1px var(--brd),inset 0 1px 2px rgba(0,0,0,.8); }
.cbt-card .cbt-fill { position: absolute; top: 0; left: 0; height: 100%; -webkit-transition: width .35s ease; transition: width .35s ease; }

/* ── Combat diorama: enemy nameplate overlaid on the arena (mockup style) ─────
   The arena is the hero — the enemy's name, weakness gems + HP ride on a scrim
   at the top of the terrain stage instead of a separate card floating above. */
.combat-arena .cbt-np { position: absolute; top: 0; left: 0; right: 0; z-index: 4; padding: 8px 13px 24px; background: -webkit-gradient(linear,left top,left bottom,color-stop(0,rgba(6,4,12,.86)),color-stop(55%,rgba(6,4,12,.5)),to(transparent)); background: linear-gradient(180deg,rgba(6,4,12,.86) 0%,rgba(6,4,12,.5) 55%,transparent 100%); pointer-events: none; }
.combat-arena .cbt-np-row { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -webkit-box-align: baseline; -ms-flex-align: baseline; align-items: baseline; gap: 8px; }
.combat-arena .cbt-np-name { font-family: var(--mk-display, Georgia, serif); font-size: 1.06em; line-height: 1.08; letter-spacing: .02em; text-shadow: 0 1px 3px #000, 0 0 12px rgba(0,0,0,.6); }
.combat-arena .cbt-np-weak { font-size: .82em; white-space: nowrap; text-shadow: 0 1px 2px #000; }
.combat-arena .cbt-np-track { position: relative; height: 12px; margin: 7px 0 3px; background: #050309; -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd); -webkit-clip-path: polygon(3px 0,100% 0,calc(100% - 3px) 100%,0 100%); clip-path: polygon(3px 0,100% 0,calc(100% - 3px) 100%,0 100%); }
.combat-arena .cbt-np-fill { position: absolute; top: 0; left: 0; height: 100%; -webkit-transition: width .35s ease; transition: width .35s ease; }
.combat-arena .cbt-np-meta { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; font-family: var(--mk-display, Georgia, serif); font-size: .72em; letter-spacing: .05em; text-transform: uppercase; color: #d8ccee; text-shadow: 0 1px 2px #000; }

/* Weakness / resist element GEMS on the nameplate (mockup "WEAK ◆" at a glance) */
.cbt-gems { display: -webkit-inline-box; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 3px; vertical-align: middle; }
.cbt-gems .cbt-gem-lab { font-family: var(--mk-mono, ui-monospace, monospace); font-size: .72em; letter-spacing: .1em; text-transform: uppercase; color: #a0c0dc; }
.cbt-gems .cbt-gem { font-size: 1.12em; line-height: 1; }
.cbt-gems .cbt-gem-res { opacity: .72; }

/* ── Combat lower deck: etched-glass log (left) + player vitals (right) ─────── */
.cbt-lower { display: -webkit-box; display: -ms-flexbox; display: flex; gap: 8px; margin: 7px 0; -webkit-box-align: stretch; -ms-flex-align: stretch; align-items: stretch; }
.cbt-log { -webkit-box-flex: 1; -ms-flex: 1.4 1 0px; flex: 1.4 1 0; min-width: 0; background: linear-gradient(180deg,rgba(22,17,34,.5),rgba(11,8,18,.62)); border: 1px solid var(--brd); padding: 4px 6px; }
.cbt-vit { -ms-flex: 1 1 0px; flex: 1 1 0; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; gap: 10px; padding: 2px 2px 2px 4px; }
/* ≤480 (phones): the log|vitals row is too cramped side-by-side — stack them.
   Hotbar: 10 hexes at min-width 34px wrap 9+1 (orphan) at ~344px content width;
   force exactly 5 per row instead. !important because the hotbar's own rules
   ride in the inline #hbarStyle block, which follows main.css in the cascade. */
@media (max-width: 480px) {
    .cbt-lower { -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; }
    .cbt-vit { padding: 4px 2px 2px; }
    .hbar .hbs { -webkit-box-flex: 1 !important; -ms-flex: 1 1 18% !important; flex: 1 1 18% !important; min-width: 17% !important; }
}
.cbt-v-lab { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -webkit-box-align: baseline; -ms-flex-align: baseline; align-items: baseline; gap: 6px; font-size: .7em; letter-spacing: .04em; text-transform: uppercase; color: #9a8cb8; margin-bottom: 3px; white-space: nowrap; }
.cbt-v-lab b { font-family: 'IM Fell English',Georgia,serif; font-weight: normal; color: #eaf3fb; letter-spacing: 0; }
.cbt-v-track { position: relative; height: 9px; background: #070510; overflow: hidden; -webkit-box-shadow: inset 0 0 0 1px var(--brd),inset 0 1px 2px rgba(0,0,0,.8); box-shadow: inset 0 0 0 1px var(--brd),inset 0 1px 2px rgba(0,0,0,.8); }
.cbt-v-fill { position: absolute; top: 0; left: 0; height: 100%; -webkit-transition: width .35s ease; transition: width .35s ease; }

/* ───── Combat Arena (player left · monster right) ─────────── */
.combat-arena {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-orient: horizontal;
    -webkit-box-direction: normal;
        -ms-flex-direction: row;
            flex-direction: row;
    -webkit-box-align: flex-end;
        -ms-flex-align: flex-end;
            align-items: flex-end;
    -webkit-box-pack: space-around;
        -ms-flex-pack: distribute;
            justify-content: space-around;
    position: relative;
    overflow: hidden;
    padding: 50px 16px 10px;
    min-height: 176px;
    /* Border language 2026-08-05 — square frame on the tokens, matching every
       other panel. Was a 14px faceted clip-path with a TRANSLUCENT violet rim
       (rgba(150,110,210,.32)) plus a 10% gold ring: over a bright arena
       background that washed out to a pale grey line that matched nothing.
       A solid 1px var(--brd) holds its colour against any backdrop.
       The inner vignette stays — it is what seats the sprites in the scene. */
    border: 1px solid var(--brd);
    -webkit-box-shadow: inset 0 0 26px 2px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.05);
            box-shadow: inset 0 0 26px 2px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.05);
}
/* Gold shoulder for the arena. Deliberately on ::after, NOT a background layer:
   this element carries the SCENE image, so a background-image here would fight
   it — and an inline arena background would win the cascade and silently drop
   the shoulder. z-index sits under .cbt-np (4) so the nameplate stays on top. */
.combat-arena::after {
    content: '';
    position: absolute;
    left: 0; right: 0; top: 0; bottom: 0;
    /* ABOVE .cbt-np (z-index 4): the nameplate lays a near-opaque dark gradient
       across the top of the arena, which swallowed the top shoulder and left
       only the bottom one showing — worse than having none. pointer-events:none
       keeps it inert. */
    z-index: 5;
    pointer-events: none;
    background-image: var(--shoulder-t), var(--shoulder-b);
    background-size: 100% 1px, 100% 1px;
    background-position: 0 0, 0 100%;
    background-repeat: no-repeat;
}

/* Cinematic ground — a grounding shadow band + warm spotlight so the taller
   arena reads as combatants standing on a lit stage, not floating in a void. */
.combat-arena::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 42px;
    background:
        radial-gradient(ellipse 62% 80% at 50% 118%, rgba(255,224,150,.20), rgba(255,224,150,0) 70%),
        linear-gradient(180deg, rgba(6,4,12,0) 0%, rgba(6,4,12,.42) 62%, rgba(6,4,12,.72) 100%);
    pointer-events: none;
}

/* ───── Sprites (shared size) ──────────────────────────────── */
.mon-sprite-wrap,
.plr-sprite-wrap { text-align: center; }

/* ── Sprite cosmetics (Patch 2.6 "Resonance") ── */
.plr-sprite-wrap { position: relative; }
.plr-sprite-wrap .plr-sprite { position: relative; z-index: 1; }
/* #269: the Tamer's pet is its OWN figure in the arena's row, right after the Tamer (owner, 30 Sep: "Tamer > Pet and
   Monster") — a flex sibling, never absolute, so the arena's space-around seats it between the Tamer and the bolt.
   The size is inline, set by the pet's tier (pet_helper.php); the negative margin keeps it at its master's side. */
.combat-arena .pet-sprite-wrap { display: flex; align-items: flex-end; margin-left: -12px; }
.combat-arena .pet-sprite { display: block; image-rendering: auto; pointer-events: none; }
.cos-aura { position: absolute; left: 50%; top: 50%; width: 104px; height: 104px; -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%); border-radius: 50%; pointer-events: none; z-index: 0; }
.cos-ember { background: radial-gradient(circle, rgba(255,140,40,.45), transparent 68%); -webkit-animation: cosPulse 1.8s ease-in-out infinite; animation: cosPulse 1.8s ease-in-out infinite; }
.cos-void  { background: radial-gradient(circle, rgba(150,60,220,.50), transparent 68%); -webkit-animation: cosPulse 2.2s ease-in-out infinite; animation: cosPulse 2.2s ease-in-out infinite; }
.cos-holy  { background: radial-gradient(circle, rgba(255,240,170,.50), transparent 68%); -webkit-animation: cosPulse 2.0s ease-in-out infinite; animation: cosPulse 2.0s ease-in-out infinite; }
.cos-champion { background: radial-gradient(circle, rgba(255,200,60,.55), transparent 66%); -webkit-animation: cosPulse 1.6s ease-in-out infinite; animation: cosPulse 1.6s ease-in-out infinite; }
/* Hardcore shop auras (11 Oct 2026): bought with Hardcore points, worn on any side */
.cos-skull { background: radial-gradient(circle, rgba(221,68,68,.55), rgba(90,0,0,.25) 45%, transparent 68%); -webkit-animation: cosPulse 1.4s ease-in-out infinite; animation: cosPulse 1.4s ease-in-out infinite; }
.cos-ashen { background: radial-gradient(circle, rgba(170,170,160,.45), rgba(80,80,80,.25) 50%, transparent 70%); -webkit-animation: cosPulse 3.2s ease-in-out infinite; animation: cosPulse 3.2s ease-in-out infinite; }
@-webkit-keyframes cosPulse { 0%,100% { -webkit-transform: translate(-50%,-50%) scale(1); opacity: .7; } 50% { -webkit-transform: translate(-50%,-50%) scale(1.14); opacity: 1; } }
@keyframes cosPulse { 0%,100% { transform: translate(-50%,-50%) scale(1); opacity: .7; } 50% { transform: translate(-50%,-50%) scale(1.14); opacity: 1; } }

/* ───── Spell-cast effects — a one-shot, element-themed burst painted over the target ───── */
.mon-sprite-wrap { position: relative; }   /* monster art is drawn facing LEFT (toward the left-side hero) — no flip needed */
.spell-fx { position: absolute; left: 50%; bottom: 3px; width: 62px; height: 70px; margin-left: -31px; pointer-events: none; z-index: 3; -webkit-animation: spellFx .9s ease-out forwards; animation: spellFx .9s ease-out forwards; }
.spell-fx::before, .spell-fx::after { content: ''; position: absolute; }
@-webkit-keyframes spellFx { 0%{opacity:0} 14%{opacity:1} 76%{opacity:1} 100%{opacity:0} }
@keyframes spellFx { 0%{opacity:0} 14%{opacity:1} 76%{opacity:1} 100%{opacity:0} }

/* Generated sheets — images/fx/<key>.png, N 64×64 frames side by side (installers/fx_generate.php). actionFxHtml() hands the
   strip in --fx and the frame count in .fx-nN; the div STEPS onto each frame in place while spellFx fades it in and out.
   ⚠⚠ steps(N, jump-none) EMITS N VALUES, NOT N+1 (owner report 2026-09-05: "the animations scroll across the enemy, they
   need to animate ON the enemy"). Every rule here used steps(frames-1), so the sampled background-positions landed BETWEEN
   frame boundaries — a 5-frame strip stepped to 0 / 33.3 / 66.7 / 100% when its frames sit at 0 / 25 / 50 / 75 / 100%, so two
   of the four "frames" were half of one drawing beside half of the next, and the burst read as a strip sliding sideways
   rather than an animation playing on the target. The frame count and the step count are THE SAME NUMBER.
   ⚠ .fx-n5 is spelled out even though it matches the base: actionFxHtml() emits fx-nN for every N, and a class with no rule
   silently inherits whatever the base happens to be. Today that is right for 5 and wrong for everything else. */
/* Owner, 2026-09-05: "the burst sits too low on the sprite, center it, and make them slightly larger."
   ⚠⚠ CENTRED ON THE SPRITE, NOT SAT ON ITS FEET. The base .spell-fx anchors bottom:3px and centres
   horizontally with margin-left — right for the hand-drawn bursts, which are drawn to rise from the
   ground, and wrong for a square sheet that should play over the middle of the target. This rule
   therefore has to CLEAR both of those (bottom:auto, margin-left:0) before its own centring can work;
   leaving either in place would fight the transform and drag the burst back down or sideways.
   ★ translate(-50%,-50%) off left/top 50% is the same pattern .cos-aura already uses to sit on a
     sprite, and it stays correct if the size below is ever changed again — no margin to keep in sync.
   ⚠ Safe only because spellFx animates OPACITY ONLY and fxSheet animates background-position. If
     either ever animates transform, this static transform is the thing it would silently overwrite.
   Size 64 -> 80px against a 96px sprite (67% -> 83%). Deliberately FLAT, not scaled at the 116/176px
   sprite breakpoints: "slightly larger" is a modest global bump, and tracking the sprite would have
   made it 144px on a wide screen, which is a different request. */
.spell-fx.fx-sheet { width: 80px; height: 80px; left: 50%; top: 50%; bottom: auto; margin-left: 0; -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%); background-image: var(--fx); background-repeat: no-repeat; background-position: 0 0; background-size: 500% 100%; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; -webkit-animation: spellFx .9s ease-out forwards, fxSheet .9s steps(5, jump-none) forwards; animation: spellFx .9s ease-out forwards, fxSheet .9s steps(5, jump-none) forwards; }
.spell-fx.fx-sheet.fx-n3 { background-size: 300% 100%; -webkit-animation-timing-function: ease-out, steps(3, jump-none); animation-timing-function: ease-out, steps(3, jump-none); }
.spell-fx.fx-sheet.fx-n4 { background-size: 400% 100%; -webkit-animation-timing-function: ease-out, steps(4, jump-none); animation-timing-function: ease-out, steps(4, jump-none); }
.spell-fx.fx-sheet.fx-n5 { background-size: 500% 100%; -webkit-animation-timing-function: ease-out, steps(5, jump-none); animation-timing-function: ease-out, steps(5, jump-none); }
.spell-fx.fx-sheet.fx-n6 { background-size: 600% 100%; -webkit-animation-timing-function: ease-out, steps(6, jump-none); animation-timing-function: ease-out, steps(6, jump-none); }
.spell-fx.fx-sheet.fx-n7 { background-size: 700% 100%; -webkit-animation-timing-function: ease-out, steps(7, jump-none); animation-timing-function: ease-out, steps(7, jump-none); }
.spell-fx.fx-sheet.fx-n8 { background-size: 800% 100%; -webkit-animation-timing-function: ease-out, steps(8, jump-none); animation-timing-function: ease-out, steps(8, jump-none); }
@-webkit-keyframes fxSheet { from { background-position: 0 0; } to { background-position: 100% 0; } }
@keyframes fxSheet { from { background-position: 0 0; } to { background-position: 100% 0; } }

/* Fire — flame tongues licking upward */
.fx-fire { background: radial-gradient(ellipse at 50% 96%, rgba(255,170,40,.5), rgba(255,70,0,.16) 48%, transparent 72%); }
.fx-fire::before, .fx-fire::after { bottom: 5px; width: 18px; height: 34px; background: linear-gradient(to top, #ffd34a, #ff5410 58%, rgba(255,84,16,0)); border-radius: 50% 50% 46% 46% / 74% 74% 26% 26%; -webkit-animation: fxFireFlick .24s ease-in-out infinite alternate; animation: fxFireFlick .24s ease-in-out infinite alternate; }
.fx-fire::before { left: 22%; height: 26px; }
.fx-fire::after  { left: 52%; }
@-webkit-keyframes fxFireFlick { from{-webkit-transform:scaleY(.85) translateY(2px)} to{-webkit-transform:scaleY(1.18) translateY(-3px)} }
@keyframes fxFireFlick { from{transform:scaleY(.85) translateY(2px)} to{transform:scaleY(1.18) translateY(-3px)} }

/* Water — a splash ring expands, a droplet falls through */
.fx-water { background: radial-gradient(ellipse at 50% 55%, rgba(70,185,255,.45), rgba(0,110,220,.12) 52%, transparent 74%); }
.fx-water::before { left: 50%; top: 14px; width: 34px; height: 34px; margin-left: -17px; border: 3px solid rgba(130,215,255,.75); border-radius: 50%; -webkit-animation: fxRing .9s ease-out forwards; animation: fxRing .9s ease-out forwards; }
.fx-water::after { left: 50%; top: 4px; width: 8px; height: 8px; margin-left: -4px; background: radial-gradient(circle, #bfeaff, #3aa0ee); border-radius: 50%; -webkit-animation: fxDrop .9s ease-in forwards; animation: fxDrop .9s ease-in forwards; }
@-webkit-keyframes fxRing { 0%{-webkit-transform:scale(.3);opacity:.9} 100%{-webkit-transform:scale(1.5);opacity:0} } @keyframes fxRing { 0%{transform:scale(.3);opacity:.9} 100%{transform:scale(1.5);opacity:0} }
@-webkit-keyframes fxDrop { 0%{-webkit-transform:translateY(-8px);opacity:0} 30%{opacity:1} 100%{-webkit-transform:translateY(36px);opacity:0} } @keyframes fxDrop { 0%{transform:translateY(-8px);opacity:0} 30%{opacity:1} 100%{transform:translateY(36px);opacity:0} }

/* Earth — rock chunks erupt, the ground shakes */
.fx-earth { background: radial-gradient(ellipse at 50% 100%, rgba(170,110,50,.5), rgba(110,70,30,.18) 50%, transparent 72%); -webkit-animation: spellFx .9s ease-out forwards, fxShake .42s ease-in-out; animation: spellFx .9s ease-out forwards, fxShake .42s ease-in-out; }
.fx-earth::before, .fx-earth::after { bottom: 8px; width: 14px; height: 12px; background: linear-gradient(#b07a44, #6e4a22); border-radius: 3px; -webkit-animation: fxRock .8s ease-out forwards; animation: fxRock .8s ease-out forwards; }
.fx-earth::before { left: 26%; }
.fx-earth::after  { left: 54%; width: 11px; height: 10px; -webkit-animation-delay: .06s; animation-delay: .06s; }
@-webkit-keyframes fxRock { 0%{-webkit-transform:translateY(14px) scale(.5);opacity:0} 30%{opacity:1} 100%{-webkit-transform:translateY(-12px) rotate(45deg);opacity:0} } @keyframes fxRock { 0%{transform:translateY(14px) scale(.5);opacity:0} 30%{opacity:1} 100%{transform:translateY(-12px) rotate(45deg);opacity:0} }
@-webkit-keyframes fxShake { 0%,100%{-webkit-transform:translateX(0)} 25%{-webkit-transform:translateX(-3px)} 75%{-webkit-transform:translateX(3px)} } @keyframes fxShake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-3px)} 75%{transform:translateX(3px)} }

/* Wind — twin arcs whirl around the foe */
.fx-wind { background: radial-gradient(ellipse at 50% 55%, rgba(150,255,170,.30), transparent 70%); -webkit-animation: spellFx .9s ease-out forwards, fxSpin .9s linear; animation: spellFx .9s ease-out forwards, fxSpin .9s linear; }
.fx-wind::before, .fx-wind::after { left: 50%; top: 50%; width: 44px; height: 44px; margin: -22px 0 0 -22px; border: 3px solid transparent; border-top-color: rgba(190,255,200,.8); border-right-color: rgba(150,235,180,.5); border-radius: 50%; }
.fx-wind::after { width: 28px; height: 28px; margin: -14px 0 0 -14px; -webkit-transform: rotate(140deg); transform: rotate(140deg); }
@-webkit-keyframes fxSpin { from{-webkit-transform:rotate(0)} to{-webkit-transform:rotate(330deg)} } @keyframes fxSpin { from{transform:rotate(0)} to{transform:rotate(330deg)} }

/* Lightning — a forked bolt strikes with a flash */
.fx-lightning { background: radial-gradient(ellipse at 50% 45%, rgba(255,240,130,.4), transparent 66%); -webkit-animation: fxFlash .55s ease-out forwards; animation: fxFlash .55s ease-out forwards; }
.fx-lightning::before { left: 50%; top: 2px; width: 14px; height: 66px; margin-left: -7px; background: linear-gradient(#fffbe0, #ffe23a); -webkit-box-shadow: 0 0 8px #ffe23a; box-shadow: 0 0 8px #ffe23a; -webkit-clip-path: polygon(42% 0,100% 40%,54% 44%,88% 100%,0 54%,46% 50%); clip-path: polygon(42% 0,100% 40%,54% 44%,88% 100%,0 54%,46% 50%); }
@-webkit-keyframes fxFlash { 0%{opacity:0} 8%{opacity:1} 26%{opacity:.25} 42%{opacity:.95} 60%{opacity:.4} 100%{opacity:0} } @keyframes fxFlash { 0%{opacity:0} 8%{opacity:1} 26%{opacity:.25} 42%{opacity:.95} 60%{opacity:.4} 100%{opacity:0} }

/* Dark — a void orb implodes then bursts */
.fx-dark { background: radial-gradient(circle, rgba(60,10,90,.1), rgba(125,45,205,.55) 40%, rgba(28,0,48,.72) 70%, transparent 80%); -webkit-animation: spellFx .9s ease-out forwards, fxImplode .9s ease-out; animation: spellFx .9s ease-out forwards, fxImplode .9s ease-out; }
.fx-dark::before { left: 50%; top: 50%; width: 50px; height: 50px; margin: -25px 0 0 -25px; border-radius: 50%; -webkit-box-shadow: inset 0 0 14px 4px rgba(160,70,230,.6); box-shadow: inset 0 0 14px 4px rgba(160,70,230,.6); -webkit-animation: fxImplode .9s ease-out; animation: fxImplode .9s ease-out; }
@-webkit-keyframes fxImplode { 0%{-webkit-transform:scale(1.45);opacity:.2} 45%{-webkit-transform:scale(.5);opacity:1} 100%{-webkit-transform:scale(1.25);opacity:0} } @keyframes fxImplode { 0%{transform:scale(1.45);opacity:.2} 45%{transform:scale(.5);opacity:1} 100%{transform:scale(1.25);opacity:0} }

/* Light — a radiant sunburst with spinning rays */
.fx-light { background: radial-gradient(circle, rgba(255,250,210,.7), rgba(255,225,120,.3) 40%, transparent 68%); -webkit-animation: spellFx .9s ease-out forwards, fxBurst .9s ease-out; animation: spellFx .9s ease-out forwards, fxBurst .9s ease-out; }
.fx-light::before { left: 50%; top: 50%; width: 72px; height: 72px; margin: -36px 0 0 -36px; background: repeating-conic-gradient(from 0deg, rgba(255,245,190,.55) 0deg 7deg, transparent 7deg 26deg); border-radius: 50%; -webkit-animation: fxRays .9s ease-out forwards; animation: fxRays .9s ease-out forwards; }
@-webkit-keyframes fxBurst { 0%{-webkit-transform:scale(.4);opacity:0} 30%{opacity:1} 100%{-webkit-transform:scale(1.3);opacity:0} } @keyframes fxBurst { 0%{transform:scale(.4);opacity:0} 30%{opacity:1} 100%{transform:scale(1.3);opacity:0} }
@-webkit-keyframes fxRays { 0%{-webkit-transform:scale(.3) rotate(0);opacity:0} 30%{opacity:.8} 100%{-webkit-transform:scale(1.45) rotate(45deg);opacity:0} } @keyframes fxRays { 0%{transform:scale(.3) rotate(0);opacity:0} 30%{opacity:.8} 100%{transform:scale(1.45) rotate(45deg);opacity:0} }

/* ───── Action-type impact FX — physical/martial actions get their own language ─────
   Painted over the target exactly like the element bursts (same .spell-fx base).
   The slash streak keyframe reads its angle from --ang so one keyframe serves
   the single steel slash and both arms of the crossed gold arte. */

/* Weapon attack — one steel slash cuts through + an impact spark */
.fx-slash { -webkit-animation: spellFx .55s ease-out forwards; animation: spellFx .55s ease-out forwards; }
.fx-slash::before {
    left: 50%; top: 50%; width: 70px; height: 7px; margin: -4px 0 0 -35px;
    --ang: -36deg;
    background: linear-gradient(90deg, transparent, #e8eeff 14%, #ffffff 50%, #9fb2cc 86%, transparent);
    border-radius: 4px;
    -webkit-box-shadow: 0 0 10px rgba(225,238,255,.95), 0 0 24px rgba(180,210,255,.5);
    box-shadow: 0 0 10px rgba(225,238,255,.95), 0 0 24px rgba(180,210,255,.5);
    -webkit-transform-origin: left center; transform-origin: left center;
    -webkit-animation: fxSlashCut .4s cubic-bezier(.2,.85,.3,1) forwards; animation: fxSlashCut .4s cubic-bezier(.2,.85,.3,1) forwards;
}
.fx-slash::after {
    left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px; opacity: 0;
    background: radial-gradient(circle, #ffffff, rgba(255,255,255,.45) 42%, transparent 70%);
    border-radius: 50%;
    -webkit-animation: fxSpark .32s ease-out .16s forwards; animation: fxSpark .32s ease-out .16s forwards;
}
@-webkit-keyframes fxSlashCut { 0%{-webkit-transform:rotate(var(--ang,-36deg)) scaleX(0);opacity:0} 22%{opacity:1} 58%{-webkit-transform:rotate(var(--ang,-36deg)) scaleX(1.05)} 100%{-webkit-transform:rotate(var(--ang,-36deg)) scaleX(1);opacity:0} }
@keyframes fxSlashCut { 0%{transform:rotate(var(--ang,-36deg)) scaleX(0);opacity:0} 22%{opacity:1} 58%{transform:rotate(var(--ang,-36deg)) scaleX(1.05)} 100%{transform:rotate(var(--ang,-36deg)) scaleX(1);opacity:0} }
@-webkit-keyframes fxSpark { 0%{-webkit-transform:scale(.2);opacity:0} 40%{opacity:1} 100%{-webkit-transform:scale(1.5);opacity:0} }
@keyframes fxSpark { 0%{transform:scale(.2);opacity:0} 40%{opacity:1} 100%{transform:scale(1.5);opacity:0} }

/* Weapon Arte — two crossed gold arcs land a beat apart, the frame judders */
.fx-arte {
    -webkit-animation: spellFx .7s ease-out forwards, fxShake .3s ease-in-out .12s;
    animation: spellFx .7s ease-out forwards, fxShake .3s ease-in-out .12s;
}
.fx-arte::before, .fx-arte::after {
    left: 50%; top: 50%; width: 70px; height: 7px; margin: -4px 0 0 -35px;
    background: linear-gradient(90deg, transparent, #fff2c0 14%, #ffd75e 50%, #b8902a 86%, transparent);
    border-radius: 4px;
    -webkit-box-shadow: 0 0 10px rgba(255,220,110,.95), 0 0 24px rgba(var(--mkg-rgb),.5);
    box-shadow: 0 0 10px rgba(255,220,110,.95), 0 0 24px rgba(var(--mkg-rgb),.5);
    -webkit-transform-origin: left center; transform-origin: left center;
    -webkit-animation: fxSlashCut .4s cubic-bezier(.2,.85,.3,1) forwards; animation: fxSlashCut .4s cubic-bezier(.2,.85,.3,1) forwards;
}
.fx-arte::before { --ang: -36deg; }
.fx-arte::after  { --ang: 36deg; -webkit-animation-delay: .13s; animation-delay: .13s; }

/* Ability — a rune ring erupts with a spinning gold glyph (class energy) */
.fx-abil {
    background: radial-gradient(circle at 50% 55%, rgba(150,100,220,.3), transparent 68%);
    -webkit-animation: spellFx .8s ease-out forwards; animation: spellFx .8s ease-out forwards;
}
.fx-abil::before {
    left: 50%; top: 50%; width: 46px; height: 46px; margin: -23px 0 0 -23px;
    border: 3px solid rgba(185,135,255,.85); border-radius: 50%;
    -webkit-box-shadow: 0 0 12px rgba(160,100,240,.7), inset 0 0 8px rgba(160,100,240,.5);
    box-shadow: 0 0 12px rgba(160,100,240,.7), inset 0 0 8px rgba(160,100,240,.5);
    -webkit-animation: fxRing .7s ease-out forwards; animation: fxRing .7s ease-out forwards;
}
.fx-abil::after {
    left: 50%; top: 50%; width: 24px; height: 24px; margin: -12px 0 0 -12px;
    border: 2px solid var(--mkg); -webkit-box-shadow: 0 0 8px rgba(var(--mkg-rgb),.7); box-shadow: 0 0 8px rgba(var(--mkg-rgb),.7);
    -webkit-animation: fxGlyph .75s ease-out forwards; animation: fxGlyph .75s ease-out forwards;
}
@-webkit-keyframes fxGlyph { 0%{-webkit-transform:rotate(0) scale(.3);opacity:0} 35%{opacity:1} 100%{-webkit-transform:rotate(135deg) scale(1.2);opacity:0} }
@keyframes fxGlyph { 0%{transform:rotate(0) scale(.3);opacity:0} 35%{opacity:1} 100%{transform:rotate(135deg) scale(1.2);opacity:0} }

/* ── Elemental bolt — a projectile flies hero → target before the burst lands ──
   Emitted into .combat-arena (position:relative, overflow:hidden) by
   actionBoltHtml() for elemental actions only; melee actions keep the lunge.
   One base + per-element tints via --c1 (core) / --c2 (glow). The burst's own
   fade-in (~0.13s) overlaps the arrival so the impact reads as the bolt
   detonating. Unknown elements fall back to the violet default. */
.fx-bolt {
    position: absolute; left: 18%; bottom: 36%; width: 14px; height: 14px;
    border-radius: 50%; pointer-events: none; z-index: 4; opacity: 0;
    background: radial-gradient(circle at 35% 35%, var(--c1,#f2eaff), var(--c2,#8a55dd) 72%);
    -webkit-box-shadow: 0 0 10px var(--c2,#8a55dd), 0 0 22px var(--c2,#8a55dd);
    box-shadow: 0 0 10px var(--c2,#8a55dd), 0 0 22px var(--c2,#8a55dd);
    -webkit-animation: fxBoltTravel .3s ease-in forwards;
    animation: fxBoltTravel .3s ease-in forwards;
}
.fx-bolt::after {   /* motion trail */
    content: ''; position: absolute; right: 100%; top: 50%; width: 34px; height: 4px;
    margin-top: -2px; border-radius: 2px; opacity: .75;
    background: linear-gradient(90deg, transparent, var(--c2,#8a55dd));
}
@-webkit-keyframes fxBoltTravel {
    0%   { left: 18%; opacity: 0; -webkit-transform: scale(.5); }
    16%  { opacity: 1; }
    82%  { opacity: 1; }
    100% { left: 76%; opacity: 0; -webkit-transform: scale(1.1); }
}
@keyframes fxBoltTravel {
    0%   { left: 18%; opacity: 0; transform: scale(.5); }
    16%  { opacity: 1; }
    82%  { opacity: 1; }
    100% { left: 76%; opacity: 0; transform: scale(1.1); }
}
.bolt-fire      { --c1: #ffe9a0; --c2: #ff5410; }
.bolt-water     { --c1: #e8f8ff; --c2: #3aa0ee; }
.bolt-earth     { --c1: #e0b98a; --c2: #8a5a28; border-radius: 4px; }   /* a hurled rock, not an orb */
.bolt-wind      { --c1: #eafff0; --c2: #5abb7a; }
.bolt-lightning { --c1: #fffbe0; --c2: #ffd728; }
.bolt-dark      { --c1: #c9a0f0; --c2: #5a1a9a; }
.bolt-light     { --c1: #ffffff; --c2: #ffd75e; }

.mon-sprite {
    image-rendering: auto;               /* raster PNG enemy sprites downscale smoothly */
    width:  96px;
    height: 96px;
    display: block;
}
.plr-sprite {
    image-rendering: auto;               /* raster PNG class sprites downscale smoothly */
    width:  96px;
    height: 96px;
    display: block;
}

@-webkit-keyframes monIdle {
    0%, 100% { -webkit-transform: translateY(0px);   }
    50%       { -webkit-transform: translateY(-5px);  }
}
@keyframes monIdle {
    0%, 100% { transform: translateY(0px);  }
    50%       { transform: translateY(-5px); }
}

@-webkit-keyframes monHurt {
    0%   { -webkit-transform: translateX(0);     filter: brightness(1); }
    20%  { -webkit-transform: translateX(-9px);  filter: brightness(4) sepia(1) hue-rotate(-20deg); }
    45%  { -webkit-transform: translateX(5px);   filter: brightness(2); }
    65%  { -webkit-transform: translateX(-3px); }
    100% { -webkit-transform: translateX(0);     filter: brightness(1); }
}
@keyframes monHurt {
    0%   { transform: translateX(0);    filter: brightness(1); }
    20%  { transform: translateX(-9px); filter: brightness(4) sepia(1) hue-rotate(-20deg); }
    45%  { transform: translateX(5px);  filter: brightness(2); }
    65%  { transform: translateX(-3px); }
    100% { transform: translateX(0);    filter: brightness(1); }
}

@-webkit-keyframes monCrit {
    0%   { -webkit-transform: scale(1)    translateX(0);    filter: brightness(1); }
    10%  { -webkit-transform: scale(0.88) translateX(-13px); filter: brightness(6) saturate(0); }
    30%  { -webkit-transform: scale(1.06) translateX(7px);  filter: brightness(3) sepia(1) hue-rotate(-20deg) saturate(3); }
    55%  { -webkit-transform: scale(0.96) translateX(-4px); filter: brightness(2); }
    75%  { -webkit-transform: scale(1.02) translateX(2px); }
    100% { -webkit-transform: scale(1)    translateX(0);    filter: brightness(1); }
}
@keyframes monCrit {
    0%   { transform: scale(1)    translateX(0);     filter: brightness(1); }
    10%  { transform: scale(0.88) translateX(-13px); filter: brightness(6) saturate(0); }
    30%  { transform: scale(1.06) translateX(7px);   filter: brightness(3) sepia(1) hue-rotate(-20deg) saturate(3); }
    55%  { transform: scale(0.96) translateX(-4px);  filter: brightness(2); }
    75%  { transform: scale(1.02) translateX(2px); }
    100% { transform: scale(1)    translateX(0);     filter: brightness(1); }
}

@-webkit-keyframes monDie {
    0%   { -webkit-transform: translateY(0)  rotate(0deg);  opacity: 1;   filter: brightness(1); }
    20%  { -webkit-transform: translateY(-6px) rotate(-6deg); filter: brightness(4) sepia(1) hue-rotate(-20deg); }
    55%  { -webkit-transform: translateY(2px)  rotate(12deg); opacity: 0.65; }
    80%  { -webkit-transform: translateY(8px)  rotate(22deg); opacity: 0.30; }
    100% { -webkit-transform: translateY(14px) rotate(32deg); opacity: 0;   filter: brightness(0); }
}
@keyframes monDie {
    0%   { transform: translateY(0)   rotate(0deg);  opacity: 1;    filter: brightness(1); }
    20%  { transform: translateY(-6px) rotate(-6deg); filter: brightness(4) sepia(1) hue-rotate(-20deg); }
    55%  { transform: translateY(2px)  rotate(12deg); opacity: 0.65; }
    80%  { transform: translateY(8px)  rotate(22deg); opacity: 0.30; }
    100% { transform: translateY(14px) rotate(32deg); opacity: 0;    filter: brightness(0); }
}

@-webkit-keyframes monCast {
    0%   { -webkit-transform: scale(1);    filter: brightness(1); }
    25%  { -webkit-transform: scale(1.10); filter: brightness(1.6) hue-rotate(40deg); }
    50%  { -webkit-transform: scale(0.95); filter: brightness(1.25); }
    75%  { -webkit-transform: scale(1.05); }
    100% { -webkit-transform: scale(1);    filter: brightness(1); }
}
@keyframes monCast {
    0%   { transform: scale(1);    filter: brightness(1); }
    25%  { transform: scale(1.10); filter: brightness(1.6) hue-rotate(40deg); }
    50%  { transform: scale(0.95); filter: brightness(1.25); }
    75%  { transform: scale(1.05); }
    100% { transform: scale(1);    filter: brightness(1); }
}

@-webkit-keyframes playerHitFlash {
    0%   { background-color: transparent; }
    20%  { background-color: rgba(180,0,0,0.28); }
    70%  { background-color: rgba(180,0,0,0.10); }
    100% { background-color: transparent; }
}
@keyframes playerHitFlash {
    0%   { background-color: transparent; }
    20%  { background-color: rgba(180,0,0,0.28); }
    70%  { background-color: rgba(180,0,0,0.10); }
    100% { background-color: transparent; }
}

/* Animations applied via inline style on the img — see combatfunctions.php */

.player-flash { -webkit-animation: playerHitFlash 0.7s ease-out; animation: playerHitFlash 0.7s ease-out; }

/* ───── Player Sprite Animations ───────────────────────────── */

/* Attack — lunge forward (toward monster on the right) */
@-webkit-keyframes plrAttack {
    0%   { -webkit-transform: translateX(0);    }
    28%  { -webkit-transform: translateX(20px); }
    55%  { -webkit-transform: translateX(8px);  }
    100% { -webkit-transform: translateX(0);    }
}
@keyframes plrAttack {
    0%   { transform: translateX(0);    }
    28%  { transform: translateX(20px); }
    55%  { transform: translateX(8px);  }
    100% { transform: translateX(0);    }
}

/* Cast — magical bloom with colour shift */
@-webkit-keyframes plrCast {
    0%   { -webkit-transform: scale(1);    filter: brightness(1); }
    20%  { -webkit-transform: scale(1.14); filter: brightness(2.8) hue-rotate(80deg) saturate(2); }
    50%  { -webkit-transform: scale(0.93); filter: brightness(1.6) hue-rotate(160deg); }
    75%  { -webkit-transform: scale(1.07); filter: brightness(1.9) hue-rotate(100deg); }
    100% { -webkit-transform: scale(1);    filter: brightness(1); }
}
@keyframes plrCast {
    0%   { transform: scale(1);    filter: brightness(1); }
    20%  { transform: scale(1.14); filter: brightness(2.8) hue-rotate(80deg) saturate(2); }
    50%  { transform: scale(0.93); filter: brightness(1.6) hue-rotate(160deg); }
    75%  { transform: scale(1.07); filter: brightness(1.9) hue-rotate(100deg); }
    100% { transform: scale(1);    filter: brightness(1); }
}

/* Hit — recoil left with red flash (player takes fatal blow) */
@-webkit-keyframes plrHit {
    0%   { -webkit-transform: translateX(0);     filter: brightness(1); }
    18%  { -webkit-transform: translateX(-14px); filter: brightness(5) sepia(1) hue-rotate(-20deg); }
    45%  { -webkit-transform: translateX(-6px);  filter: brightness(2); }
    72%  { -webkit-transform: translateX(-2px);  }
    100% { -webkit-transform: translateX(0);     filter: brightness(1); }
}
@keyframes plrHit {
    0%   { transform: translateX(0);     filter: brightness(1); }
    18%  { transform: translateX(-14px); filter: brightness(5) sepia(1) hue-rotate(-20deg); }
    45%  { transform: translateX(-6px);  filter: brightness(2); }
    72%  { transform: translateX(-2px);  }
    100% { transform: translateX(0);     filter: brightness(1); }
}

/* Idle — gentle float */
@-webkit-keyframes plrIdle {
    0%, 100% { -webkit-transform: translateY(0);    }
    50%       { -webkit-transform: translateY(-5px); }
}
@keyframes plrIdle {
    0%, 100% { transform: translateY(0);    }
    50%       { transform: translateY(-5px); }
}

/* Weapon Arte — wind-up recoil, then a lunge past the basic attack's reach */
@-webkit-keyframes plrArte {
    0%   { -webkit-transform: translateX(0); }
    16%  { -webkit-transform: translateX(-8px) rotate(-4deg); }
    44%  { -webkit-transform: translateX(28px) rotate(3deg) scale(1.06); filter: brightness(1.35); }
    68%  { -webkit-transform: translateX(16px); }
    100% { -webkit-transform: translateX(0); filter: brightness(1); }
}
@keyframes plrArte {
    0%   { transform: translateX(0); }
    16%  { transform: translateX(-8px) rotate(-4deg); }
    44%  { transform: translateX(28px) rotate(3deg) scale(1.06); filter: brightness(1.35); }
    68%  { transform: translateX(16px); }
    100% { transform: translateX(0); filter: brightness(1); }
}

/* Ability — a rising surge of gathered class energy (violet flare) */
@-webkit-keyframes plrAbil {
    0%   { -webkit-transform: translateY(0); filter: brightness(1); }
    30%  { -webkit-transform: translateY(-8px) scale(1.05); filter: brightness(1.6) drop-shadow(0 0 9px rgba(190,140,255,.85)); }
    60%  { -webkit-transform: translateY(-3px); filter: brightness(1.25) drop-shadow(0 0 4px rgba(190,140,255,.5)); }
    100% { -webkit-transform: translateY(0); filter: brightness(1); }
}
@keyframes plrAbil {
    0%   { transform: translateY(0); filter: brightness(1); }
    30%  { transform: translateY(-8px) scale(1.05); filter: brightness(1.6) drop-shadow(0 0 9px rgba(190,140,255,.85)); }
    60%  { transform: translateY(-3px); filter: brightness(1.25) drop-shadow(0 0 4px rgba(190,140,255,.5)); }
    100% { transform: translateY(0); filter: brightness(1); }
}

/* Player animations applied via inline style — see combatfunctions.php */

/* ─────────────────────────────────────────────────────────────────
   News body — controls spacing and table rendering
   ───────────────────────────────────────────────────────────────── */

/* Tighten paragraph spacing — browser default (1em top+bottom) causes huge gaps */
.news-body p {
    margin: 0.25em 0;
}

/* Lists: remove the double-spacing that appeared when nl2br injected <br> inside <li> */
.news-body ul,
.news-body ol {
    margin: 0.3em 0 0.3em 1.4em;
    padding: 0;
}

.news-body li {
    margin: 0.1em 0;
}

/* Tables inserted via the news editor */
.news-body table {
    border-collapse: collapse;
    width: 100%;
    margin: 6px 0;
    font-size: 0.9em;
}

.news-body th,
.news-body td {
    border: 1px solid #3a3050;
    padding: 4px 9px;
    text-align: left;
    vertical-align: top;
}

.news-body th {
    background: #161c38;
    color: #c0b0d8;
    font-family: var(--mk-display, Georgia, serif);
    font-weight: 600;
    font-size: 0.88em;
}

.news-body td {
    background: #0d1121;
    color: #c0b8cc;
}

/* ───── Quest Board ──────────────────────────────────────────────────── */

.quest-board-panel {
    background: #0a0c18;
    border: 1px solid #2a2000;
    border-radius: 4px;
    padding: 10px 12px;
    margin: 4px 0;
}

.qb-quest-row {
    background: #0d0b10;
    border: 1px solid #1e1a08;
    border-radius: 3px;
    padding: 7px 10px;
    margin-bottom: 5px;
}

.qb-quest-row:hover {
    border-color: #3a3010;
}

.qb-btn {
    display: inline-block;
    padding: 2px 11px;
    background: #1a1004;
    border: 1px solid var(--mkgd);
    border-radius: 3px;
    color: var(--mkgd);
    text-decoration: none;
    font-size: 0.85em;
    cursor: pointer;
}

.qb-btn:hover {
    background: #2a1c08;
    color: var(--mkg);
}

.qb-btn-claim {
    background: var(--mkbtn-bg);
    border-color: var(--mkg);
    color: var(--mkg);
}

.qb-btn-claim:hover {
    background: #2a2200;
    color: #ffe855;
}

.qb-reward {
    font-size: 0.85em;
    margin: 3px 0;
    color: #888;
}

.qb-progress {
    background: #1e1a08;
    border-radius: 3px;
    height: 5px;
    margin-top: 2px;
}

.news-body tr:hover td {
    background: #140f22;
}

/* ─────────────────────────────────────────────────────────────────
   Crafting Panel
   ───────────────────────────────────────────────────────────────── */

.craft-panel {
    padding: 6px 8px;
    color: #c0b8d0;
}

.craft-title {
    font-family: var(--mk-display, Georgia, serif);
    font-size: 1.2em;
    color: #c8a0e8;
    text-align: center;
    padding: 4px 0 6px;
    border-bottom: 1px solid #222848;
    margin-bottom: 6px;
    letter-spacing: 0.05em;
}

/* ── Tabs ── */
.craft-tabs {
    display: flex;
    justify-content: center;
    gap: 4px;
    margin-bottom: 8px;
    flex-wrap: wrap;
}

.craft-tab {
    display: inline-block;
    padding: 4px 10px;
    background: #130f1e;
    border: 1px solid #222848;
    border-radius: 4px;
    color: #8070a0;
    text-decoration: none;
    font-size: 0.84em;
    text-align: center;
    line-height: 1.3;
    transition: background 0.15s, color 0.15s;
}

.craft-tab:hover {
    background: #1c1430;
    color: #b090d0;
}

.craft-tab-active {
    background: #2a1a44;
    border-color: #7040a0;
    color: #d0a0f0;
}

.craft-tab-skill {
    font-size: 0.8em;
    color: #7060a0;
    font-family: var(--mk-mono, ui-monospace, monospace);
}

.craft-tab-active .craft-tab-skill {
    color: #a080c0;
}

/* ── Skill bar ── */
.craft-skillbar-wrap {
    text-align: center;
    margin-bottom: 6px;
    font-size: 0.88em;
}

.craft-skillbar-label {
    color: #9080b0;
}

/* ── Resource summary ── */
.craft-resources {
    text-align: center;
    margin-bottom: 8px;
    padding: 4px;
    background: #0f0c18;
    border: 1px solid var(--brd-soft);
    border-radius: 3px;
}

/* ── Recipe table ── */
.craft-list {
    max-height: 340px;
    overflow-y: auto;
    border: 1px solid var(--brd-soft);
    border-radius: 3px;
    margin-bottom: 8px;
    background: #0a0c18;
}

.craft-table {
    border-collapse: collapse;
    font-size: 0.85em;
    width: 100%;
}

.craft-header th {
    background: #130f20;
    color: #7060a0;
    padding: 3px 5px;
    border-bottom: 1px solid #222848;
    font-family: var(--mk-mono, ui-monospace, monospace);
    font-weight: 600;
    font-size: 0.74em;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    position: sticky;
    top: 0;
    z-index: 1;
}

.craft-section-head {
    background: #110e1c;
    color: #806090;
    font-size: 0.82em;
    font-style: italic;
    padding: 2px 6px;
    border-top: 1px solid var(--brd-soft);
}

.craft-row-ok td,
.craft-row-locked td {
    padding: 3px 5px;
    border-bottom: 1px solid #140e20;
}

.craft-row-ok {
    background: #0d1121;
}

.craft-row-ok:hover {
    background: #141a34;
}

.craft-row-locked {
    background: #0a0810;
    opacity: 0.7;
}

.craft-item-name {
    color: #c8b8e0;
    font-family: var(--mk-display, Georgia, serif);
}

.craft-row-locked .craft-item-name {
    color: #4a6484;
}

/* 120px -> 104px (2026-08-25): the action column now has to fit a quantity
   box beside the button, and at 120px here the table overflowed its ~424px
   panel and clipped the button. Ingredient text already wraps, so it gives
   up the space more gracefully than the primary action does. */
.craft-ing-cell {
    min-width: 104px;
}
/* ≤480 (phones): forced ingredient-cell widths overflow the craft table —
   release them and let the list scroll sideways as a last resort. */
@media (max-width: 480px) {
    .craft-ing-cell { min-width: 0; }
    .craft-list { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* ── Craft action column ──────────────────────────────────────────────────
   Holds the batch quantity box + the Brew/Cook/Craft button (2026-08-25).
   The column is content-sized, so with nothing to constrain it all three
   recipe tables collapsed it to ~69-80px and the button wrapped underneath
   the box — measured in-browser on alchemy, culinarian AND runecrafting, not
   just runecrafting as first reported.

   ⚠ The controls are TIGHTENED here rather than the column simply widened.
   The crafting panel is only ~424px and the recipe table already fills it, so
   a 112px reservation pushed the table to 464px and the button slid out of
   view behind a horizontal scroll — the primary action off-screen is worse
   than the wrap it replaced. Narrowing the box and the button instead lets the
   pair sit on one line inside roughly the space the column already had. */
.craft-act {
    white-space: nowrap;
}
/* ⚠ Deliberately NOT scoped to .craft-act — the class describes the control,
   not where it sits. The Crystagyn's "buy N Essence Extractors" box reuses it
   so bulk-quantity inputs look the same everywhere in the game. */
.craft-qty {
    width: 32px;
    padding: 2px 1px;
    /* Spinners eat ~15px and are useless at this size — the value is 1-50 and
       typing is faster than clicking. */
    -moz-appearance: textfield;
    appearance: textfield;
}
.craft-qty::-webkit-outer-spin-button,
.craft-qty::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.craft-act .craft-btn {
    padding: 3px 6px;
}

/* ── Craft buttons ── */
.craft-btn {
    display: inline-block;
    padding: 3px 10px;
    background: #120c1e;
    border: 1px solid #3f2d63;
    color: #b98ce6;
    text-decoration: none;
    font-family: var(--mk-mono, ui-monospace, monospace);
    font-size: 0.72em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    cursor: pointer;
    -webkit-transition: border-color 0.12s, color 0.12s, -webkit-box-shadow 0.12s;
    transition: border-color 0.12s, color 0.12s, box-shadow 0.12s;
}

.craft-btn:hover {
    color: #b98ce6;
    border-color: #b98ce6;
    -webkit-box-shadow: 0 0 8px rgba(185,140,230,0.32);
    box-shadow: 0 0 8px rgba(185,140,230,0.32);
}

.craft-btn-locked {
    display: inline-block;
    padding: 2px 8px;
    color: #443850;
    font-size: 0.80em;
    font-style: italic;
    white-space: nowrap;
}

/* ── Craft log ── */
.craft-log {
    background: #0a0c18;
    border: 1px solid var(--brd-soft);
    border-radius: 3px;
    padding: 4px 6px;
    font-size: 0.84em;
}

.craft-log-title {
    color: #7060a0;
    font-family: var(--mk-mono, ui-monospace, monospace);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.8em;
    border-bottom: 1px solid var(--brd-soft);
    margin-bottom: 4px;
    padding-bottom: 2px;
}

/* ── Game Navigation Buttons ─────────────────────────────────── */
.nav-btn {
    display: inline-block;
    vertical-align: middle;    /* matches .nav-group's vertical-align so leaf and
                                  dropdown-group buttons sit at the same height */
    padding: 5px 10px;
    margin: 2px 1px;
    background: linear-gradient(150deg, rgba(255,255,255,.06), rgba(255,255,255,0) 42%), linear-gradient(to bottom, #182038 0%, #0d1022 100%);
    border: 1px solid #2e2248;
    border-radius: 4px;
    box-shadow: inset 0 1px 0 rgba(170,140,230,.14), inset 0 -2px 4px rgba(0,0,0,.3);
    color: #a898c8;
    text-decoration: none;
    font-family: var(--mk-display, Georgia, serif);
    font-size: 0.75em;
    letter-spacing: 0.05em;
    white-space: nowrap;
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease,
                box-shadow 0.2s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.nav-btn:hover {
    background: linear-gradient(to bottom, #28305a 0%, #151e44 100%);
    border-color: #8050cc;
    color: #e0d0ff;
    text-decoration: none;
    box-shadow: 0 2px 10px rgba(130, 80, 210, 0.4);
    transform: translateY(-1px);
}
.nav-btn:active {
    background: linear-gradient(to bottom, #0d1022 0%, #161c38 100%);
    border-color: #5030a0;
    color: #b8a8e0;
    box-shadow: none;
    transform: translateY(0);
    transition: background 0.05s, border-color 0.05s, color 0.05s, box-shadow 0.05s, transform 0.05s;
}

/* Alert variant — gold pulsing glow when the button has something the player
   should know about (claimable objectives, unread mail, etc.).  The pulse
   keyframe runs on the border + box-shadow so the underlying nav-btn hover
   transitions don't fight it. */
.nav-btn-alert {
    border-color: var(--mkgd);
    background: linear-gradient(to bottom, #2a1f0c 0%, #15100a 100%);
    color: #ffe06a;
    animation: navAlertPulse 1.6s ease-in-out infinite;
}
.nav-btn-alert:hover {
    background: linear-gradient(to bottom, #3a2a14 0%, #1f1808 100%);
    border-color: #ffd24a;
    color: #fff0a0;
}
@keyframes navAlertPulse {
    0%, 100% {
        box-shadow: 0 0 4px rgba(var(--mkgd-rgb),0.35),
                    inset 0 0 6px rgba(var(--mkgd-rgb),0.12);
    }
    50% {
        box-shadow: 0 0 11px rgba(255, 215, 90, 0.65),
                    inset 0 0 9px rgba(255, 215, 90, 0.22);
    }
}
/* Small numeric pill that hangs off the right of an alert nav button */
.nav-btn-badge {
    display: inline-block;
    background: #cc4444;
    color: #fff;
    border-radius: 9px;
    padding: 0 5px;
    font-size: 0.78em;
    margin-left: 4px;
    font-family: 'Exo 2', sans-serif;
    font-weight: 600;
    box-shadow: 0 0 4px rgba(204, 68, 68, 0.5);
}

/* ── Nav dropdowns ──────────────────────────────────────────────
   .nav-group is an inline-block wrapper around a top-level button
   (the .nav-btn) and a hidden .nav-menu. Hovering the group reveals
   the menu beneath it.  Each .nav-menu-item is a fluid full-width
   link inside the dropdown, keeping the Cinzel/gold game palette.
*/
.nav-group {
    position: relative;
    display: inline-block;
    margin: 2px 1px;
    vertical-align: middle;
}
.nav-group > .nav-btn {
    margin: 0;
}
/* Small caret on grouped buttons */
.nav-group > .nav-btn::after {
    content: '\25BE';                  /* ▾ */
    display: inline-block;
    color: #6a5a8a;
    font-size: 0.85em;
    margin-left: 5px;
    transition: color 0.18s ease, transform 0.2s ease;
}
.nav-group:hover > .nav-btn::after,
.nav-group:focus-within > .nav-btn::after { color: var(--mkgd); transform: rotate(180deg); }
/* The dropdown panel — hidden by default, revealed on group hover. */
.nav-menu {
    position: absolute;
    top: 100%;
    /* Left-align the dropdown to its button (was centered via left:50% +
       translateX(-50%), which pushed the leftmost "Game" menu off the left
       edge of the screen). The menu buttons live in the left cluster, so a
       right-extending dropdown always has room. */
    left: 0;
    transform: translateY(-7px);
    transform-origin: top left;
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
    min-width: 160px;
    margin-top: 3px;
    padding: 4px 0;
    background: linear-gradient(to bottom, #160e2a 0%, #0b0e1c 100%);
    border: 1px solid #333a64;
    border-radius: 4px;
    box-shadow: 0 6px 16px rgba(0,0,0,0.7),
                inset 0 1px 0 rgba(255,215,90,0.04);
    z-index: 1000;
    text-align: left;
    transition: opacity 0.18s ease,
                transform 0.22s cubic-bezier(0.34, 1.3, 0.64, 1),
                visibility 0.18s;
}
.nav-group:hover .nav-menu,
.nav-group:focus-within .nav-menu {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
}
/* Tiny hover bridge so the menu does not close when crossing the gap. */
.nav-menu::before {
    content: '';
    position: absolute;
    left: 0; right: 0;
    top: -6px;
    height: 6px;
}
.nav-menu-item {
    display: block;
    padding: 5px 14px;
    border-left: 2px solid transparent;
    color: #a898c8;
    font-family: var(--mk-display, Georgia, serif);
    font-size: 0.78em;
    letter-spacing: 0.04em;
    text-decoration: none;
    white-space: nowrap;
    transition: background 0.15s ease, color 0.15s ease,
                border-color 0.15s ease, padding-left 0.15s ease;
}
.nav-menu-item:hover {
    background: #252a4e;
    color: var(--mkg);
    border-left-color: var(--mkgd);
    padding-left: 18px;
    text-decoration: none;
}
/* Light staggered entrance as the dropdown opens, so items cascade in
   instead of all appearing at once. */
@keyframes navItemIn {
    from { opacity: 0; transform: translateX(-6px); }
    to   { opacity: 1; transform: translateX(0); }
}
.nav-group:hover .nav-menu-item,
.nav-group:focus-within .nav-menu-item {
    animation: navItemIn 0.2s ease both;
}
.nav-group:hover .nav-menu-item:nth-child(2),
.nav-group:focus-within .nav-menu-item:nth-child(2) { animation-delay: 0.03s; }
.nav-group:hover .nav-menu-item:nth-child(3),
.nav-group:focus-within .nav-menu-item:nth-child(3) { animation-delay: 0.06s; }
.nav-group:hover .nav-menu-item:nth-child(4),
.nav-group:focus-within .nav-menu-item:nth-child(4) { animation-delay: 0.09s; }
.nav-group:hover .nav-menu-item:nth-child(5),
.nav-group:focus-within .nav-menu-item:nth-child(5) { animation-delay: 0.12s; }
.nav-group:hover .nav-menu-item:nth-child(6),
.nav-group:focus-within .nav-menu-item:nth-child(6) { animation-delay: 0.15s; }
.nav-group:hover .nav-menu-item:nth-child(n+7),
.nav-group:focus-within .nav-menu-item:nth-child(n+7) { animation-delay: 0.18s; }
.nav-menu-item .nav-btn-badge { margin-left: 8px; }
/* When the parent group should show the gold pulsing alert because one
   of its children has unread/claimable content, the parent .nav-btn
   keeps its existing .nav-btn-alert class — we just stop the caret
   from clashing colour-wise. */
.nav-group .nav-btn-alert::after { color: var(--mkgd); }

/* Admin/GM variant — gold accent */
.nav-btn-admin {
    border-color: #3a2800;
    color: #aa7820;
}
.nav-btn-admin:hover {
    background: linear-gradient(to bottom, #241800 0%, #140e00 100%);
    border-color: var(--mkgd);
    color: #ffe06a;
    box-shadow: 0 0 7px rgba(200, 155, 20, 0.35);
}
.nav-btn-admin:active {
    background: linear-gradient(to bottom, #140e00 0%, #201600 100%);
    border-color: #806010;
    color: #ccaa40;
    box-shadow: none;
}

/* ═══════════════════════════════════════════════════════════════
   Multi-Resolution Scaling — HD+ through 4K
   Breakpoints : 1440 (HD+) · 1920 (FHD) · 2560 (QHD) · 3840 (4K)
   Strategy    : scale font-size on body; scale fixed-pixel
                 elements (sprites, compass, chat, craft list,
                 stat bars, scrollbars) proportionally.
   ═══════════════════════════════════════════════════════════════ */

/* ── HD+ — 1440p and common large laptops (1440 × 900 +) ─── */
@media screen and (min-width: 1440px) {
    body { font-size: 90%; }
}

/* ── Full HD — 1920 × 1080 ───────────────────────────────── */
@media screen and (min-width: 1920px) {
    body { font-size: 94%; }



    .craft-list { max-height: 400px; }

    /* Slightly thicker stat bars */
    .stat-fill,
    .qb-progress { height: 6px; }
}

/* ── QHD / 1440p monitors — 2560 × 1440 ─────────────────── */
@media screen and (min-width: 2560px) {
    body { font-size: 112%; }

    #chatRoom   { height: 600px; }

    .craft-list     { max-height: 500px; }
    .craft-ing-cell { min-width: 165px; }

    .combat-arena { min-height: 144px; }

    /* Combat sprites */
    .mon-sprite,
    .plr-sprite {
        width:  116px;
        height: 116px;
    }

    /* Compass rose — cardinal buttons */
    .dpad-btn-d {
        width:  56px;
        height: 50px;
        font-size: 1.25em;
    }
    /* Diagonal buttons */
    .dpad-diag-d {
        width:  46px;
        height: 40px;
    }
    /* Centre refresh button */
    .dpad-ctr-d {
        width:  52px;
        height: 52px;
        font-size: 1.25em;
    }
    /* Compass rose container */
    .dpad-wrap   { padding: 10px 8px; }
    .dpad-row-d  { gap: 3px; margin-bottom: 3px; }

    /* Stat / progress bars */
    .stat-fill,
    .qb-progress { height: 7px; }

    /* Scrollbars */
    ::-webkit-scrollbar { width: 10px; height: 10px; }

    /* Chat tabs */
    .chat-tab { padding: 4px 14px 6px; font-size: 0.86em; }

    /* Nav buttons */
    .nav-btn { padding: 6px 13px; }
}

/* ── 4K UHD — 3840 × 2160 ───────────────────────────────── */
@media screen and (min-width: 3840px) {
    body { font-size: 146%; }

    #wrapper { top: 10px; }

    #chatRoom   { height: 820px; }

    .craft-list     { max-height: 700px; }
    .craft-ing-cell { min-width: 230px; }

    .combat-arena { min-height: 212px; }

    /* Combat sprites — enlarged so the new PNG art reads clearly */
    .mon-sprite,
    .plr-sprite {
        width:  176px;
        height: 176px;
    }

    /* Compass rose — cardinal buttons */
    .dpad-btn-d {
        width:  78px;
        height: 66px;
        font-size: 1.55em;
        border-radius: 9px;
    }
    /* Diagonal buttons */
    .dpad-diag-d {
        width:  62px;
        height: 54px;
        border-radius: 7px;
    }
    /* Centre refresh button */
    .dpad-ctr-d {
        width:  70px;
        height: 70px;
        font-size: 1.45em;
    }
    /* Compass rose container */
    .dpad-wrap  { padding: 16px 12px; }
    .dpad-row-d { gap: 5px; margin-bottom: 5px; }

    /* Stat / progress bars */
    .stat-fill,
    .qb-progress { height: 10px; }

    /* Scrollbars */
    ::-webkit-scrollbar { width: 14px; height: 14px; }

    /* Chat tabs */
    .chat-tab { padding: 6px 20px 9px; font-size: 0.92em; }

    /* Nav buttons */
    .nav-btn { padding: 9px 18px; font-size: 0.85em; }

    /* Map zoom buttons */
    .map-zoom-btn { padding: 5px 16px; font-size: 0.90em; }

    /* Craft buttons */
    .craft-btn        { padding: 4px 12px; font-size: 0.90em; }
    .craft-btn-locked { padding: 4px 12px; font-size: 0.88em; }
}

/* ═══════════════════════════════════════════════════════════════
   Accessibility
   Toggled via body / #displayArea / #chatRoom classes set by the
   A11Y module in gamefunctions.js (Nav -> Game -> Accessibility).
   Settings are stored client-side (localStorage) — same convention
   as Keybinds, so there's no server round-trip for these.
   ═══════════════════════════════════════════════════════════════ */

/* Game / chat text size — independent scales. Percentages stack
   multiplicatively on top of the multi-resolution body font-size
   above, same mechanism that block already uses. */
body.a11y-text-1 #displayArea { font-size: 112%; }
body.a11y-text-2 #displayArea { font-size: 126%; }
body.a11y-text-3 #displayArea { font-size: 142%; }

body.a11y-chat-1 #chatRoom { font-size: 112%; }
body.a11y-chat-2 #chatRoom { font-size: 126%; }
body.a11y-chat-3 #chatRoom { font-size: 142%; }

/* Reduced motion — collapse every animation/transition to an instant
   jump-cut instead of `animation: none`, so fill-mode:forwards end
   states (death-fade, staggered nav entrance, etc.) still land on
   their correct final frame instead of snapping back to the start.
   OPT-IN ONLY via the panel toggle (body class). Do NOT add a blanket
   @media (prefers-reduced-motion: reduce) version of this rule: Windows
   sets that flag for lots of players who never chose it (Animation
   effects off, RDP sessions, some performance modes), and it silently
   killed combat animations for all of them when it briefly shipped
   (reported 2026-07-02, removed same day). Weather effects keep their
   own scoped media query in weather_overlay.css — decorative only,
   that one is correct. */
body.a11y-reduced-motion *,
body.a11y-reduced-motion *::before,
body.a11y-reduced-motion *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
}

/* High contrast — blanket white-on-black override. main.css has no
   CSS custom properties to retarget centrally, so this forces the
   fundamentals (text / background / links / panel borders) rather
   than re-theming every hardcoded hex colour in the file. Trade-off:
   colour-coded meaning (rarity, elements, HP-danger red, etc.) is
   necessarily flattened to white while this is on. */
body.a11y-high-contrast { background: #000 !important; }
body.a11y-high-contrast * {
    color: #fff !important;
    text-shadow: none !important;
    background-image: none !important;
}
body.a11y-high-contrast a,
body.a11y-high-contrast a:visited {
    color: #ffe066 !important;
    text-decoration: underline !important;
}
body.a11y-high-contrast #displayArea,
body.a11y-high-contrast #chatRoom,
body.a11y-high-contrast #statMenu,
body.a11y-high-contrast #equipMenu,
body.a11y-high-contrast #moveMenu,
body.a11y-high-contrast #mapMenu,
body.a11y-high-contrast .nav-menu {
    background: #000 !important;
    border: 2px solid #fff !important;
}
body.a11y-high-contrast .chat-msg { border-left-color: #ffe066 !important; }
body.a11y-high-contrast .nav-btn,
body.a11y-high-contrast .nav-menu-item,
body.a11y-high-contrast button,
body.a11y-high-contrast input,
body.a11y-high-contrast select {
    background: #000 !important;
    border: 1px solid #fff !important;
    color: #fff !important;
}

/* Readable font — wider letter/word spacing + a plainer sans-serif
   stack. Not a bundled dyslexia typeface (none ships with the game),
   but the spacing increase alone is independently shown to help
   reading speed/accuracy for a lot of dyslexic readers. */
body.a11y-readable-font,
body.a11y-readable-font * {
    font-family: Verdana, Tahoma, Arial, sans-serif !important;
    letter-spacing: 0.03em !important;
    word-spacing: 0.08em !important;
    line-height: 1.6 !important;
}

/* Bold text — flat font-weight boost for low-vision readers. */
body.a11y-bold-text,
body.a11y-bold-text * { font-weight: 700 !important; }

/* Always-underline links — lets players who can't rely on colour
   alone (colour-blindness, low contrast displays) spot interactive
   text without hovering. */
body.a11y-underline-links a { text-decoration: underline !important; }

/* Stronger keyboard-focus ring — always on, independent of the
   toggles above. :focus-visible only fires for keyboard/assistive
   navigation (not mouse clicks), so this is a free improvement with
   no visual cost for mouse users. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible {
    outline: 2px solid #ffe066 !important;
    outline-offset: 2px !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOCKUP TYPOGRAPHY  —  arena_spec.html / crystaria_redesign.html design system
   ───────────────────────────────────────────────────────────────────────────
   display = Georgia serif · body = system sans · data (labels + numbers) = mono.
   Placed last so it wins the cascade. Cinzel/IM Fell/Exo 2 are no longer loaded,
   so headings & names already fall back to Georgia; here we set body -> sans and
   every stat/combat LABEL + NUMBER -> tabular monospace (the mockup's signature). */
:root {
    --mk-display: Georgia, 'Iowan Old Style', 'Palatino Linotype', serif;
    --mk-body:    ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --mk-mono:    ui-monospace, 'Cascadia Code', 'SF Mono', 'Segoe UI Mono', Consolas, 'Roboto Mono', monospace;
}

/* Body copy + form controls + chat → clean system sans */
body, input, textarea, select, button,
.chatmessage, .chatmsg, .chat-row, .msg-body { font-family: var(--mk-body); }

/* Big names / headings → Georgia serif (pins inline-Cinzel name spans too) */
h1, h2, h3, h4,
.mkstat .mk-name, .cbt-np-name, .cbt-name { font-family: var(--mk-display); }

/* Labels → letter-spaced monospace (the stat-sheet feel) */
.mkstat .mk-head, .mkstat .mk-ministat .mk-k, .mkstat .mkvital .mklab,
.mkstat .mk-gold .mk-k, .cbt-v-lab, .cbt-np-meta { font-family: var(--mk-mono); }

/* Numbers / values → tabular monospace */
.mkstat .mkvital .mklab span:last-child, .mkstat .mk-ministat .mk-v,
.mkstat .mk-gold .mk-v, .cbt-v-lab b, .cbt-hp,
[style*="IM Fell English"] {
    font-family: var(--mk-mono) !important;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
}

/* Mono digits are wider than the old serif — shrink the long figures in the
   tightest cells so 7-digit gold and cur/max vitals don't clip or wrap. */
.mkstat .mk-gold .mk-v { font-size: .8em; letter-spacing: -.02em; }
.mkstat .mkvital .mklab { font-size: .64em; letter-spacing: .04em; }
.cbt-np-meta { letter-spacing: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   EQUIPMENT paper-doll + BAG rows (mockup — crystaria_redesign.html)
   Faceted 3-column doll (5 slots | lit class portrait + live stats | 4 slots),
   rarity-bordered slot tiles with essence badges; bag as a windowed row list. */
.mkeq { font-family: var(--mk-body); }
.mkeq .eq-head, .mkbag .bag-head { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -webkit-box-align: baseline; -ms-flex-align: baseline; align-items: baseline; border-bottom: 1px solid var(--brd); padding-bottom: 7px; margin-bottom: 11px; }
.mkeq .eq-head h2, .mkbag .bag-head h2 { font-family: var(--mk-display); font-size: 1.1em; color: #eaf3fb; margin: 0; font-weight: normal; letter-spacing: .01em; }
.mkeq .eq-head .tag, .mkbag .bag-head .tag { font-family: var(--mk-mono); font-size: .66em; letter-spacing: .1em; text-transform: uppercase; color: #7fa0c0; }
.mkeq .doll { display: grid; grid-template-columns: 1fr 1.15fr 1fr; gap: 11px; -webkit-box-align: start; -ms-flex-align: start; align-items: start; }
@media (max-width:640px){ .mkeq .doll { grid-template-columns: 1fr 1fr; } .mkeq .dcenter { grid-column: 1 / -1; -webkit-box-ordinal-group: 0; -ms-flex-order: -1; order: -1; } }
.mkeq .dcol { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; gap: 8px; }
.mkeq .eqslot { position: relative; background: linear-gradient(180deg,#101c30,#0e1626); border: 1px solid var(--brd); border-left: 2px solid var(--mkad); padding: 6px 9px 7px 10px; }
.mkeq .eqslot .eyb { font-family: var(--mk-mono); font-size: .62em; letter-spacing: .09em; text-transform: uppercase; color: #7fa0c0; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 5px; }
.mkeq .eqslot .inm { font-size: .9em; margin-top: 2px; color: #eaf3fb; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mkeq .eqslot .ilv { font-family: var(--mk-mono); font-size: .66em; color: #7fa0c0; margin-top: 1px; }
.mkeq .eqslot.empty { opacity: .5; }
.mkeq .eqslot.empty .inm { color: #4a6484; font-style: italic; }
.mkeq .eqslot .ess { display: inline-block; font-family: var(--mk-mono); font-size: .6em; color: #9cc8f0; border: 1px solid var(--mkad); padding: 0 4px; margin: 4px 3px 0 0; }
.mkeq .eqslot .lk { position: absolute; top: 6px; right: 8px; font-size: .72em; color: #7fa0c0; }
.mkeq .dcenter { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-align: center; -ms-flex-align: center; align-items: center; text-align: center; gap: 3px; }
.mkeq .dportrait { width: 104px; height: 104px; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; background: radial-gradient(circle at 50% 42%,rgba(var(--mka-rgb),.22),transparent 62%),linear-gradient(160deg,#1a1330,#0a1424); -webkit-box-shadow: inset 0 0 0 1px var(--brd),0 0 14px rgba(var(--mka-rgb),.15); box-shadow: inset 0 0 0 1px var(--brd),0 0 14px rgba(var(--mka-rgb),.15); -webkit-clip-path: polygon(50% 0,100% 26%,100% 74%,50% 100%,0 74%,0 26%); clip-path: polygon(50% 0,100% 26%,100% 74%,50% 100%,0 74%,0 26%); }
.mkeq .dportrait img { width: 82%; height: 82%; -o-object-fit: contain; object-fit: contain; -webkit-filter: drop-shadow(0 2px 4px rgba(0,0,0,.7)); filter: drop-shadow(0 2px 4px rgba(0,0,0,.7)); }
.mkeq .dname { font-family: var(--mk-display); color: #f0e2c0; font-size: 1.15em; margin-top: 5px; }
.mkeq .dsub { font-family: var(--mk-mono); font-size: .68em; color: #7fa0c0; letter-spacing: .03em; }
.mkeq .dstats { display: grid; grid-template-columns: repeat(3,1fr); gap: 2px 10px; margin-top: 11px; width: 100%; font-size: .72em; }
.mkeq .dstats span { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; border-bottom: 1px solid #14243c; padding: 2px 0; }
.mkeq .dstats span i { color: #7fa0c0; font-style: normal; font-family: var(--mk-mono); font-size: .9em; }
.mkeq .dstats span b { color: #eaf3fb; font-family: var(--mk-mono); font-weight: normal; }
/* ≤480 (phones): 3 stat columns crush below ~380px — drop to 2. */
@media (max-width: 480px) { .mkeq .dstats { grid-template-columns: repeat(2,1fr); } }
.mkeq .dcur { display: -webkit-box; display: -ms-flexbox; display: flex; gap: 14px; margin-top: 14px; -ms-flex-wrap: wrap; flex-wrap: wrap; font-size: .76em; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; }
.mkeq .dcur span { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 6px; color: #9cb8d4; }
.mkeq .dcur b { color: var(--mkg); font-family: var(--mk-mono); }
.mkeq .dfoot { display: -webkit-box; display: -ms-flexbox; display: flex; gap: 8px; margin-top: 13px; -ms-flex-wrap: wrap; flex-wrap: wrap; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; }
.mkeq .eq-btn { font-family: var(--mk-mono); font-size: .72em; letter-spacing: .04em; padding: 5px 13px; text-decoration: none; color: var(--mkg); border: 1px solid #7a6a2e; background: rgba(var(--mkg-rgb),.06); }
.mkeq .eq-btn.ghost { color: #9cb8d4; border-color: var(--brd); background: transparent; }

.mkbag { font-family: var(--mk-body); }
.mkbag .bag-cap { font-family: var(--mk-mono); font-size: .72em; color: #9cb8d4; }
.mkbag .bag-filters { display: -webkit-box; display: -ms-flexbox; display: flex; gap: 6px; margin-bottom: 9px; -ms-flex-wrap: wrap; flex-wrap: wrap; }
.mkbag .pill { font-family: var(--mk-mono); font-size: .64em; padding: 2px 8px; border: 1px solid var(--brd); color: #9cb8d4; letter-spacing: .02em; }
.mkbag .bagrows { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; }
.mkbag .brow { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; gap: 7px; padding: 6px 4px; border-bottom: 1px solid #14243c; font-size: .84em; }
.mkbag .brow.eq { background: rgba(var(--mkg-rgb),.05); }
/* ≤480 (phones): a bag row (name·EQ·essence·type·Lv·button·?·lock) overflows —
   let it wrap, with the item name taking its own line. */
@media (max-width: 480px) {
    .mkbag .brow { -ms-flex-wrap: wrap; flex-wrap: wrap; row-gap: 4px; }
    .mkbag .brow > span:first-child { -webkit-box-flex: 1; -ms-flex: 1 1 100%; flex: 1 1 100%; min-width: 0; }
}
.mkbag .brow .bn { -webkit-box-flex: 1; -ms-flex: 1; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #eaf3fb; }
.mkbag .beq { font-family: var(--mk-mono); font-size: .6em; color: var(--mkg); font-style: italic; }
.mkbag .bess { font-family: var(--mk-mono); font-size: .58em; color: #9cc8f0; border: 1px solid var(--mkad); padding: 0 4px; white-space: nowrap; }
.mkbag .bt { font-family: var(--mk-mono); font-size: .6em; letter-spacing: .04em; white-space: nowrap; }
.mkbag .blv { font-family: var(--mk-mono); font-size: .66em; color: #7fa0c0; white-space: nowrap; }
.mkbag .bbtn { font-family: var(--mk-mono); font-size: .64em; color: var(--mkg); border: 1px solid #7a6a2e; padding: 1px 8px; white-space: nowrap; text-decoration: none; cursor: pointer; }
.mkbag .bbtn.un { color: #9cb8d4; border-color: var(--brd); }

/* ═══════════════════════════════════════════════════════════════════════════
   QUEST / GUILD card language (mockup .gcard/.pbar/.state) — shared by the
   Quest Log, Objectives, Quest Board, and Guild screens. Wrap a screen in .mkq. */
.mkq { font-family: var(--mk-body); }
.mkq .qhead { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; -webkit-box-align: baseline; -ms-flex-align: baseline; align-items: baseline; border-bottom: 1px solid var(--brd); padding-bottom: 7px; margin-bottom: 12px; -ms-flex-wrap: wrap; flex-wrap: wrap; gap: 8px; }
.mkq .qhead h2 { font-family: var(--mk-display); font-size: 1.1em; color: #eaf3fb; margin: 0; font-weight: normal; letter-spacing: .01em; }
.mkq .qhead .tag { font-family: var(--mk-mono); font-size: .66em; letter-spacing: .1em; text-transform: uppercase; color: #7fa0c0; }
.mkq .tri { display: grid; grid-template-columns: repeat(3,1fr); gap: 14px; -webkit-box-align: start; -ms-flex-align: start; align-items: start; }
@media (max-width:820px){ .mkq .tri { grid-template-columns: 1fr; } }
.mkq .h5 { font-family: var(--mk-mono); font-size: .7em; letter-spacing: .12em; text-transform: uppercase; color: var(--mkg); margin: 0 0 10px; padding-bottom: 5px; border-bottom: 1px solid #7a6a2e; }
.mkq .gcard { background: linear-gradient(180deg,#101c30,#0e1626); border: 1px solid var(--brd); padding: 9px 11px; margin-bottom: 9px; }
.mkq .gcard.lb { border-left: 3px solid #7a6a2e; }
.mkq .grow { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; gap: 8px; }
.mkq .nm2 { font-family: var(--mk-display); color: #eaf3fb; font-size: .98em; letter-spacing: .01em; }
.mkq .tiny { font-size: .76em; color: #7fa0c0; }
.mkq .pbar { height: 6px; background: #070c16; -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd); margin: 5px 0; overflow: hidden; }
.mkq .pbar i { display: block; height: 100%; background: linear-gradient(90deg,var(--mkgd),var(--mkg)); }
.mkq .state { font-family: var(--mk-mono); font-size: .6em; letter-spacing: .08em; text-transform: uppercase; padding: 1px 7px; border: 1px solid var(--brd); white-space: nowrap; }
.mkq .state.rdy { color: #5ad06a; border-color: #2a6a3a; }
.mkq .state.prog { color: var(--mkgd); border-color: #7a6a2e; }
.mkq .pill { font-family: var(--mk-mono); font-size: .64em; padding: 2px 7px; border: 1px solid var(--brd); color: #9cb8d4; letter-spacing: .02em; white-space: nowrap; }
.mkq .bbtn { font-family: var(--mk-mono); font-size: .66em; color: var(--mkg); border: 1px solid #7a6a2e; padding: 1px 8px; white-space: nowrap; text-decoration: none; cursor: pointer; }
.mkq .cgold { color: var(--mkg); } .mkq .camx { color: #9cc8f0; } .mkq .cgrn { color: #5ad06a; } .mkq .cred { color: #e0553a; } .mkq .cblu { color: #5a9ae0; }
.mkq .streak { display: grid; grid-template-columns: repeat(7,1fr); gap: 3px; margin-top: 7px; }
.mkq .streak span { height: 16px; background: #161225; -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd); display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; font-family: var(--mk-mono); font-size: .62em; color: #4a6484; }
.mkq .streak span.lit { background: linear-gradient(180deg,#1e5a34,#123a22); color: #8fe0a8; }
.mkq .streak span.gold { background: linear-gradient(180deg,#5a4a14,#2a2208); color: var(--mkg); }

/* Shop / Guild extras: window frame, filter tabs, roster rows, hall pips, relic slots */
.mkq .window { position: relative; background: linear-gradient(180deg,#101c30,#0b0816); border: 1px solid var(--brd); -webkit-box-shadow: 0 24px 50px -24px #000, inset 0 1px 0 rgba(var(--mka-rgb),.08); box-shadow: 0 24px 50px -24px #000, inset 0 1px 0 rgba(var(--mka-rgb),.08); }
.mkq .winbar { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; padding: 7px 11px; border-bottom: 1px solid var(--brd); background: linear-gradient(180deg,rgba(var(--mka-rgb),.08),transparent); font-family: var(--mk-mono); font-size: .7em; letter-spacing: .05em; color: #9cb8d4; }
.mkq .winbar .x { color: #7fa0c0; }
.mkq .duo { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; -webkit-box-align: start; -ms-flex-align: start; align-items: start; }
@media (max-width:760px){ .mkq .duo { grid-template-columns: 1fr; } }

/* ── ≤480 (phones): shop rows + generic width guards ─────────────────────────
   Shop rows are INLINE-styled by shop_helper.php (name·Lv·type·price·Buy·?) and
   overflow a ~344px column; the data-shop-row attribute is the stable hook.
   !important is required to beat the inline style attribute.  The item name
   takes its own line; chips/price/buttons wrap to a second row.
   The table guard keeps any PHP-rendered data table from forcing sideways
   scroll on the whole panel. */
@media (max-width: 480px) {
    [data-shop-row] { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; row-gap: 4px !important; }
    [data-shop-row] > span:first-child { -webkit-box-flex: 1 !important; -ms-flex: 1 1 100% !important; flex: 1 1 100% !important; min-width: 0 !important; white-space: normal !important; }
    #displayArea table, #infoArea table { max-width: 100% !important; }
    #displayArea td, #infoArea td { word-break: break-word; }
}
.mkq .vtabs { display: -webkit-box; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
.mkq .vtab { font-family: var(--mk-mono); font-size: .64em; padding: 4px 9px; border: 1px solid var(--brd); color: #7fa0c0; cursor: pointer; white-space: nowrap; text-decoration: none; }
.mkq .vtab.on { color: var(--mkg); background: #0e1626; -webkit-box-shadow: inset 0 -2px 0 var(--mkg); box-shadow: inset 0 -2px 0 var(--mkg); }
.mkq .vtab.lock { color: #4a6484; }
.mkq .brank { font-family: var(--mk-mono); font-size: .6em; letter-spacing: .06em; text-transform: uppercase; padding: 1px 6px; border: 1px solid var(--brd); white-space: nowrap; }
.mkq .frow { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between; gap: 8px; padding: 4px 0; border-bottom: 1px solid #14243c; font-size: .84em; }
.mkq .pips { display: -webkit-box; display: -ms-flexbox; display: flex; gap: 3px; }
.mkq .pip { width: 11px; height: 11px; -webkit-transform: rotate(45deg); transform: rotate(45deg); background: #14243c; -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd); }
.mkq .pip.on { background: radial-gradient(circle at 35% 30%,#fff2c0,var(--mkg)); -webkit-box-shadow: 0 0 5px var(--mkg); box-shadow: 0 0 5px var(--mkg); }
.mkq .rslot { background: linear-gradient(180deg,#101c30,#0e1626); border: 1px solid var(--brd); border-top: 2px solid var(--mkad); padding: 9px; text-align: center; font-size: .8em; }
.mkq .rslot.empty { border-top-color: var(--brd); color: #4a6484; }

/* ═══ Game frame column widths ═══
   Narrow the stat panel (left) and hand the freed width to the arena (centre)
   and the map (right). Overrides the 22%/56%/22% width attrs on the fixed-layout
   #wrapper table — one place instead of the 4 mirrored login/guest layout files. */
#wrapper table td:has(#statMenu)    { width: 280px; }   /* fixed: fits content, never wraps */
#wrapper table td:has(#displayArea) { width: 58%; }     /* arena — takes the lion's share of the freed width */
#wrapper table td:has(#moveMenu)    { width: 24%; }     /* map — a little wider than before */

/* ═══════════════════════════════════════════════════════════════════════════
   TUTORIAL POP-UPS (Patch 3.5.1) — one-time animated tips, driven by the TUT
   module at the end of gamefunctions.js. Overlay attaches to document.body
   (never displayArea) at z-index 9985 — below the craft alert (9998). The
   a11y-reduced-motion rule above already flattens every animation here. */
.tut-overlay {
    display: none; position: fixed; inset: 0; z-index: 9985;
    background: rgba(4, 3, 10, 0.62);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    align-items: center; justify-content: center; padding: 18px;
}
.tut-card {
    position: relative; width: 100%; max-width: 460px;
    background: linear-gradient(180deg, #171129, #0d0a18);
    border: 1px solid #4a3820;
    -webkit-box-shadow: inset 0 0 0 1px rgba(var(--mkg-rgb),0.07), 0 0 40px -8px rgba(var(--mkg-rgb),0.25), 0 24px 60px -20px #000;
    box-shadow: inset 0 0 0 1px rgba(var(--mkg-rgb),0.07), 0 0 40px -8px rgba(var(--mkg-rgb),0.25), 0 24px 60px -20px #000;
    padding: 22px 24px 14px; text-align: center;
    animation: tutFacetIn 0.28s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
@keyframes tutFacetIn {
    0%   { opacity: 0; transform: scale(0.86) translateY(10px); }
    100% { opacity: 1; transform: scale(1) translateY(0); }
}
.tut-gem {
    width: 14px; height: 14px; margin: 0 auto 10px;
    background: radial-gradient(circle at 35% 30%, #fff2c0, var(--mkg) 55%, #8a6a10);
    transform: rotate(45deg);
    animation: tutGemPulse 2.2s ease-in-out infinite;
}
@keyframes tutGemPulse {
    0%, 100% { box-shadow: 0 0 6px 1px rgba(var(--mkg-rgb),0.5); }
    50%      { box-shadow: 0 0 16px 4px rgba(var(--mkg-rgb),0.85); }
}
.tut-title {
    font-family: var(--mk-display, Georgia, serif);
    color: var(--mkg); font-size: 1.18em; letter-spacing: 0.05em;
    margin-bottom: 9px; text-shadow: 0 0 18px rgba(var(--mkg-rgb),0.3);
}
.tut-body {
    color: #b8b0cc; font-size: 0.9em; line-height: 1.65; text-align: left;
    animation: tutStepIn 0.22s ease-out;
}
.tut-body strong { color: var(--mkg); }
@keyframes tutStepIn {
    0%   { opacity: 0; transform: translateX(10px); }
    100% { opacity: 1; transform: translateX(0); }
}
.tut-dots { margin-top: 12px; }
.tut-dot {
    display: inline-block; width: 7px; height: 7px; margin: 0 3px;
    transform: rotate(45deg); background: #2a4a80;
    -webkit-box-shadow: inset 0 0 0 1px #3a3050; box-shadow: inset 0 0 0 1px #3a3050;
}
.tut-dot.on { background: var(--mkg); -webkit-box-shadow: 0 0 6px rgba(var(--mkg-rgb),0.7); box-shadow: 0 0 6px rgba(var(--mkg-rgb),0.7); }
.tut-btns {
    display: -webkit-box; display: -ms-flexbox; display: flex;
    -webkit-box-pack: justify; -ms-flex-pack: justify; justify-content: space-between;
    -webkit-box-align: center; -ms-flex-align: center; align-items: center;
    gap: 10px; margin-top: 14px;
}
.tut-btn {
    display: inline-block; padding: 5px 16px; text-decoration: none;
    font-family: var(--mk-mono, Consolas, monospace); font-size: 0.76em; letter-spacing: 0.05em;
    color: #9cb8d4; border: 1px solid var(--brd); background: #0e1626; white-space: nowrap;
}
.tut-btn:hover { color: #d8cff0; border-color: #4a3a6a; }
.tut-btn.gold {
    color: var(--mkg); border-color: var(--brd-gold);
    background: linear-gradient(180deg, var(--mkbtn-bg), var(--mkv));
    -webkit-box-shadow: 0 0 14px -6px rgba(var(--mkg-rgb),0.5); box-shadow: 0 0 14px -6px rgba(var(--mkg-rgb),0.5);
}
.tut-btn.gold:hover { color: #fff0a0; border-color: var(--mkgd); }
.tut-hint { color: #4a4260; font-size: 0.68em; margin-top: 10px; letter-spacing: 0.03em; }

/* In-card mini demo — four animated forge bars (craft-session tutorial). */
.tut-demo { margin: 12px 2px 2px; }
.tut-demo-bar {
    height: 8px; background: #070c16; margin-bottom: 5px; overflow: hidden;
    -webkit-box-shadow: inset 0 0 0 1px var(--brd); box-shadow: inset 0 0 0 1px var(--brd);
}
.tut-demo-bar i { display: block; height: 100%; width: 0; }
.tut-demo-bar.b1 i { background: linear-gradient(90deg, #8a4a1a, #cc7733); animation: tutBarFill 3.2s ease-in-out infinite; }
.tut-demo-bar.b2 i { background: linear-gradient(90deg, #5a2080, #a070d0); animation: tutBarFill 3.2s ease-in-out 0.4s infinite; }
.tut-demo-bar.b3 i { background: linear-gradient(90deg, #1a6a4a, #44aa88); animation: tutBarDrain 3.2s ease-in-out infinite; }
.tut-demo-bar.b4 i { background: linear-gradient(90deg, #1a4a7a, #5599cc); animation: tutBarDrain 3.2s ease-in-out 0.6s infinite; }
@keyframes tutBarFill  { 0% { width: 4%; }  45% { width: 86%; } 55% { width: 86%; } 100% { width: 4%; } }
@keyframes tutBarDrain { 0% { width: 92%; } 45% { width: 30%; } 55% { width: 30%; } 100% { width: 92%; } }

/* Spotlight mode — a step that names a UI element rings it. The overlay's own
   backdrop switches off and the ring's giant box-shadow paints the dim WITH a
   cut-out hole, so the highlighted element stays bright while the rest of the
   screen sleeps. The card docks to the bottom so it never sits on the target. */
.tut-overlay.spot {
    background: transparent;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    align-items: flex-end;
}
.tut-overlay.spot .tut-card { position: relative; z-index: 2; margin-bottom: 22px; }
.tut-ring {
    position: fixed; z-index: 1; pointer-events: none;
    border: 2px solid var(--mkg); border-radius: 7px;
    -webkit-box-shadow: 0 0 0 9999px rgba(4, 3, 10, 0.62), 0 0 14px 2px rgba(var(--mkg-rgb),0.55);
    box-shadow: 0 0 0 9999px rgba(4, 3, 10, 0.62), 0 0 14px 2px rgba(var(--mkg-rgb),0.55);
    animation: tutRingPulse 1.6s ease-in-out infinite;
    transition: left 0.22s ease, top 0.22s ease, width 0.22s ease, height 0.22s ease;
}
@keyframes tutRingPulse {
    0%, 100% { border-color: var(--mkg); -webkit-box-shadow: 0 0 0 9999px rgba(4,3,10,0.62), 0 0 10px 1px rgba(var(--mkg-rgb),0.45); box-shadow: 0 0 0 9999px rgba(4,3,10,0.62), 0 0 10px 1px rgba(var(--mkg-rgb),0.45); }
    50%      { border-color: #ffe27a; -webkit-box-shadow: 0 0 0 9999px rgba(4,3,10,0.62), 0 0 22px 5px rgba(var(--mkg-rgb),0.85); box-shadow: 0 0 0 9999px rgba(4,3,10,0.62), 0 0 22px 5px rgba(var(--mkg-rgb),0.85); }
}

/* ≤480 (phones): the card hugs the viewport; buttons stay tappable. */
@media (max-width: 480px) {
    .tut-overlay { padding: 10px; }
    .tut-card { padding: 16px 14px 10px; max-width: 100%; }
    .tut-body { font-size: 0.86em; }
    .tut-btn { padding: 7px 14px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PANEL-SWAP MOTION — fillDiv('displayArea', …) restarts .panel-in so
   every screen change reads as a brief fade + rise instead of an
   innerHTML jump-cut. dom.js throttles the restart (first swap in a
   450ms window only) so combat rounds and rapid menu hops stay instant.
   translateY is safe here: overlays that must not move (tutorial cards,
   crysPrompt) attach to document.body, not #displayArea. The
   body.a11y-reduced-motion rule jump-cuts this like everything else.
   ═══════════════════════════════════════════════════════════════════════ */
#displayArea.panel-in {
    -webkit-animation: panelIn 0.16s ease-out;
    animation: panelIn 0.16s ease-out;
}

/* ── Display-panel width normalisation (2026-08-15) ───────────────────────
   Every top-level info panel spans the full game pane. Panels arrive as
   DIRECT children of #displayArea with inline max-width shells (440px,
   540px, 560px…) or width:95% wrappers from dozens of endpoints — this one
   scoped rule levels all of them without touching each emitter. In-game
   only (body.in-game, stamped by updatestats.php) so the pre-login landing
   page keeps its own layout. Nested inner cards keep their widths — only
   the pane-level shell stretches. */
body.in-game #displayArea > div { max-width: 100% !important; }
body.in-game #displayArea > div[style*="width:95%"],
body.in-game #displayArea > div[style*="width: 95%"],
body.in-game #displayArea > div[style*="width:90%"],
body.in-game #displayArea > div[style*="width: 90%"] {
    width: 100% !important;
    box-sizing: border-box !important;   /* padded shells must not overflow at 100% */
}
@-webkit-keyframes panelIn {
    from { opacity: 0.35; -webkit-transform: translateY(5px); transform: translateY(5px); }
    to   { opacity: 1;    -webkit-transform: none;            transform: none; }
}
@keyframes panelIn {
    from { opacity: 0.35; -webkit-transform: translateY(5px); transform: translateY(5px); }
    to   { opacity: 1;    -webkit-transform: none;            transform: none; }
}

/* ── Combat-result juice (dom.js _fxCombatJuice adds these) ──────────────
   .loot-glint — a narrow white band sweeps twice across Rare+ drop names.
   The overlay rides on ::after with screen blending so the name keeps its
   inline rarity colour underneath.
   .rw-ghost — a "+EXP  +gold" clone drifts up out of the victory banner
   and fades; fill-mode forwards means the a11y jump-cut leaves it ended
   (invisible) rather than stuck on screen. */
.loot-glint { position: relative; display: inline-block; }
.loot-glint::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    mix-blend-mode: screen;
    background: linear-gradient(115deg, transparent 0%, rgba(255,255,255,0.9) 50%, transparent 100%);
    background-size: 60px 100%;
    background-repeat: no-repeat;
    background-position: -60px 0;
    -webkit-animation: lootGlint 1.1s ease-in-out 0.25s 2;
    animation: lootGlint 1.1s ease-in-out 0.25s 2;
}
@-webkit-keyframes lootGlint { to { background-position: calc(100% + 60px) 0; } }
@keyframes lootGlint         { to { background-position: calc(100% + 60px) 0; } }

/* .rw-ghost (reward drift) is now FULLY self-contained in dom.js — inline
   styles + Web Animations API — after a stale-stylesheet client showed it as
   dead unanimated text (2026-08-15). No CSS here on purpose: keeping a CSS
   animation too would double-drive the same properties. */

/* ── Large Touch Controls (Settings toggle, 2026-08-23) ─────────────────────
   Tablets run the DESKTOP layout — which is the right call for the screen size
   — but that layout assumes a mouse and a keyboard. The movement compass is
   28x23px (well under any touch-target guideline) and the map tiles are
   smaller still, so with no keyboard to fall back on, moving meant poking at
   single map cells. This scales both up on demand without touching the mouse
   experience. Owner report 2026-08-23.
   Toggled by A11Y (gamefunctions.js) as body.a11y-big-touch. */
body.a11y-big-touch .dpad-btn-d {
    width: 54px;
    height: 46px;
    font-size: 1.25em;
    border-radius: 9px;
}
body.a11y-big-touch .dpad-diag-d {
    width: 45px;
    height: 39px;
    font-size: 1.05em;
}
body.a11y-big-touch .dpad-ctr-d {
    width: 44px;
    height: 40px;
}
body.a11y-big-touch .dpad-row-d { margin-bottom: 5px; }
/* Map cells: the other half of "I can only move by clicking tiny tiles". */
body.a11y-big-touch #mapMenu td,
body.a11y-big-touch #mapMenu img {
    min-width: 26px;
    min-height: 26px;
}

/* The compass is deliberately hidden at map zoom 1 (maprender.php:1163 — the 8
   neighbour tiles are click-to-move there, and hiding it gives the enlarged map
   the space). That trade assumes a mouse: on a tablet those tiles are a few
   pixels across and there is no keyboard to fall back on, which left touch
   players with no practical way to walk. This forces it back on at every zoom.
   ⚠ !important is required — maprender.php sets style.display inline. */
body.a11y-big-touch #moveMenu .dpad-wrap { display: inline-block !important; }

/* ── Free-form panel layout (2026-08-25) ──────────────────────────────────
   The three columns and the chat dock were nested <table> rows with hardcoded
   width='26%' attributes. They are grids now, driven ENTIRELY by custom
   properties, so a drag handle only has to move a number and the browser does
   the rest. The defaults below reproduce the old table widths exactly
   (26/52/22 and 75/25) — a player who never touches a splitter sees no change.
   Persisted per device in localStorage; see layoutInit() in gamefunctions.js.
   ⚠ fr units, not %: the splitters take their fixed 6px first and the columns
   divide what is left, so the numbers never have to sum to 100. */
/* ── The layout FILLS THE VIEWPORT (2026-08-25) ───────────────────────────
   Before this the panes were a hard 585px and the dock sat below them, so
   scaling the UI up (browser/Steam zoom) pushed the chat off the bottom of
   the screen instead of making it smaller — owner report: "the chat just
   gets pushed down rather than shrink". Now the two rows SHARE the height:
   whatever the window gives us is divided between panes and chat, and the
   horizontal handle moves the split rather than an absolute pixel count.
   ⚠ The height is set by layoutFit() in JS from the element's own top offset,
   NOT a hardcoded calc(100vh - 128px) — the navbar height is not a constant
   (it wraps on narrow windows and grows with the font scale). */
#gameLayout {
    width: 99%; margin: 0 auto;
    display: grid;
    grid-template-rows: var(--row-top, 585fr) 10px var(--row-bot, 464fr);
    min-height: 320px;
}
#gameGrid, #chatDock { min-height: 0; height: 100%; }
#chatMain { display: flex; flex-direction: column; min-height: 0; }
#gameGrid {
    display: grid;
    grid-template-columns: var(--gc-l, 26fr) 10px var(--gc-m, 52fr) 10px var(--gc-r, 22fr);
    align-items: start;
}
#chatDock {
    /* A flex ROW since panels can move (5 Oct 2026) — it was a two-track grid
       holding exactly chat + online. The first panel in it grows by the chat
       weight (--lay-a), every other by the online weight (--lay-b); see "Panels
       that move, hide and fold" below. */
    display: flex;
    /* ⚠ The dock and everything in it are transparent, so the BODY background
       showed through — and body carries a radial gradient anchored at
       'ellipse 60% 30% at 85% 100%', i.e. bottom-RIGHT, exactly under the
       online list. Its fade edge landed mid-list and read as "the online list
       colour changes halfway down". One flat surface for the whole dock kills
       the seam for the roster AND the chat side. */
    background: #090c1a;
    /* ⚠ stretch, NOT start: #onlineList fills its track by BEING stretched
       now, replacing the absolute-inset trick it used inside the old <td>. */
    align-items: stretch;
}
/* The position/inset reset that used to live here is gone with the bare rule it
   existed to cancel (2026-08-31). What remains is what this container actually
   needs: the grid stretches the item, min-height:0 lets it scroll rather than
   stretch the row. */
/* (Was `#chatDock > #onlineList`. The list now sits in its own .gpanel wrapper,
   so the bottom row sizes and scrolls the wrapper's content — see "Panels that
   move, hide and fold" below.) */
#chatMain { min-width: 0; }
#chatMain > #chatBox, #chatMain > #chatTabs { flex: 0 0 auto; }
#gameGrid > .game-pane { min-width: 0; }

/* Drag handles. The hit area is the whole 6px cell; the visible hairline is
   the ::after, so the grab target is comfortable without looking heavy. */
.gsplit { position: relative; background: transparent; }
.gsplit::after {
    content: ''; position: absolute; background: var(--brd);
    opacity: .85; border-radius: 3px;
    -webkit-transition: opacity .12s ease, background .12s ease, box-shadow .12s ease;
    transition: opacity .12s ease, background .12s ease, box-shadow .12s ease;
}
/* The grip: three dots centred on the bar, the universal "drag me" cue. */
.gsplit::before {
    content: ''; position: absolute; z-index: 1;
    background-image: radial-gradient(circle, #6a5a9a 1px, transparent 1.2px);
    opacity: .9; pointer-events: none;
}
.gsplit-v::before { left: 50%; top: 50%; width: 4px; height: 26px;
    -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%);
    background-size: 4px 7px; }
.gsplit-h::before { top: 50%; left: 50%; width: 26px; height: 4px;
    -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%);
    background-size: 7px 4px; }
.gsplit:hover::before, .gsplit.gs-active::before { background-image: radial-gradient(circle, #1a1004 1px, transparent 1.2px); }
/* ⚠⚠ align-self:stretch is LOAD-BEARING. #gameGrid uses align-items:start so
   the panes sit at the top; an EMPTY splitter div then collapses to 0 height
   — 10px wide and nothing to grab. That is why the first build read as
   "you've only provided buttons": the drag handles were literally unclickable. */
.gsplit-v { cursor: col-resize; align-self: stretch; min-height: 40px; }
.gsplit-v::after { left: 50%; -webkit-transform: translateX(-50%); transform: translateX(-50%); top: 6px; bottom: 6px; width: 4px; }
.gsplit-h { cursor: row-resize; height: 10px; }
.gsplit-h::after { top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); left: 6px; right: 6px; height: 4px; }
.gsplit:hover::after, .gsplit.gs-active::after { opacity: 1; background: var(--mkgd); box-shadow: 0 0 8px rgba(var(--mkgd-rgb),.55); }

/* While dragging: kill text selection and keep the resize cursor everywhere,
   otherwise the pointer flickers as it crosses panels. */
body.gs-drag, body.gs-drag * { -webkit-user-select: none; user-select: none; }
body.gs-drag-col, body.gs-drag-col * { cursor: col-resize !important; }
body.gs-drag-row, body.gs-drag-row * { cursor: row-resize !important; }


/* Layout controls, docked beside the existing chat toggle so they cost no
   vertical space — the whole point of the exercise was to give space back. */
#layoutCtl {
    position: fixed; right: 16px; bottom: 52px; z-index: 900;
    display: flex; gap: 4px; align-items: center;
}
#layoutCtl .lc-btn {
    background: linear-gradient(180deg,#141a30,#0d1120);
    border: 1px solid var(--brd); color: #b9a8e8;
    font-family: var(--mk-display, Georgia, serif); font-size: 0.74em; letter-spacing: .04em;
    padding: 5px 9px; border-radius: 14px; cursor: pointer;
    -webkit-user-select: none; user-select: none;
    -webkit-transition: color .12s ease, border-color .12s ease;
    transition: color .12s ease, border-color .12s ease;
}
#layoutCtl .lc-btn:hover { color: var(--mkg); border-color: var(--mkgd); }
#layoutCtl .lc-btn.on    { color: var(--mkg); border-color: var(--mkgd); }

/* Narrow screens: stack and stop offering resize — the desktop grid makes no
   sense at phone width, and mobile.html is a separate shell anyway. */
@media screen and (max-width: 900px) {
    #gameGrid, #chatDock { grid-template-columns: 1fr; }
    .gsplit, #layoutCtl { display: none; }
    #chatDock { flex-direction: column; }
}

/* ═══ Panels that move, hide and fold (owner, 5 Oct 2026: "per device") ═══════
   game_shell.php wraps every side panel in a .gpanel and marks the three docks
   with data-dock (#paneLeft, #paneRight, the bottom row #chatDock);
   gamefunctions.js (panelsApply) moves the wrappers and sets the classes below.
   A player who never customises sees the shell exactly as before: a wrapper has
   no frame, no padding and no bar unless it is folded or being customised. */
.gpanel { position: relative; min-width: 0; }
.gpanel.lay-hidden { display: none !important; }
.gpanel.lay-folded > :not(.gp-bar) { display: none !important; }

/* Every grid child pinned to its own track, so a dock that folds away
   (display:none) cannot slide the others into the wrong column. Wide screens
   only: under 900px the grid stacks (above). */
@media screen and (min-width: 901px) {
    #paneLeft  { grid-column: 1; }  #gsplitL { grid-column: 2; }  #paneMid { grid-column: 3; }
    #gsplitR   { grid-column: 4; }  #paneRight { grid-column: 5; }
    #gameGrid > * { grid-row: 1; }
    body.lay-no-left  #gameGrid { grid-template-columns: 0 0 var(--gc-m, 52fr) 10px var(--gc-r, 22fr); }
    body.lay-no-right #gameGrid { grid-template-columns: var(--gc-l, 26fr) 10px var(--gc-m, 52fr) 0 0; }
    body.lay-no-left.lay-no-right #gameGrid { grid-template-columns: 0 0 1fr 0 0; }
}
body.lay-no-left #paneLeft,  body.lay-no-left #gsplitL,
body.lay-no-right #paneRight, body.lay-no-right #gsplitR,
body.lay-no-bottom #chatDock, body.lay-no-bottom #gsplitH { display: none; }
/* An empty bottom row gives its height back to the panes (what Hide Chat always did). */
body.lay-no-bottom #gameLayout { grid-template-rows: 1fr 0 0; }

/* An OLD shell (chat and online as bare children of the row, drawn before panels existed) keeps its old split for the
   moment a new stylesheet meets it during a deploy. The new shell never matches these: its children are .gpanel. */
#chatDock > #chatMain   { flex: var(--lay-a, 75) 1 0; }
#chatDock > #onlineList { flex: var(--lay-b, 25) 1 0; min-height: 0; overflow-y: auto; }
/* The bottom row: panels side by side, filling its height. */
#chatDock > .gpanel { flex: var(--lay-b, 25) 1 0; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
#chatDock > .gpanel.lay-first { flex-grow: var(--lay-a, 75); }
#chatDock > .gpanel.lay-folded { flex: 0 0 auto; }
#chatDock > .gsplit-v { flex: 0 0 10px; }
#chatDock > .gpanel > :not(.gp-bar) { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#chatDock > .gpanel > #chatMain { overflow: hidden; }   /* #chatRoom scrolls inside it */
#chatDock > .gpanel > #dungeonEnterArea { flex: 0 0 auto; } /* rides with the map; empty, so no share of the height */
/* Chat in a column needs a height of its own: the column scrolls, so without one
   the message list would grow to its full length instead of scrolling. */
.game-pane > .gpanel[data-panel="chat"]:not(.lay-folded) { height: 46vh; min-height: 260px; display: flex; flex-direction: column; }
.game-pane > .gpanel[data-panel="chat"] > #chatMain { flex: 1 1 auto; min-height: 0; }
.game-pane > .gpanel[data-panel="online"] > #onlineList { max-height: 40vh; }

/* The title bar: shown while customising, and on a folded panel (then with only
   its unfold button). */
.gp-bar {
    display: none; align-items: center; gap: 5px; padding: 3px 4px; margin-bottom: 4px;
    border: 1px solid var(--brd);
    background: linear-gradient(180deg, rgba(var(--mkg-rgb),.08), rgba(0,0,0,.2)), var(--mkp);
    font-family: var(--mk-mono, ui-monospace, monospace); font-size: .68em; letter-spacing: .1em;
    text-transform: uppercase; color: #a9bdd4;
    -webkit-user-select: none; user-select: none;
}
body.lay-editing .gpanel > .gp-bar, .gpanel.lay-folded > .gp-bar { display: flex; }
.gp-title { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gp-tools { display: flex; gap: 3px; }
.gp-btn, .gp-grip {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; height: 22px; padding: 0 5px; margin: 0;
    border: 1px solid #2a2440; background: rgba(10,8,20,.6); color: #a0c0dc;
    cursor: pointer; font-family: inherit; font-size: 12px; letter-spacing: 0; line-height: 1;
}
.gp-btn:hover, .gp-grip:hover { border-color: var(--mkgd); color: var(--mkg); }
.gp-grip { cursor: grab; touch-action: none; font-size: 14px; }
body:not(.lay-editing) .gp-grip, body:not(.lay-editing) .gp-move,
body:not(.lay-editing) .gp-hide, body:not(.lay-editing) .gp-opt { display: none; }

/* Customising: dashed frames on the docks and the panels, a drop target at the
   end of every dock, a gold line where a dragged panel will land. */
body.lay-editing [data-dock] { outline: 1px dashed rgba(var(--mkg-rgb),.28); outline-offset: -1px; background-color: rgba(var(--mkg-rgb),.02); }
body.lay-editing .gpanel { outline: 1px dashed rgba(var(--mkg-rgb),.35); outline-offset: 1px; min-height: 28px; }
.lay-ph {
    display: none; align-items: center; justify-content: center; min-height: 54px; margin-top: 4px; padding: 6px;
    border: 1px dashed rgba(var(--mkg-rgb),.35); color: #8a7a50; text-align: center;
    font-family: var(--mk-mono, ui-monospace, monospace); font-size: .68em; letter-spacing: .1em; text-transform: uppercase;
}
body.lay-editing .lay-ph { display: flex; }
#chatDock > .lay-ph { flex: 0 0 130px; margin: 0; }
.lay-mark { flex: 0 0 auto; height: 4px; margin: 2px 0 6px; background: var(--mkg); box-shadow: 0 0 10px rgba(var(--mkg-rgb),.8); border-radius: 2px; }
.lay-mark.lay-mark-v { width: 4px; height: auto; margin: 0 2px; align-self: stretch; }
.gpanel.lay-dragging { opacity: .35; }
.lay-ghost {
    position: fixed; z-index: 3000; pointer-events: none; padding: 6px 12px;
    border: 1px solid var(--mkgd); background: #140e22; color: var(--mkg);
    font-family: var(--mk-mono, ui-monospace, monospace); font-size: .74em; letter-spacing: .1em; text-transform: uppercase;
    box-shadow: 0 10px 30px rgba(0,0,0,.6);
    -webkit-transform: translate(12px, 10px); transform: translate(12px, 10px);
}
body.lay-dnd, body.lay-dnd * { cursor: grabbing !important; -webkit-user-select: none; user-select: none; }

/* The toolbar between the navbar and the panes, only while customising. */
#layBar { display: none; }
body.lay-editing #layBar {
    display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px;
    width: 99%; margin: 5px auto 0; padding: 6px 10px;
    border: 1px solid var(--brd-gold);
    background: linear-gradient(180deg, rgba(var(--mkg-rgb),.10), rgba(var(--mkg-rgb),.03)), var(--mkp);
    font-size: .84em;
}
#layBar .lb-title { font-family: var(--mk-display, Georgia, serif); color: var(--mkg); letter-spacing: .04em; }
#layBar .lb-hint  { color: #b8c8dc; font-size: .92em; }
#layBar .lb-grp   { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
#layBar .lb-lab   { font-family: var(--mk-mono, ui-monospace, monospace); font-size: .7em; letter-spacing: .1em; text-transform: uppercase; color: #7fa0c0; }
#layBar .lb-none  { color: #5f7390; font-style: italic; font-size: .88em; }
#layBar .lb-sp    { flex: 1; }

/* ⇄ and ⋯ pop-up menus */
.lay-pop {
    position: fixed; z-index: 2500; min-width: 190px; padding: 4px;
    background: linear-gradient(180deg, var(--mkp2), var(--mkp)); border: 1px solid var(--mkgd);
    box-shadow: 0 12px 30px rgba(0,0,0,.6); font-size: .86em;
}
.lay-pop .lp-h { font-family: var(--mk-mono, ui-monospace, monospace); font-size: .66em; letter-spacing: .12em; text-transform: uppercase; color: #7fa0c0; padding: 4px 8px 2px; }
.lay-pop a, .lay-pop label, .lay-pop .lp-off { display: flex; gap: 8px; align-items: center; padding: 5px 8px; color: #c9d6ea; cursor: pointer; }
.lay-pop a:hover { background: rgba(var(--mkg-rgb),.08); color: var(--mkg); }
.lay-pop .lp-off { opacity: .4; cursor: default; }
.lay-pop hr { border: 0; border-top: 1px solid var(--brd); margin: 4px 0; }

/* Character regions a player switched off (updatestats.php wraps each in .sp-<letter>). */
body.lay-off-V #statMenu .sp-V, body.lay-off-G #statMenu .sp-G, body.lay-off-D #statMenu .sp-D { display: none; }

/* The ✎ Layout button counts what is hidden. */
#layoutCtl .lc-n { font-family: var(--mk-mono, ui-monospace, monospace); font-size: .82em; color: #1a1004; background: var(--mkg); border-radius: 8px; padding: 0 5px; margin-left: 4px; }
/* Admin messages — a red glow so staff posts carry weight in a busy channel
   (owner call 2026-08-26; was a slate glow matching the text colour).
   ⚠ The glow is DELIBERATELY a different hue from the text: the message itself
   still renders in ADMIN_CHAT_COLOUR (#a89d9f, db.php) with the matching
   [Admin] tag. If the glow ever needs to match the text again, that constant is
   the one to read — do not assume this rgba() tracks it.
   Applied by _chatRow() in readchatroom.php via .chat-msg-admin. */
.chat-msg-admin {
    /* Glow on the TEXT only — no rail, no tint, no panel shadow. The row keeps
       the ordinary .chat-msg background and hover.
       ⚠ TWO layers on purpose. A darker red on a dark ground loses presence, so
       weight comes from a tight near-opaque core plus a wider soft halo, not
       from a brighter hue. Editing this to a single shadow will look washed out. */
    text-shadow: 0 0 4px rgba(170, 17, 51, 0.95),
                 0 0 12px rgba(170, 17, 51, 0.55);
}
/* High-contrast mode already forces its own rail colour; drop the glow there
   so the text stays crisp for anyone who turned that on deliberately. */
body.a11y-high-contrast .chat-msg-admin {
    text-shadow: none;
    -webkit-box-shadow: none;
    box-shadow: none;
}
/* ══════════════════════════════════════════════════════════════════════════
   CLASS THEMES — Curio of Vanities (2026-08-25)

   A theme is ONLY a token override. Nothing below restyles a component: every
   rule in this file already reads --mkv/--mkp/--mke/--mkg/--mkgd and the three
   *-rgb channel tokens, so re-pointing those retints the whole interface —
   ground, panels, frames, glows and shoulder gradients together.

   ⚠ DELIBERATELY NOT THEMED: item rarity colours, the HP/MP bar fills and
   damage numbers. Those carry MEANING — a Legendary must read as Legendary
   whichever theme the player bought — so they stay literal everywhere.

   Applied as a class on <body> by crysApplyTheme() (gamefunctions.js).
   Sold in the wardrobe as the 'theme' cosmetic slot (cosmetic_helper.php).
   ══════════════════════════════════════════════════════════════════════════ */

/* Aetherforge — Runesmith. Gunmetal and rune-light; the highest-contrast set. */
body.theme-aetherforge{
  --mkbtn-bg:#08161a;
  --mkv:#060d10; --mkp:#0d1a20; --mkp2:#12242c; --mkp3:#183038;
  --mke:#1c5a68; --mkes:#0e2c34; --mkad:#1c5a68;
  --mkg:#33e0ff; --mkgd:#00ccee; --mkg-rgb:51,224,255; --mkgd-rgb:0,204,238;
  --mka:#7fe8ff; --mka-rgb:127,232,255;
  --mkink:#e0f8ff; --mkmist:#8fc0cc;
  --brd:#1c5a68; --brd-soft:#0e2c34; --brd-gold:#0a4a58;
}

/* One Life — the Hardcore shop (11 Oct 2026). Black and blood red; worn on any side. */
body.theme-onelife{
  --mkbtn-bg:#160808;
  --mkv:#0a0404; --mkp:#160808; --mkp2:#200c0c; --mkp3:#2a1010;
  --mke:#6a1c1c; --mkes:#2e0e0e; --mkad:#6a1c1c;
  --mkg:#ff5555; --mkgd:#dd3333; --mkg-rgb:255,85,85; --mkgd-rgb:221,51,51;
  --mka:#ff9999; --mka-rgb:255,153,153;
  --mkink:#f4e4e4; --mkmist:#c09a9a;
  --brd:#6a1c1c; --brd-soft:#2e0e0e; --brd-gold:#5a1010;
}

/* Sepulchre — Necromancer. Grave-violet on near-black; the darkest ground. */
body.theme-sepulchre{
  --mkbtn-bg:#150a1e;
  --mkv:#0a0610; --mkp:#150d1e; --mkp2:#1d1229; --mkp3:#261634;
  --mke:#4a2a6a; --mkes:#2a1740; --mkad:#4a2a6a;
  --mkg:#b060ff; --mkgd:#9020d0; --mkg-rgb:176,96,255; --mkgd-rgb:144,32,208;
  --mka:#c890ff; --mka-rgb:200,144,255;
  --mkink:#f0e6f8; --mkmist:#a894c0;
  --brd:#4a2a6a; --brd-soft:#2a1740; --brd-gold:#5a2a7a;
}

/* Dawnwarden — Paladin. The whole stack warms, not just the accent: the only
   theme that reads lit rather than lit-from-within. */
body.theme-dawnwarden{
  --mkbtn-bg:#17120a;
  --mkv:#0f0d08; --mkp:#1c1810; --mkp2:#262013; --mkp3:#332b19;
  --mke:#6a5a2a; --mkes:#3a3018; --mkad:#6a5a2a;
  --mkg:#ffd97a; --mkgd:#c9a020; --mkg-rgb:255,217,122; --mkgd-rgb:201,160,32;
  --mka:#ffe9b0; --mka-rgb:255,233,176;
  --mkink:#fff6e0; --mkmist:#c8b894;
  --brd:#6a5a2a; --brd-soft:#3a3018; --brd-gold:#8a7a3a;
}

/* ══════════════════════════════════════════════════════════════════════════
   THEME TYPOGRAPHY (2026-08-28) — the second theme axis.

   A theme already re-points the colour tokens; it now re-points the three TYPE
   tokens too, so each one has a voice as well as a palette.

   ⚠⚠ SYSTEM FACES ONLY, DELIBERATELY. Nothing here downloads a font. The game
     runs inside a Steam shell as well as a browser, a web-font link is
     render-blocking on every single page load, and the previous Cinzel /
     IM Fell / Exo 2 @import was removed from this file for exactly that reason
     — re-adding one is a performance decision for the owner, not a side effect
     of a cosmetic. Every stack below degrades through at least two widely
     installed faces to a generic family, so a theme never lands on a default it
     did not choose.

   ⚠ PREREQUISITE, done in the same change: 17 rules here (and 8 inline styles
     in index.html) still named 'Cinzel' literally. Cinzel has not been loaded
     since the @import was dropped, so those rules were silently rendering
     Georgia while the 63 tokenised rules obeyed --mk-display. A font theme over
     that would have retinted two thirds of the interface and left the rest —
     the same half-applied mess the .mkbtn chassis had. They are all
     var(--mk-display) now.

   ⚠ --mk-mono is only re-pointed where the theme has a real reason to. Numbers
     want a tabular face far more than they want personality, and a bad mono
     makes stat blocks harder to read for a cosmetic gain nobody asked for.  */

/* Amethyst Court — the default. No body class; the :root tokens at the end of
   this file already define it. Listed here only so the set reads as nine. */

/* Aetherforge — Runesmith. Rune-light on gunmetal, so the display face is the
   technical one: a mono heading reads as machined rather than written. */
body.theme-aetherforge{
  --mk-display: Consolas, 'Cascadia Mono', 'SF Mono', 'DejaVu Sans Mono', ui-monospace, monospace;
  --mk-body:    'Segoe UI', ui-sans-serif, system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* Sepulchre — Necromancer. Old-style serif with heavy stroke contrast; the
   body copy goes serif too, the only theme where it does. It should read like
   something exhumed, not like an app. */
body.theme-sepulchre{
  --mk-display: 'Palatino Linotype', Palatino, 'Book Antiqua', 'URW Palladio L', Georgia, serif;
  --mk-body:    'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, serif;
}

/* Dawnwarden — Paladin. Garamond's humanist serif is the illuminated-manuscript
   face; wide and calm rather than grand. */
body.theme-dawnwarden{
  --mk-display: Garamond, 'EB Garamond', 'Apple Garamond', 'Times New Roman', Georgia, serif;
  --mk-body:    ui-sans-serif, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* Bloodrite — Death Knight. A slab: blunt, weighty, no taper. The only theme
   whose headings are meant to feel like an object rather than a voice. */
body.theme-bloodrite{
  --mk-display: Rockwell, 'American Typewriter', 'Roboto Slab', 'Courier New', Georgia, serif;
  --mk-body:    ui-sans-serif, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* Wildheart — Tamer & Archer. Humanist sans, carved rather than drawn; the
   warmest sans available without downloading anything. */
body.theme-wildheart{
  --mk-display: Candara, Optima, 'Gill Sans', 'Gill Sans MT', 'Segoe UI', sans-serif;
  --mk-body:    Candara, Optima, 'Gill Sans MT', 'Segoe UI', ui-sans-serif, sans-serif;
}

/* Starfall — Astromancer. High-contrast transitional serif: the closest thing
   the system stack has to something engraved on an instrument. */
body.theme-starfall{
  --mk-display: Constantia, Cambria, 'Hoefler Text', 'Times New Roman', Georgia, serif;
  --mk-body:    ui-sans-serif, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* Corsair — Skypirate. Geometric sans, open and bright. The one theme that
   reads as daytime, and its type should not be gothic about it. */
body.theme-corsair{
  --mk-display: 'Century Gothic', Futura, 'Avenir Next', 'Trebuchet MS', 'Segoe UI', sans-serif;
  --mk-body:    'Trebuchet MS', 'Avenir Next', ui-sans-serif, system-ui, 'Segoe UI', sans-serif;
}

/* Fracture — Hexblade. Tight, tall, high-impact grotesque. The loudest palette
   gets the loudest type; it is the theme people buy to be seen using. */
body.theme-fracture{
  --mk-display: 'Franklin Gothic Medium', 'Arial Narrow', Haettenschweiler, 'Helvetica Neue', Impact, sans-serif;
  --mk-body:    ui-sans-serif, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE REMAINING FIVE PALETTES (2026-08-28)
   Ground/panel/edge/accent values are the owner-approved ones from the class
   themes pitch. The tokens the pitch did not name are derived the same way the
   first three were: --mkp2/--mkp3 step the panel lighter, --mkes is the edge
   darkened, --mkad matches --mke, --mka is the accent lifted, --mkink is a
   near-white carrying the hue, --mkmist is the muted mid.

   ⚠ The *-rgb channels are NOT optional. rgba() cannot take a hex custom
     property, so every glow and translucent fill reads --mkg-rgb/--mkgd-rgb/
     --mka-rgb. A theme that sets the hex and forgets the channel keeps the
     PREVIOUS theme's glow colour.
   ⚠ Item rarity, HP/MP bar fills, damage numbers and the staff chat glow are
     deliberately NOT themed anywhere — they carry meaning, and a Legendary must
     read as Legendary under every palette. */

/* Bloodrite — Death Knight. ⚠ The risky one: red is the damage colour
   everywhere else. It holds because the HP bar keeps its own crimson and the
   accent sits brighter than it — worth watching in a real fight. */
body.theme-bloodrite{
  --mkbtn-bg:#1a0a0c;
  --mkv:#0c0708; --mkp:#1a0e10; --mkp2:#241416; --mkp3:#2f1b1e;
  --mke:#5a1e22; --mkes:#331113; --mkad:#5a1e22;
  --mkg:#e04444; --mkgd:#aa2222; --mkg-rgb:224,68,68; --mkgd-rgb:170,34,34;
  --mka:#ff8a8a; --mka-rgb:255,138,138;
  --mkink:#ffeaea; --mkmist:#c09a9a;
  --brd:#5a1e22; --brd-soft:#331113; --brd-gold:#7a2a2e;
}

/* Wildheart — Tamer & Archer. Deep forest floor with new-growth green. Shared
   between the two nature classes: their palettes were within a few points of
   each other and two near-identical greens would read as padding. */
body.theme-wildheart{
  --mkbtn-bg:#0c1a0c;
  --mkv:#070d08; --mkp:#101a10; --mkp2:#172417; --mkp3:#1f301f;
  --mke:#2e5a2a; --mkes:#193318; --mkad:#2e5a2a;
  --mkg:#9ee060; --mkgd:#66bb55; --mkg-rgb:158,224,96; --mkgd-rgb:102,187,85;
  --mka:#c4f090; --mka-rgb:196,240,144;
  --mkink:#eefae4; --mkmist:#a0bc98;
  --brd:#2e5a2a; --brd-soft:#193318; --brd-gold:#3d7a36;
}

/* Starfall — Astromancer. Midnight blue rather than black, so the ground reads
   as sky. Closest of the set to the default — deliberately, as the gentle
   option for players who like the current look but want it cooler. */
body.theme-starfall{
  --mkbtn-bg:#111530;
  --mkv:#080a1a; --mkp:#121630; --mkp2:#1a1f40; --mkp3:#232a52;
  --mke:#3a3a7a; --mkes:#20214a; --mkad:#3a3a7a;
  --mkg:#cfa8ff; --mkgd:#b98ce6; --mkg-rgb:207,168,255; --mkgd-rgb:185,140,230;
  --mka:#e2ccff; --mka-rgb:226,204,255;
  --mkink:#f0eeff; --mkmist:#a6a4cc;
  --brd:#3a3a7a; --brd-soft:#20214a; --brd-gold:#4d4d99;
}

/* Corsair — Skypirate. Open sky over deep water; the lightest ground of the
   set, and the only theme that makes the game feel like daytime. */
body.theme-corsair{
  --mkbtn-bg:#0a1a24;
  --mkv:#061018; --mkp:#0e1c28; --mkp2:#142838; --mkp3:#1c3648;
  --mke:#2a5a70; --mkes:#153241; --mkad:#2a5a70;
  --mkg:#7fd0ff; --mkgd:#4488dd; --mkg-rgb:127,208,255; --mkgd-rgb:68,136,221;
  --mka:#b0e4ff; --mka-rgb:176,228,255;
  --mkink:#e8f6ff; --mkmist:#93b4c8;
  --brd:#2a5a70; --brd-soft:#153241; --brd-gold:#37768f;
}

/* Fracture — Hexblade. Crystal-fracture magenta on obsidian. The loudest of the
   set, priced accordingly and worth keeping uncommon. */
body.theme-fracture{
  --mkbtn-bg:#1c0a1c;
  --mkv:#0d0710; --mkp:#1a0d1c; --mkp2:#251327; --mkp3:#311a33;
  --mke:#6a2a5a; --mkes:#3d1734; --mkad:#6a2a5a;
  --mkg:#ff66cc; --mkgd:#aa3388; --mkg-rgb:255,102,204; --mkgd-rgb:170,51,136;
  --mka:#ffa3e0; --mka-rgb:255,163,224;
  --mkink:#ffeaf8; --mkmist:#c396b6;
  --brd:#6a2a5a; --brd-soft:#3d1734; --brd-gold:#8a3a74;
}

/* ══════════════════════════════════════════════════════════════════════════
   CHAT GLOWS — Curio of Vanities + seasonal events (2026-08-25)

   Applied by readchatroom.php: on the message BODY in chat (never the row,
   so the timestamp and report flag stay clean) and on the NAME in the
   online list. Keyed to the author, so a glow lights up old messages and
   cannot be forged by typing markup.

   ⚠ The staff red (#aa1133) is RESERVED — it is set inline in the online
   list and via .chat-msg-admin in chat, and is deliberately NOT a class
   here so it can never be sold.
   ⚠ 'Resonance' has no class: it glows in the player's own chat colour via
   an inline text-shadow, so .cg alone carries no shadow.
   ══════════════════════════════════════════════════════════════════════════ */
.cg { /* Resonance and the no-glow case — the inline style supplies it */ }
/* ⚠ INTENSITY, NOT HUE, is what makes a chat glow unreadable. These sit on
   ~12px text whose colour is baked into each stored message, so a glow can
   land on ANY hue — cyan text under a strong pink glow renders as mud, and
   the player reads it as "the purple is broken". The first attempt at a fix
   LIGHTENED the hue, which made the wash worse.
   Values are 5px/.45 + 16px/.70 (owner-tuned 2026-08-26).
   ⚠ The core is 5px again, but at .55 — NOT the .85 that made the original
     unreadable. It is the core's ALPHA that fills the glyphs, not its radius:
     5px/.85 was mud, 5px/.45 is not. If legibility complaints return, drop the
     core alpha further before touching anything else. Original was 5px/.85 + 13px/.40, which was a
   fill rather than a glow; a first pass at 3px/.50 + 9px/.26 fixed legibility
   but went too faint to read as a purchased cosmetic at all.
   ⚠ To brighten further: widen and lift the 12px halo. NEVER the 3px core. */
.cg-ember      { text-shadow: 0 0 5px rgba(255,136,68,.45), 0 0 16px rgba(255,136,68,.70); }   /* Emberlight */
.cg-frost      { text-shadow: 0 0 5px rgba(127,232,255,.45), 0 0 16px rgba(127,232,255,.70); }   /* Frostfire */
.cg-verdant    { text-shadow: 0 0 5px rgba(158,224,96,.45), 0 0 16px rgba(158,224,96,.70); }   /* Verdant Wisp */
.cg-arcane     { text-shadow: 0 0 5px rgba(176,96,255,.45), 0 0 16px rgba(176,96,255,.70); }   /* Arcane Bloom */
.cg-bone       { text-shadow: 0 0 5px rgba(240,230,208,.45), 0 0 16px rgba(240,230,208,.70); }   /* Bonelight */
.cg-abyssal    { text-shadow: 0 0 5px rgba(74,122,210,.45), 0 0 16px rgba(74,122,210,.70); }   /* Abyssal */
.cg-void       { text-shadow: 0 0 5px rgba(255,102,204,.45), 0 0 16px rgba(255,102,204,.70); }   /* Voidtouched */
.cg-bloodlight { text-shadow: 0 0 5px rgba(221,68,68,.50), 0 0 16px rgba(221,68,68,.75); }       /* Bloodlight — the Hardcore shop (11 Oct 2026) */
.cg-graveglow  { text-shadow: 0 0 5px rgba(120,200,120,.45), 0 0 16px rgba(80,170,90,.70); }    /* Graveglow — the Hardcore shop (11 Oct 2026) */
.cg-ash        { text-shadow: 0 0 5px rgba(214,196,160,.45), 0 0 16px rgba(190,170,130,.70); }   /* Ashfall — Dust-priced (2026-09-14) */
.cg-petalfall  { text-shadow: 0 0 5px rgba(255,183,213,.45), 0 0 16px rgba(255,183,213,.70); }   /* Petalfall — Spring */
.cg-solstice   { text-shadow: 0 0 5px rgba(255,233,160,.45), 0 0 16px rgba(255,233,160,.70); }   /* Solstice — Summer */
.cg-emberfall  { text-shadow: 0 0 5px rgba(255,153,68,.45), 0 0 16px rgba(255,153,68,.70); }   /* Emberfall — Autumn */
.cg-witchfire  { text-shadow: 0 0 5px rgba(136,255,68,.45), 0 0 16px rgba(136,255,68,.70); }   /* Witchfire — Hallows */
.cg-frostbound { text-shadow: 0 0 5px rgba(168,232,255,.45), 0 0 16px rgba(168,232,255,.70); }   /* Frostbound — Midwinter */
.cg-firework   { text-shadow: 0 0 5px rgba(255,217,122,.45), 0 0 16px rgba(255,217,122,.70); }   /* Firework — New Year */
/* High-contrast mode drops every glow, purchased or not — someone who turned
   that on wants crisp text more than they want their cosmetic. */
body.a11y-high-contrast [class*='cg-'],
body.a11y-high-contrast .cg { text-shadow: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   YOUR OWN PvP STANDING (player report #61, 2026-08-26)
   Jil: "only other people can tell if you're a red player." Exactly so — the
   Mark painted your tile red on everyone else's map and tagged your name in
   their Players Here list, while the person carrying it saw nothing at all.
   These rows sit directly under the crest in the stat panel and are emitted
   only while a state is live, so a player at peace gains no chrome.
   ⚠ mobile.html loads main.css too, so these serve BOTH shells.
   ═════════════════════════════════════════════════════════════════════════ */
.mk-pvpstand { display: flex; flex-direction: column; gap: 4px; margin: 7px 0 2px; }
.mk-pvprow {
    display: flex; align-items: baseline; gap: 7px;
    padding: 4px 9px; border-radius: 4px;
    font-size: 0.84em; line-height: 1.35;
}
.mk-pvpico { font-size: 1.05em; line-height: 1; }
.mk-pvptxt { flex: 1 1 auto; }
.mk-pvpcd  { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; opacity: 0.9; }

.mk-pvprow.pvp-marked  { color: #dd6655; border: 1px solid #4a1a1a; background: #180809; }
.mk-pvprow.pvp-marked strong  { color: #ff7766; }
.mk-pvprow.pvp-rampage { color: #ff8877; border: 1px solid #7a1f1f; background: #210a0b;
                         animation: pvpPulse 1.9s ease-in-out infinite; }
.mk-pvprow.pvp-rampage strong { color: #ff9988; text-transform: uppercase; letter-spacing: 0.06em; }
.mk-pvprow.pvp-ward    { color: #7fa8cc; border: 1px solid #22384a; background: #08111a; }
.mk-pvprow.pvp-ward strong    { color: #9fc8e8; }
.mk-pvprow.pvp-bounty  { color: #d8b45a; border: 1px solid #4a3a18; background: #161206; }
.mk-pvprow.pvp-bounty strong  { color: #f0cc6a; }

@keyframes pvpPulse {
    0%, 100% { box-shadow: 0 0 0 rgba(255, 85, 68, 0); }
    50%      { box-shadow: 0 0 11px rgba(255, 85, 68, 0.32); }
}
/* Honour both the OS setting and the in-game accessibility toggle. */
@media (prefers-reduced-motion: reduce) { .mk-pvprow.pvp-rampage { animation: none; } }
body.a11y-reduced-motion .mk-pvprow.pvp-rampage { animation: none; }

/* ── Scroll containment (owner report, Steam shell, 2026-08-26) ──────────────
   Every scrollable region in the free-form layout is a PANE; the document
   itself is meant to sit still. Without containment, wheeling past the end of
   a pane chains the scroll to the document, and because #navBar is sticky
   inside #wrapper{top:5px} even a few pixels of movement snapped the menu bar
   between y=5 and y=0 — a visible jiggle on every notch. layoutWatchNav() in
   gamefunctions.js removes the overflow that made the page scrollable at all;
   this stops the chaining from ever reaching the document again. */
.game-pane, #chatMain, #onlineList { overscroll-behavior: contain; }
html, body { overscroll-behavior: none; }
/* ═══════════════════════════════════════════════════════════════════════════
   HOTBAR POSITION — player choice (Accessibility ▸ Hotbar Position, 2026-08-26)
   Ptah asked for the hotbar above the fold on tablet; the owner then asked for
   it to be an OPTION rather than a fixed layout.

   ⚠⚠ THESE LIVE IN main.css, NOT mobile.css, ON PURPOSE. Both shells load
   main.css, and some tablets run the DESKTOP layout — modern iPadOS reports a
   Macintosh UA, so index.html's mobile redirect never fires for it (that is
   also why a11y-big-touch exists). A mobile.css-only option would miss exactly
   the players who asked for it.

   ⚠ Nothing here applies until the player picks something: 'auto' writes no
   class at all, so every default layout is untouched.

   ⚠ Anchored to `div:has(> .combat-arena)` — the element that DIRECTLY holds
   the arena — because combat modes fill different containers (#combatArea for
   overworld/dungeon/raid, #displayArea for rift/world-boss). An id-anchored
   rule silently misses two of the nine combat screens.
   ═════════════════════════════════════════════════════════════════════════ */
body.a11y-hb-above div:has(> .combat-arena),
body.a11y-hb-below div:has(> .combat-arena)                 { display: flex; flex-direction: column; }
body.a11y-hb-above div:has(> .combat-arena) > *,
body.a11y-hb-below div:has(> .combat-arena) > *             { order: 2; }   /* log, loot, everything else */

body.a11y-hb-above div:has(> .combat-arena) > #hbarBox      { order: 0; }   /* abilities, then the fight */
body.a11y-hb-above div:has(> .combat-arena) > .combat-arena { order: 1; }

body.a11y-hb-below div:has(> .combat-arena) > .combat-arena { order: 0; }   /* the fight, then abilities */
body.a11y-hb-below div:has(> .combat-arena) > #hbarBox      { order: 1; }

/* ── Crafting panel declutter (2026-08-28) ──────────────────────────────────
   The moon's four numbers and its flavour line used to take two full blocks
   above the fold, and a permanent boss-material sentence and rarity legend took
   two more. All of that is reference you read once — it lives in tooltips now,
   and this one line carries the part that changes. */
.craft-statusline {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    background: #0e0b18; border: 1px solid var(--brd-soft); border-radius: 4px;
    padding: 5px 10px; margin: 4px 0 6px; font-size: 0.84em; cursor: help;
}
.craft-status-sep { color: #3a3450; }

/* ── Filter bar. The single biggest usability change: a tier can hold thirty
      recipes and there was no way to ask "which of these can I make?". ── */
.craft-filterbar {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 5px 6px; margin-bottom: 6px;
    border-bottom: 1px solid var(--brd-soft);
}
.craft-search {
    flex: 1 1 160px; min-width: 120px;
    background: #0a0814; border: 1px solid var(--brd-soft); border-radius: 3px;
    color: #d8d0e8; padding: 4px 8px; font-family: inherit; font-size: 0.85em;
}
.craft-search:focus { outline: none; border-color: var(--brd-gold); }
.craft-onlybox {
    color: #9a90b0; font-size: 0.82em; white-space: nowrap;
    display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
    user-select: none;
}
.craft-onlybox input { cursor: pointer; }
.craft-count { color: #6a6280; font-size: 0.78em; margin-left: auto; white-space: nowrap; }

/* The recipe list gets more room now that four blocks above it are gone. */
.craft-list { max-height: 420px; }

/* Tab strip: one line per tab, not two. The skill number is inline and only
   rendered when non-zero, so a new player sees seven names rather than seven
   zeroes; the exact figure is on each tab's title, and the ACTIVE bench's is
   spelled out in full by the skill bar directly beneath. */
.craft-tab { line-height: 1.5; white-space: nowrap; }
.craft-tab-skill { margin-left: 3px; opacity: 0.85; }
/* Hairline between the four equipment benches and the three specialist ones —
   they are genuinely different render paths, and the eye should see that. */
.craft-tab-div {
    width: 1px; align-self: stretch; margin: 2px 4px;
    background: var(--brd-soft); display: inline-block;
}

/* ── Material stock strip (2026-08-28, restyled) ────────────────────────────
   First attempt used bare text chips separated by vertical rules. It read as a
   grey smear: nothing marked where one material ended and the next began, the
   group labels were louder than the data, and in a WRAPPING flex the rules
   landed mid-line as stray pipes. Pills carry the boundary instead. */
.craft-stock {
    background: #0f0c18; border: 1px solid var(--brd-soft); border-radius: 3px;
    margin-bottom: 8px;
}
.craft-stock-head {
    padding: 5px 9px; font-size: 0.82em; color: #9a90b0;
    cursor: pointer; user-select: none; display: flex; align-items: center; gap: 6px;
}
.craft-stock-head:hover { color: #c8b8e8; }
.craft-stock-sum { color: #6a6280; font-size: 0.92em; margin-left: auto; }
.craft-stock-body {
    display: flex; flex-wrap: wrap; align-items: center; gap: 4px 5px;
    padding: 0 9px 7px;
}
/* A quiet caption, not a heading — the DATA should be the loudest thing here. */
/* The rule is part of the LABEL, not a separate element. A standalone
   divider in a wrapping flex floats to wherever the line breaks and reads
   as a stray pipe; attached here it always opens its own group. */
.craft-stock-lbl {
    color: #6a6486; font-size: 0.68em; letter-spacing: 0.1em; text-transform: uppercase;
    font-family: var(--mk-mono, ui-monospace, monospace);
    border-left: 1px solid #2c2646; padding-left: 8px;
    margin-left: 4px; align-self: center;
}
/* Each material is one pill, so name and count read as a single unit and the
   eye can find the edges without a separator between every pair. */
.craft-chip {
    display: inline-flex; align-items: baseline; gap: 5px;
    background: #17132a; border: 1px solid #251e3c; border-radius: 10px;
    padding: 1px 9px; font-size: 0.78em; color: #a8b49e; white-space: nowrap;
}
.craft-chip b {
    color: #f0e4c0; font-weight: 600;
    font-family: var(--mk-mono, ui-monospace, monospace); font-size: 0.96em;
}
/* Zero stock recedes as a whole pill — dimmed, never hidden. "I have none of
   that" is an answer, and a list that reorders as you gather is harder to read. */
.craft-chip-none { background: #110e1c; border-color: #1b1729; color: #565069; }
.craft-chip-none b { color: #565069; font-weight: 400; }

/* ── #301 / #300 (30 Sep 2026): a buff chip carries its rounds left (red on the last), an element gem its name ── */
.cb-buff-chip { position: relative; }
.cb-buff-chip .cb-buff-rounds { position: absolute; right: -5px; bottom: -5px; font-size: .6em; font-weight: 700; line-height: 1; padding: 1px 3px; border-radius: 3px; background: #0e0a18; border: 1px solid; font-family: var(--mk-mono, ui-monospace, monospace); }
.cbt-gems .cbt-gem-el { font-size: .66em; letter-spacing: .06em; margin-left: 2px; text-transform: uppercase; font-family: var(--mk-mono, ui-monospace, monospace); }
