/* The always-on HUD. Loaded last, after gs.css, game.css, panels.css and lucis.css, and it owns
   every .hud-* rule: nothing about the persistent HUD is styled anywhere else.

   The language: no panels. There is no box, border, bevel, plate or well anywhere on the screen
   while you play. Text sits straight on the world, and every ROW stands on its own soft wash - a
   short dark gradient exactly the row's height, near-black at the left and gone by the right, with
   a hair of clear world between rows. Clean sans type, white, a soft drop shadow. A row opens with
   a tag: coloured [brackets] on a list, a small rectangular chip in chat. A section has one head
   row on a bluer wash, icon left, title, ">>" right.

   Skill slots are the one framed thing: loose translucent squares with a thin light rim, a "+" in
   an empty one, the key centred on the bottom edge, the count bottom-right. No tray behind them. */

/* The tokens are on the chat's own window as well as on the HUD: a detached chat is mounted on
   <body> like every other window, outside .hud, and it is still the same chat. */
.hud, .hud-chatwin {
  --hud-ink: rgba(6, 8, 12, .74);
  --hud-ink-2: rgba(6, 8, 12, .46);
  --hud-navy: rgba(30, 46, 84, .86);
  --hud-navy-2: rgba(30, 46, 84, .38);
  --hud-wash: linear-gradient(90deg, var(--hud-ink) 0%, var(--hud-ink-2) 55%, transparent 100%);
  --hud-wash-rev: linear-gradient(270deg, var(--hud-ink) 0%, var(--hud-ink-2) 55%, transparent 100%);
  --hud-head: linear-gradient(90deg, var(--hud-navy) 0%, var(--hud-navy-2) 60%, transparent 100%);
  --hud-head-rev: linear-gradient(270deg, var(--hud-navy) 0%, var(--hud-navy-2) 60%, transparent 100%);
  --hud-text: #ffffff;
  --hud-dim: rgba(255, 255, 255, .62);
  --hud-gold: #f2c86a;
  --hud-blue: #86bcff;
  --hud-teal: #7de6da;
  --hud-hp: #e0453a;
  --hud-mp: #3f7dff;
  --hud-xp: #c9a227;       /* experience is gold now, not green */
  --hud-good: #6fcf6a;     /* the green that means live or healthy: a good ping, a war clock running */
  --hud-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 0 4px rgba(0, 0, 0, .6);
  --hud-face: var(--gs-face-ui, 'Jost', 'Barlow Semi Condensed', sans-serif);
  /* Two faces the rail names on purpose. The cursive one is the game's own - a level and a name
     are a nameplate, not a readout - and the sans one is for the figures inside a gauge, where
     Grenze's serifs close up at 10px. */
  --hud-face-cursive: var(--gs-face-cursive, 'Grenze', serif);
  --hud-face-sans: 'Barlow Semi Condensed', system-ui, sans-serif;
  --hud-edge: 10px;
  --hud-slot: 34px;
  /* Where every gauge in the left rail starts, measured from the rail's own left edge. You,
     your pet and each teammate carry a different portrait at a different standoff, and each
     one used to work its own gap out - so three blocks that stack in one column put their
     health bars on three different left edges. One number, and each block spends whatever
     gap is left over after its own padding and portrait to reach it. */
  --hud-gauge-x: 92px;
}

.hud {
  position: fixed;
  inset: 0;
  pointer-events: none;
  user-select: none;
  z-index: 10;
  font-family: var(--hud-face);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.35;
  letter-spacing: .01em;
  color: var(--hud-text);
  text-shadow: var(--hud-shadow);
}
.hud > * { position: absolute; pointer-events: auto; zoom: var(--gs-hud-scale, 1); z-index: 1; }
.hud button { font: inherit; color: inherit; letter-spacing: inherit; text-shadow: inherit; }
/* Offline (main.ts, the freeze): the whole HUD is inert while the socket is down. Every block
   re-enables its own pointer events, so the kill has to reach them all. */
.hud.is-offline, .hud.is-offline * { pointer-events: none !important; }
/* The freeze itself: the frozen world and HUD stay on screen under a gray veil with a
   "Reconnecting" line whose dots keep moving, so the page reads as waiting rather than hung. */
.offline-veil {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: center;
  background: rgba(40, 40, 40, .55);
  cursor: var(--cur-busy, wait);
}
.offline-veil__text {
  font: 500 28px var(--gs-face-ui, 'Jost', 'Barlow Semi Condensed', sans-serif);
  letter-spacing: .04em; color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 0 6px rgba(0, 0, 0, .6);
}
.offline-veil__text::after {
  content: ""; display: inline-block; width: 1.2em; text-align: left;
  animation: offline-dots 1.2s steps(4, end) infinite;
}
@keyframes offline-dots {
  0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; }
}

/* ------------------------------------------------------------------ THE ROW */
/* The one building block. A wash the height of its own content and nothing else. */
.hud-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 8px;
  background: var(--hud-wash);
}
.hud-row--rev { background: var(--hud-wash-rev); }
.hud-row--head { background: var(--hud-head); font-size: 15px; font-weight: 500; }
.hud-row--head.hud-row--rev { background: var(--hud-head-rev); }
.hud-row__grow { flex: 1; min-width: 0; }
.hud-row__end { margin-left: auto; }
.hud-rows { display: flex; flex-direction: column; gap: 2px; }

/* ------------------------------------------------------------------ THE TAG */
/* Coloured [brackets] opening a list row. Chat lines carry no tag: their colour is the channel. */
.hud-tag { flex: none; color: var(--hud-blue); }
.hud-tag::before { content: "["; }
.hud-tag::after { content: "]"; }
.hud-tag--gold { color: var(--hud-gold); }

/* ------------------------------------------------------------- THE LEFT RAIL */
/* You and your team in one column, your deployed pet beside them, your boons under both. One
   column; a block that grows moves the ones under it by itself. Wider than it was, because the
   pet now stands to the RIGHT of your block rather than under it and the two need a gauge each. */
.hud-left {
  left: var(--hud-edge);
  top: var(--hud-edge);
  width: 470px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.hud-left > * { pointer-events: auto; }
/* The top of the rail is a ROW: your block and your team in the left column, the deployed pet
   top-aligned beside them. The pet column is fixed so your own gauges keep one width whether a
   pet is out or not - a bar that changed length when a pet was deployed read as a stat change. */
.hud-top { display: flex; flex-direction: row; align-items: flex-start; gap: 6px; }
/* Sized as if the pet's 230px column and the gap were always there, so your block is one width
   with a pet out or not (user, 2026-09-13: no pet must not stretch it across the screen). */
.hud-selfcol { flex: 0 0 calc(100% - 236px); min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hud-party { display: flex; flex-direction: column; gap: 2px; }
.hud-party:empty { display: none; }
/* The party's two menus are parchment windows seated IN the rail, so they inherit the rail's world
   halo - a black glow meant for text on grass, which blurs dark ink on parchment - and paint in
   DOM order under the chat box at the same z. The halo is cut at the window, and the whole rail
   rises over the chat for as long as a menu is open. */
.hud-party .win { text-shadow: none; }
.hud-left:has(.hud-party .win:not([hidden])) { z-index: 2; }

/* The self block, after the retail plate: a round portrait in a gold ring, and beside it the level
   and name over two thick framed gauges, green health and blue mana, with the numbers sitting at
   the left inside the bar. No wash behind it: the block sits straight on the world,
   so the class sigil ringing the portrait is not read as standing on a plate. The deployed pet
   under it is the same block a size down (.hud-self__block--sm). */
.hud-self { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.hud-self__block {
  align-items: center;
  --hud-pic: 60px;
  --hud-pad-l: 17px;
  /* Whatever is left over after this block's own padding and portrait: the gauges start on
     the rail's one column however big the face beside them is. */
  gap: calc(var(--hud-gauge-x) - var(--hud-pad-l) - var(--hud-pic));
  /* The left padding is the sigil's: the circle is wider than the portrait and the screen edge
     is right there, so the whole block stands off it. The TOP padding is the sigil's too - the
     ring overhangs the portrait by about 13px at -22% and the rail is pinned 10px off the screen, so
     with a row's padding the crown of the circle was cut off by the ceiling. The pet block
     matches, one portrait radius smaller, so the two portraits share a centre line. */
  padding: 15px 8px 8px var(--hud-pad-l);
  background: none; /* overrides .hud-row's wash - see the note above */
}
/* The gauges are one stack: health, mana and experience sit on each other with a hairline
   between, so they read as a single meter block rather than as three loose bars. The name line
   keeps its air. */
.hud-self__rows { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Wraps. The level badge and the name share one line while the rail is wide enough for both,
   and on a narrow rail - a phone, or a HUD scaled up - the name drops to a line of its own and
   gets the whole block instead of being cut to "Devk...". The ellipsis stays as the last resort,
   for a name longer than the rail itself. */
.hud-self__id { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 7px; min-height: 20px; margin-bottom: 4px; }
.hud-self__lvl { flex: none; font-family: var(--hud-face-cursive); color: var(--hud-gold); font-size: 16px; font-weight: 400; letter-spacing: .02em; }
/* 3.5em, not 5: the cursive face is wider than the condensed one this line used to wear, and on a
   200px rail a 5em basis pushed every name onto a line of its own. This is the widest basis that
   still lets an ordinary name share the line with its level. */
.hud-self__name { flex: 1 1 3.5em; font-family: var(--hud-face-cursive); font-size: 16px; font-weight: 400; letter-spacing: .03em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE OWN NAME HUGS ITS TEXT, because something follows it: the Tamer Pass emblem sits on its
   right, and a name that still filled the rail would shove the emblem against the far wall where
   it reads as a stray button rather than as part of the name. Only this row wears the modifier -
   the pet block reuses .hud-self__name with a basis of its own and has nothing after its name.
   The max-width is what keeps the pair on ONE line: .hud-self__id wraps, and wrapping is decided
   on base sizes, so an unclamped name would take its full text width and push the emblem onto a
   line by itself. Clamped, the name ellipsises and the emblem stays beside it - and a name too
   long for the rail still drops to its own line with its emblem, which is the behaviour the
   basis above was written for. */
.hud-self__name--hug { flex: 0 1 auto; max-width: calc(100% - 2.2em); }
/* A touch larger than the 16px name it follows, the same way the world plate's emblem is drawn a
   little larger than the level chip beside it: it is a picture, and a picture at glyph size is a
   smudge. Centred rather than sat on the baseline - a block box has no baseline of its own, so a
   baseline row would hang it off its bottom edge. */
.hud-self__pass { --ico-box: 1.3em; align-self: center; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .85)); cursor: var(--cur-hand, pointer); }
.hud-self__pass[hidden] { display: none; } /* .ico sets display: block, which [hidden] alone loses to */
.hud-vital { position: relative; display: block; height: 13px; }
/* Experience is a track, not a vital: half the height, no figure inside it, and it carries the
   level rather than a resource - so it sits last and stays quiet. */
.hud-vital--exp { height: 6px; }
.hud-vital__line {
  position: absolute;
  inset: 0;
  background: rgba(10, 12, 18, .85);
  border: 1px solid rgba(0, 0, 0, .9);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14);
  border-radius: 3px;
  overflow: hidden;
}
/* One bar shape in the game: squared at 3px, and the fill washed dark at the left to light at
   the right over its own colour. The wash is alpha, so a colour swapped on --hud-hp and friends
   needs no second gradient written for it. Same rule as design.css's .bar__fill. */
.hud-vital__fill { display: block; height: 100%; width: var(--gs-fill, 0%); background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(255, 255, 255, .26)), var(--hud-hp); border-radius: 2px; transition: width 200ms linear; }
.hud-vital--mp .hud-vital__fill { background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(255, 255, 255, .26)), var(--hud-mp); }
.hud-vital--exp .hud-vital__fill { background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(255, 255, 255, .26)), var(--hud-xp); }
.hud-vital__v {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 0 5px;
  font-family: var(--hud-face-sans);
  font-size: 11px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: .03em;
  color: #fff;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .9);
}

/* The portrait: a round face, and nothing round it. The ring used to BE the EXP gauge, filling
   round like a clock; experience became a bar under mana, the ring stayed on as plain gold, and
   with the class sigil hung around the same well two metal circles were reading as one fat rim.
   The sigil is the ring now. What is left here is a dark edge and a drop shadow, which is only
   what holds the face against a bright map - the well keeps its 4px of padding because the
   sigil's inset is measured off this box. */
.hud-portrait {
  position: relative;
  flex: none;
  width: 60px;
  height: 60px;
  padding: 4px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(6, 8, 12, .9) 60%, rgba(10, 12, 16, .9) 100%);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .8), 0 2px 6px rgba(0, 0, 0, .7);
  cursor: var(--cur-arrow, default);
}
/* The class sigil: a magic circle drawn AROUND the portrait, the head sitting in its centre. The
   band is bold so it reads over a bright map, but it is a narrow rim: -22% makes the ring 1.44x
   the well, and that figure also sets the block's left padding. It was -31%, which the owner read
   as still too heavy beside the face.
   The art is no longer hollow - the emblem is inked right through, with the class weapon in the
   middle and the concentric band out at 0.7-0.9 of its half-width. The well hides the middle,
   which is why this still lands: __face is opaque and paints over this ::before. So the inset is
   calibrated on where the BAND sits now, not on a hole that no longer exists. */
/* ONE INSET FOR ALL FOUR, and the files are why. This used to be per class: the mage's, the
   priest's and the ranger's shipped with about 29 per cent of dead transparent margin round the
   ink, so they needed -67.3%, -62.2% and -62.5% to land at the same size the warrior reached at
   -31%. They have since been re-exported tight - measured off the alpha the drawing is now .977
   to .984 of every one of the four files - and those cancelling numbers were left behind, which
   blew three of the rings up to 2.25x the well, over the level and the name beside it. The old
   comment called this exact case: tight files, all four back to -31%. Measure the alpha again
   before changing this number. */
.hud-portrait::before { content: ""; position: absolute; inset: var(--hud-sigil, -22%); background: center / contain no-repeat;
  /* A dark halo under the lines: with the block's wash gone the circle is drawn straight on the
     world, and the priest's holy white vanished over desert sand without it. */
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .85));
  pointer-events: none; }
.hud-portrait[data-cls="warrior"]::before { background-image: url(/data/art/ui/sigil-warrior.bdf39f22.webp); }
.hud-portrait[data-cls="priest"]::before { background-image: url(/data/art/ui/sigil-priest.99fb5271.webp); }
.hud-portrait[data-cls="mage"]::before { background-image: url(/data/art/ui/sigil-mage.e69b7ab3.webp); }
.hud-portrait[data-cls="ranger"]::before { background-image: url(/data/art/ui/sigil-ranger.04e5640f.webp); }
.hud-portrait__face { position: relative; display: block; width: 100%; height: 100%; border-radius: 50%; overflow: hidden; background: rgba(6, 8, 12, .8); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .8); }
.hud-portrait__face img, .hud-portrait__face .gs-ico { width: 100%; height: 100%; object-fit: cover; object-position: center 22%; }
/* The head crop is a square of head, neck and shoulders; a round well loses its corners, so the
   crop is blown up about the face and the shoulders fall off the bottom edge. The pet portrait
   is a bust already and only needs a touch. */
.hud-portrait__crop { position: absolute; inset: 0; transform: translateY(-11%) scale(1.38); transform-origin: 50% 36%; }
.hud-portrait--sm img { transform: scale(1.15); transform-origin: 50% 40%; }
/* The pet's portrait: the same ring a size down and thinner. */
/* The pet and the party rows are the same well a size down, so they lose the gold with it. The
   selected party row keeps a gold rim on purpose - see .hud-party__row.is-on below - because
   there it is a STATE and not a frame. */
.hud-portrait--sm { width: 44px; height: 44px; padding: 2px; cursor: var(--cur-arrow, default); box-shadow: 0 0 0 1px rgba(0, 0, 0, .8), 0 2px 6px rgba(0, 0, 0, .7); }

/* The small variant, for the pet. */
.hud-self__block--sm { --hud-pic: 44px; --hud-pad-l: 24px; padding: 4px 8px 6px var(--hud-pad-l); }
/* The pet's gauges do not sit on the rail's shared gauge column: that column is measured off the
   player's 60px portrait, and against a 44px one it left a 24px hole between face and bars. The
   pet is its own block in its own column, so its gauges start a normal gap from its portrait. */
.hud-self__block--sm { gap: 8px; }
.hud-self__block--sm .hud-self__rows { gap: 2px; }
.hud-self__block--sm .hud-self__id { min-height: 18px; margin-bottom: 3px; }
.hud-self__block--sm .hud-self__lvl, .hud-self__block--sm .hud-self__name { font-size: 15px; }
/* ONE LINE, level first, the way the player's block reads. This used to wrap: a pet's name is
   long far more often than a player's, so the small block took a 5em basis and let "Canaan Piglet"
   drop below its own level. Two blocks side by side then read differently from each other, which
   is what the owner saw. The name shrinks and ellipses instead - the column below is wider now,
   so there is room for an ordinary pet name beside its level. */
.hud-self__block--sm .hud-self__id { flex-wrap: nowrap; }
.hud-self__block--sm .hud-self__name { flex: 1 1 auto; min-width: 0; }
/* Taller than they were, and the column they sit in is wider: at 190px the pet's gauges came out
   half the length of the player's, because a gauge starts at the shared --hud-gauge-x and runs to
   the column's own edge. */
.hud-self__block--sm .hud-vital { height: 13px; }
.hud-self__block--sm .hud-vital__v { font-size: 10px; padding: 0 4px; }

/* The deployed Attack pet: the same rows a size down, standing to the right of its owner and
   top-aligned with them. Its own fixed column, so your gauges do not move when it is deployed
   or put away. */
.hud-pet { flex: 0 0 230px; min-width: 0; max-width: 230px; display: flex; flex-direction: column; gap: 2px; }
.hud-pet[hidden] { display: none; }

/* THE ACCOUNT STRIP (user, 2026-09-13): the characters of this account standing in the world, to the
   right of the pet, and the + that brings another one in. Not the party - that is the column under
   your own block. The faces are the pet's and a teammate's small well, and the one being played
   wears the gold light a targeted party row wears, because here too it is a state. */
/* OUT OF THE ROW'S ARITHMETIC. The rail is a fixed 470px and your block plus the pet's already fill
   it, so a third flex item was squeezed to nothing - five slivers of box-shadow where the faces
   should be - and letting it refuse to shrink would have taken the room out of YOUR gauges instead.
   It hangs off the rail's right edge, which is the pet's right edge: beside the pet, the rail's
   own width and every gauge in it untouched. */
.hud-top { position: relative; }
.hud-account { position: absolute; left: 100%; top: 0; margin-left: 6px; display: flex; flex-direction: row; align-items: center; gap: 6px; padding-top: 6px; }
.hud-account[hidden] { display: none; }
/* With no pet deployed the strip takes the pet's own seat, right beside your block. */
.hud:not(.has-attack-pet) .hud-account { position: static; margin-left: 0; }
.hud-account__body, .hud-account__add { flex: none; appearance: none; border: 0; background: none; padding: 0; margin: 0; font: inherit; }
/* The well is a span inside a button, where the shared rule meets it as an inline box and its 44px
   does not apply: block it, or the width is ignored and only the padding and the shadow draw. */
.hud-account__body .hud-portrait { display: block; cursor: var(--cur-hand, pointer); }
.hud-account__body.is-on .hud-portrait { cursor: var(--cur-arrow, default); box-shadow: 0 0 0 2px var(--hud-gold), 0 0 8px rgba(242, 200, 106, .6); }
.hud-account__body:not(.is-on):hover .hud-portrait { box-shadow: 0 0 0 1px var(--hud-gold), 0 2px 6px rgba(0, 0, 0, .7); }
/* The + is the same dark well with nothing in it but the sign, so it reads as the next seat. */
.hud-account__add {
  display: grid; place-items: center; flex: none;
  width: 44px; height: 44px; border-radius: 50%; cursor: var(--cur-hand, pointer);
  color: var(--hud-gold);
  background: radial-gradient(circle, rgba(6, 8, 12, .9) 60%, rgba(10, 12, 16, .9) 100%);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .8), 0 2px 6px rgba(0, 0, 0, .7);
}
.hud-account__add:hover { box-shadow: 0 0 0 1px var(--hud-gold), 0 2px 6px rgba(0, 0, 0, .7); }
.hud-account__add .ic { width: 18px; height: 18px; fill: currentColor; }
.hud-account__body:focus-visible .hud-portrait, .hud-account__add:focus-visible { outline: 2px solid var(--hud-gold); outline-offset: 2px; }

/* ------------------------------------------------------ A TEAMMATE IN THE RAIL */
/* A teammate is the rail's own status block, one size down: the same portrait, the same name
   line, the same two gauges as you and your pet wear, with no experience track and no class
   ring. party.ts builds it from ui.ts's statusBlock, so there is one object here and not three
   drawings of one.

   Only the drawing is shared. Every state, handler and menu is still party.ts's. */
.hud-party__row { cursor: var(--cur-hand, pointer); position: relative; }
/* The rail is two columns: the first teammate's frame with the party gear to its right, then every
   other frame and both menus across the full width under them (party.ts marks the lead frame). */
[data-party-rail] { grid-template-columns: minmax(0, 1fr) auto; }
[data-party-rail] > * { grid-column: 1 / -1; }
[data-party-rail] > .hud-party__row--lead { grid-column: 1; grid-row: 1; }
[data-party-rail] > [data-party-menu-open] { grid-column: 2; grid-row: 1; align-self: center; }
/* A teammate whose connection dropped: the body is still in the world on auto for a minute, and
   the frame says so over a half-black veil. */
.hud-party__off {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .5);
  color: #ff4a3d;
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  pointer-events: none;
}
/* The one thing a teammate's row says that yours does not: which of them the next cast is aimed
   at. On the world that is a light round the portrait rather than a border. */
.hud-party__row.is-on .hud-portrait { box-shadow: 0 0 0 2px var(--hud-gold), 0 0 8px rgba(242, 200, 106, .6); }
/* The leader's star, in front of the level. */
.hud-party__row [data-crown] { display: flex; color: var(--hud-gold); }
.hud-party__row [data-crown][hidden] { display: none; }
.hud-party__row [data-crown] .ic { width: 12px; height: 12px; fill: currentColor; }

/* The target frame is not drawn: the enemy's name and health are on its plate in the world. The
   element stays because the client writes into it. */
.hud-target { display: none !important; }

/* Boons: one text row each. */
.hud-boons { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.hud-boons:empty { display: none; }
.hud .gs-boon { display: flex; gap: 8px; padding: 2px 10px 2px 8px; background: var(--hud-wash); border: 0; box-shadow: none; font: inherit; font-size: 13px; color: var(--hud-text); text-shadow: var(--hud-shadow); }
.hud .gs-boon__mult { color: var(--hud-teal); }
.hud .gs-boon__left { color: var(--hud-dim); margin-left: auto; }

/* Buffs: the last row INSIDE a status block, a loose line of small cells under that body's
   gauges. No padding of its own - it starts on the gauge column like every row beside it. */
.hud-buffs { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 2px; overflow: hidden; }
.hud-buffs:empty { display: none; }

/* ---------------------------------------------------------------- STATUS MARKS
   The same mark in two places: the strip beside a portrait, and the row over a body's head in a
   fight (.cb-plate .cb-status, whose layout is game.css's). One dress, because a status means the
   same thing in both and a player should not have to learn it twice.

   WHY THESE SELECTORS CARRY `.hud`. Everything above dresses a HUD slot for the action bar: a
   painted stone frame on ::before, an inner inset 3px that clips what it holds, and
   `.hud .gs-slot__inner .gs-ico { transform: scale(1.12) }`. All of it is right for a 40px bar
   cell and wrong for a 15px mark - and being later in the load order, it won every tie. A status
   cell has to out-specify its own file. */
.hud .hud-buffs .gs-slot,
.hud .cb-plate .cb-status .gs-slot {
  width: 15px;
  height: 15px;
  padding: 0;
  box-sizing: border-box;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  overflow: visible;
  cursor: var(--cur-arrow, default);
}
/* The painted stone frame every HUD slot wears. On a 15px mark it IS the mark. */
.hud .hud-buffs .gs-slot::before,
.hud .cb-plate .cb-status .gs-slot::before { content: none; }
/* Direction on the edge, never a tint over the art: the ally green and the hostile red the health
   bars already speak. Drawn outside the box so it costs no layout, over a dark ring that keeps a
   green rim legible on grass. */
.hud .hud-buffs .gs-slot--skill,
.hud .cb-plate .cb-status .gs-slot--skill { box-shadow: 0 0 0 1px var(--moss-400), 0 0 0 2px var(--gs-border-hard); }
.hud .hud-buffs .gs-slot--white,
.hud .cb-plate .cb-status .gs-slot--white { box-shadow: 0 0 0 1px var(--ember-400), 0 0 0 2px var(--gs-border-hard); }
/* The bar slot's inner is absolutely positioned 3px in and clips: on a 15px cell that leaves a
   9px window. Put it back to the cell it is in. */
.hud .hud-buffs .gs-slot__inner,
.hud .cb-plate .cb-status .gs-slot__inner {
  position: static;
  inset: auto;
  width: 100%;
  height: 100%;
  overflow: visible;
  background: none;
  border-radius: 0;
}
/* The atlas cell is 32px and its background-position is authored in those pixels, so it is never
   resized - it is ZOOMED, and `--ico-box` is the one knob that does it. 15px of a 32px cell is the
   same 0.46875 the old hand-written transform used, with the difference that zoom shrinks the
   LAYOUT box too: the mark lands in its cell without being pinned and pulled back.

   These selectors named `.gs-ico` until now, and nothing has emitted that class since `cell()`
   took over from `sprite()` - hud-ui.ts says so where the old rule was retired. So the rule matched
   nothing, the mark kept the atlas's full 32px, and a status over a body in a fight stood twice the
   size of the same status beside a portrait. */
.hud .hud-buffs .gs-slot__inner .ico,
.hud .cb-plate .cb-status .gs-slot__inner .ico {
  --ico-box: 15px;
  margin: auto;
}
/* The seconds left, in the corner. A ground rather than a halo - bare digits lose against bright
   icon art - but sized to the DIGITS instead of to the cell, and hanging outside the box so it
   eats none of the icon. That overhang is why the row's gap is wide enough for two to sit side by
   side. */
.hud .hud-buffs .gs-slot__count,
.hud .cb-plate .cb-status .gs-slot__count {
  right: -3px;
  bottom: -3px;
  min-width: 0;
  height: auto;
  padding: 0 1px;
  border: 0;
  border-radius: 2px;
  background: rgba(6, 9, 26, .82);
  box-shadow: none;
  color: var(--gs-text);
  font-family: var(--gs-face-display);
  /* Two digits have to fit beside a 15px icon: at 9px "12" was as wide as the cell it hangs off. */
  font-size: 8px;
  /* Unitless, so the cap keeps hugging its digits whatever rung the size sits on. */
  line-height: 1.25;
}
/* A STATUS WITH NO ICON STILL HAS TO MAKE A MARK. Twelve of the 140 shown buff rows carry an empty
   `icon` (Bone Crusher, Titanborn, Prophet's Child among them) and `sprite()` answers a missing
   name with nothing at all, so the cell was a bare ground with the countdown sitting on it - the
   "tile with a number in it" that was never an icon. Filled with the rim's own colour it is still
   a mark: green says something helpful is on this body, red says something is not. */
.hud .hud-buffs .gs-slot--skill .gs-slot__inner:empty,
.hud .cb-plate .cb-status .gs-slot--skill .gs-slot__inner:empty { background: var(--moss-400); }
.hud .hud-buffs .gs-slot--white .gs-slot__inner:empty,
.hud .cb-plate .cb-status .gs-slot--white .gs-slot__inner:empty { background: var(--ember-400); }
.hud-sprite { position: relative; flex: none; }

/* ------------------------------------------------------------ THE RIGHT RAIL */
/* Minimap, status line, quest tracker: one column on the right edge. Not zoomed - the minimap is
   painted in raw screen pixels and grows by its backing store. Washes fade towards the world. */
.hud-mapcol {
  right: var(--hud-edge);
  top: var(--hud-edge);
  zoom: 1;
  width: var(--gs-map-w, 216px);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
.hud-mapcol > * { position: static; pointer-events: auto; }
/* The whole map is the button that opens the world map. Round, no frame, a soft shadow. */
.hud-map {
  appearance: none;
  align-self: flex-end;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(6, 8, 12, .55);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .55);
  line-height: 0;
  cursor: var(--cur-hand, pointer);
  overflow: hidden;
}
.hud-map canvas { display: block; border-radius: 50%; }
/* The client talking about itself: clock, ping, FPS and build, small and dim, one loose line at
   the very top of the LEFT rail, above your own block. No wash and no rim: it is four dim words
   on the world, and it starts above the portrait, on the block's own left padding, so the two
   read as one column. The rail's gap and the block's ring padding are the space under it. */
.hud-conn {
  justify-content: flex-start;
  gap: 10px;
  padding: 0 0 0 var(--hud-pad-l, 17px);
  background: none;
  font-size: 11px;
  color: var(--hud-dim);
}
.hud-conn__v { color: var(--hud-dim); }
.hud-conn [data-ping].is-good { color: var(--hud-good); }
.hud-conn [data-ping].is-slow { color: var(--hud-gold); }
.hud-conn [data-ping].is-bad { color: var(--hud-hp); }

/* The tracker is built like the chat box: one flat dark panel with the same painted chipped rim,
   no gradient, no fade, no wash per row. The head is the painted quest board icon and the word;
   pressing it folds the list. Inside a quest: the name on its own line, white, gold
   on the pinned one, then a line per objective with the step's glyph, what to do, n/m in tabular
   figures, and the walk mark where there is somebody to go and see. A step that is full swaps its
   glyph for a check and turns gold, so a finished quest reads as a column of gold checks before
   anybody reads a number. The category is not printed: it is long, it is the same for every quest
   on an island, and it is on the name as a tooltip and in the log where it belongs. */
.hud-track {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  box-sizing: border-box;
  padding: 2px 0 4px;
  background: rgba(6, 8, 12, .68);     /* the chat box's own face and rim, see .hud-chat */
  border: 2px solid transparent;
  border-image: url(/data/art/ui/box-border.2a3e0b71.webp) 15 / 2px / 0 stretch;
}
.hud-track[hidden] { display: none; }
.hud-track__head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 10px 2px 6px;
  font-size: 15px;
  font-weight: 500;
  cursor: var(--cur-hand, pointer);
}
.hud-track__head::before {
  content: "";
  flex: none;
  width: 22px;
  height: 22px;
  background: url(/data/art/ui/hud-quests.215be736.webp) center / contain no-repeat;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .7));
}
.hud-track__head-title { letter-spacing: .02em; }
.hud-track__list { display: flex; flex-direction: column; gap: 2px; }
.hud-track__list[hidden] { display: none; }
.hud-track__q {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 10px 4px 8px;
}
/* Outlevelled: the band closed over it and it can no longer be finished, so it is still listed
   and no longer shouts. */
.hud-track__q.is-band { opacity: .55; }
.hud-track__title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
  cursor: var(--cur-hand, pointer);
}
.hud-track__title:hover .hud-track__name { color: var(--hud-gold); }
.hud-track__name { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hud-track__q.is-pinned .hud-track__name { color: var(--hud-gold); }
.hud-track__obj {
  display: flex;
  gap: 6px;
  align-items: center;
  padding-left: 2px;
  font-size: 13px;
  line-height: 1.35;
  color: rgba(255, 255, 255, .9);
}
.hud-track__obj .ic { width: 12px; height: 12px; flex: none; fill: var(--hud-dim); }
.hud-track__obj.is-done { color: var(--hud-gold); }
.hud-track__obj.is-done .ic { fill: var(--hud-gold); }
.hud-track__obj span:nth-child(2) { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hud-track__n { color: var(--hud-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
.hud-track__obj.is-done .hud-track__n { color: var(--hud-gold); }
/* The walk mark: quiet until the pointer is on it, like every other glyph in this layer. */
.hud-track__walk {
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  color: var(--hud-dim);
  cursor: var(--cur-hand, pointer);
}
.hud-track__walk .ic { width: 12px; height: 12px; fill: currentColor; }
.hud-track__walk:hover { color: var(--hud-gold); }

/* ------------------------------------------------------ WHO WALKED IN, WHO WENT */
/* A friend's presence changing, under the quest tracker in the same right-hand
   column and in the same language: a wash the height of its own line, no box and
   no frame. Small on purpose - this is news, not a question, and the friend list
   is where the standing answer lives. Each line removes itself after a few
   seconds; the strip is a plain column so a second one lands under the first
   rather than moving it. */
.hud-notes { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.hud-note {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 10px 2px 8px;
  background: var(--hud-wash-rev);
  font-size: 13px;
  color: var(--hud-dim);
  white-space: nowrap;
  animation: hud-note-in 140ms ease-out;
}
.hud-note b { font-weight: 500; color: var(--hud-text); }
/* The dot carries the whole of the state, so the words stay the same weight
   either way: a name that has gone grey is not shouting. */
.hud-note__dot { width: 7px; height: 7px; flex: none; border-radius: 50%; background: rgba(255, 255, 255, .38); }
.hud-note.is-on .hud-note__dot { background: #7ddc85; box-shadow: 0 0 6px rgba(125, 220, 133, .7); }
@keyframes hud-note-in { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .hud-note { animation: none; } }

/* ------------------------------------------------------- THE TOP-RIGHT ICONS */
/* The five boards, bare painted icons on one wash beside the map. The run reward and the war
   readouts stack under them. */
.hud-primary {
  right: calc(var(--hud-edge) + var(--gs-map-w, 216px) + 8px);
  top: var(--hud-edge);
  zoom: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}
.hud-primary > * { position: static; pointer-events: auto; }
.hud-primary-menu { display: flex; flex-direction: row-reverse; gap: 4px; padding: 0; background: none; }
.hud-ibtn {
  appearance: none;
  width: 38px;
  height: 38px;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  box-shadow: none;
  clip-path: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .7));
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: var(--cur-hand, pointer);
  transition: transform 80ms ease;
}
.hud-ibtn::before, .hud-ibtn::after { content: none; }
.hud-ibtn:hover { background: none; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .7)) brightness(1.2); transform: translateY(-1px); }
.hud-ibtn .gs-icon { width: 100%; height: 100%; }
.hud-ibtn .gs-dot { position: absolute; right: 4px; top: 4px; }

/* The run's reward: label and value rows. */
.hud-reward { display: flex; flex-direction: column; gap: 2px; min-width: 190px; font-size: 13px; }
.hud-reward[hidden] { display: none; }
.hud-reward .hud-row { justify-content: space-between; }
.hud-reward .hud-row span:last-child { color: var(--hud-gold); }
.hud-reward--shut .hud-row span:last-child { color: var(--hud-dim); }
.hud-reward__k { color: var(--hud-dim); }

/* The battlefield readout and the leader's war skills, only while standing in a war. */
.hud-warfield { display: flex; flex-direction: column; gap: 2px; min-width: 190px; font-size: 13px; }
.hud-warfield[hidden] { display: none; }
.hud-warfield__row { justify-content: space-between; }
.hud-warfield__row .gs-caption { font: inherit; letter-spacing: inherit; text-transform: none; color: var(--hud-dim); }
.hud-warfield .gs-num { color: var(--hud-teal); }
.hud-warfield__cur { color: var(--hud-good); }
.hud-warfield__max { color: var(--hud-hp); }
.hud-warskills { display: flex; flex-direction: column; gap: 2px; min-width: 190px; }
.hud-warskills[hidden] { display: none; }
.hud-warskills__slot {
  appearance: none;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 3px 10px 3px 8px;
  border: 0;
  background: var(--hud-wash-rev);
  box-shadow: none;
  clip-path: none;
  text-align: left;
  cursor: var(--cur-hand, pointer);
}
.hud-warskills__slot::before, .hud-warskills__slot::after { content: none; }
.hud-warskills__slot:hover { --hud-ink: rgba(20, 28, 48, .84); }
.hud-warskills__ko { color: var(--hud-gold); white-space: nowrap; }
.hud-warskills__slot.is-poor { opacity: .55; }
.hud-warskills__slot.is-poor .hud-warskills__ko { color: var(--hud-hp); }
.hud-warskills__slot.is-live .hud-warskills__ko { color: var(--hud-good); }

/* ---------------------------------------------------------------- THE CHAT */
/* Bottom left, one flat box with a worn hairline round it. Top to bottom: the channel words, the
   log, then the composer. A said line is a small face, a light bubble carrying the speaker's name
   over their words, and the clock outside the bubble; a system line is the bubble alone in the
   same column. The bubble is the one light surface in the HUD, which is what makes a line of
   conversation read as somebody talking rather than as more chrome. */
.hud-chat {
  /* The one block of the HUD that is TALK, and talk is read as prose, not as a readout. The rest
     of the HUD keeps its condensed mono - a figure lines up under a figure there - but a
     conversation in it reads as console output. So the chat box, and only the chat box, takes the
     game's own UI face: the variable is redefined here, so the channel row, the log lines and the
     composer all follow it without each naming a font of its own. Rajdhani ships at 600 alone,
     which is why the weight comes with it. */
  --hud-face: var(--face-ui, 'Rajdhani', sans-serif);
  font-family: var(--hud-face);
  font-weight: var(--face-weight, 600);
  left: var(--hud-edge);
  bottom: var(--hud-edge);
  width: clamp(240px, calc((50vw - 8px) / var(--gs-hud-scale, 1) - 340px), 460px);
  padding: 0;
  background: rgba(6, 8, 12, .68);
  /* A painted chipped rim (arts/ui-icons/box-border.png), 9-sliced: the source rim measures 15px
     of 256 and is drawn at 2, which is the whole point - at this width the wear reads as a worn
     hairline rather than as a frame. Change the art and re-measure the slice; the two have to
     agree or the corners smear. */
  border: 2px solid transparent;
  border-image: url(/data/art/ui/box-border.2a3e0b71.webp) 15 / 2px / 0 stretch;
  display: flex;
  flex-direction: column;
  gap: 0;
}
/* Gone from the corner while it is standing in its own window. */
.hud-chat[hidden] { display: none; }
/* Take the chat out of the corner: a small glyph in the chat's top right corner, quiet until
   the pointer is on it. It travels with the chat and is hidden in the window, where the head's
   own button is the way back. */
.hud-chat-pop {
  position: absolute;
  right: 6px;
  top: 3px;
  appearance: none;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  clip-path: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--hud-dim);
  cursor: var(--cur-hand, pointer);
}
.hud-chat-pop::before, .hud-chat-pop::after { content: none; }
.hud-chat-pop[hidden] { display: none; }
/* Fold the chat to its channel strip. The same quiet glyph as the pop, beside it, and a phone's
   alone: on a desktop the chat already leaves the world its room. Hidden in the chat window,
   where folding would leave a frame round nothing. */
.hud-chat-fold { display: none; }
.hud-chat.is-folded .hud-log, .hud-chat.is-folded .hud-composer { display: none; }
@media (hover: none) and (pointer: coarse) {
  .hud-chat-fold {
    position: absolute;
    right: 30px;
    top: 0;
    appearance: none;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    clip-path: none;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--hud-dim);
    cursor: var(--cur-hand, pointer);
  }
  .hud-chat-fold::before, .hud-chat-fold::after { content: none; }
  .hud-chat-fold .ic { width: 14px; height: 14px; fill: currentColor; }
  .hud-chatwin .hud-chat-fold { display: none; }
  /* The channel words stop short of the two glyphs rather than running under the fold. */
  .hud-chat .hud-chans { padding-right: 58px; }
  /* A sideways phone is about 400 px tall: the log and the quest list give back height so the
     party rail above the chat and the menu under the tracker keep theirs. */
  /* Two classes, because the rules they shorten come later in this file. */
  .hud-chat .hud-log { height: 90px; }
  .hud-track .hud-track__list { max-height: 84px; overflow-y: auto; scrollbar-width: none; }
  /* Five tiles in a row reach left into the account's portraits at a phone's width; three to a
     row keep them over the minimap's own column. */
  .hud-primary .hud-primary-menu { display: grid; grid-template-columns: repeat(3, 38px); }

  /* The systems in two rows and no more, a size up for a thumb. `.hud .` because the desktop's own
     grid for this width comes later in the file. The put-away conversations stand on two rows. */
  .hud .hud-menu { grid-template-columns: repeat(6, 44px); gap: 3px; }
  .hud .hud-menu .hud-ibtn { width: 44px; height: 44px; }
  :root .hud { --hud-dm-row: 97px; }

  /* The quest tracker a size down and narrower, and its head says it folds: minus while the list
     is out, plus once it is put away (ui.ts keeps the fold). */
  .hud .hud-track { width: 136px; align-self: flex-end; padding: 1px 0 2px; }
  .hud-track .hud-track__head { font-size: 11px; padding: 1px 6px 1px 4px; gap: 4px; }
  .hud-track .hud-track__head::before { width: 13px; height: 13px; }
  .hud-track .hud-track__head::after { content: "\2212"; margin-left: auto; padding-left: 6px; color: var(--hud-dim); }
  .hud-track:has(.hud-track__list[hidden]) .hud-track__head::after { content: "+"; }
  .hud-track .hud-track__q { padding: 1px 6px 2px; }
  .hud-track .hud-track__title { font-size: 10px; }
  .hud-track .hud-track__obj { font-size: 9px; gap: 4px; }
  .hud-track .hud-track__obj .ic, .hud-track .hud-track__walk .ic { width: 9px; height: 9px; }

  /* Names over bodies a size down: at the phone's zoom a body is half its desktop height and a
     13px name was wider than the crowd it labels. */
  .hud-plates .gs-plate .gs-plate__name { font-size: 10px; }
  .hud-plates .gs-plate .gs-plate__guild { font-size: 8px; }
  .hud-plates .gs-plate .gs-plate__emblem { width: 14px; height: 14px; }

  /* The rail as wide as what is in it. Its desktop 470px holds a 230px pet column, and with the pet
     down to a ring that column was empty room the account's faces hung off the far side of. */
  .hud .hud-left { width: auto; }
  .hud .hud-selfcol { flex: 0 0 234px; }
  /* The pet's ring on your portrait's centre line, not on the rail's top: the ring is shorter than
     your block. Measured at this width (portrait centre 15px below the ring's, unzoomed). */
  .hud .hud-pet { margin-top: 15px; }

  /* A teammate is a face, a name and two thin gauges: the figures are the party window's to say. */
  .hud-party .hud-party__row { --hud-pic: 32px; padding-top: 1px; padding-bottom: 1px; }
  .hud-party__row .hud-portrait--sm { width: 32px; height: 32px; padding: 1px; }
  /* A THUMB'S WORTH OF PORTRAIT. The face carries the party menu (panels/party.ts: make leader,
     remove, disband) and it is drawn at 32px here, which is under the 44px every other touch
     control in this block is built to - the menu icons, the switch, the chat button. The picture
     stays 32px and only the HIT BOX grows: an invisible 44px square centred on it, which is what
     the thumb is actually aiming at. Rows sit 74px apart, so two of these cannot overlap.
     Hit-tested but never painted - a pseudo-element answers the pointer for its own element, so
     the press lands on the portrait and its handler, exactly as a press on the picture does. */
  .hud-party__row .hud-portrait--sm::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
  .hud-party__row .hud-self__id { min-height: 0; margin-bottom: 1px; }
  .hud-party__row .hud-self__lvl, .hud-party__row .hud-self__name { font-size: 11px; }
  .hud-party__row .hud-vital { height: 5px; width: 90px; }
  .hud-party__row .hud-vital__v { display: none; }
  .hud-party__row .hud-buffs { display: none; }

  /* The client's status line, moved under the minimap by ui.ts: one small dim line on the map's
     right edge. */
  .hud-mapcol .hud-conn { justify-content: flex-end; align-self: flex-end; gap: 6px; padding: 2px 2px 0 0; font-size: 9px; white-space: nowrap; }

  /* SWITCH CHARACTER. A round well on the right edge over the two menu rows (44px tiles, 3px gap),
     holding the account's faces moved in by ui.ts. Shut, the faces sit folded behind it; pressed,
     they fan out in a half circle to its left, top to bottom, the + as the last seat. ui.ts writes
     each face's --i of --n. Gone with the strip when there is nobody else to switch to. */
  .hud .hud-switch { left: auto; top: auto; right: var(--hud-edge); bottom: calc(var(--hud-edge) + 101px); width: 44px; height: 44px; z-index: 3; }
  .hud-switch:has(> .hud-account[hidden]) { display: none; }
  .hud-switch__btn, .hud-chatbtn {
    appearance: none; display: grid; place-items: center; width: 44px; height: 44px; padding: 0; border: 0;
    border-radius: 50%; cursor: var(--cur-hand, pointer); color: var(--hud-gold);
    background: radial-gradient(circle, rgba(6, 8, 12, .9) 60%, rgba(10, 12, 16, .9) 100%);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .8), 0 2px 6px rgba(0, 0, 0, .7);
    position: relative; z-index: 1;
  }
  .hud-switch__btn .ic, .hud-chatbtn .ic { width: 20px; height: 20px; fill: currentColor; }
  /* Open, the faces lie in a row to the LEFT of the button, over the world, on a dark lozenge that
     keeps them read as one group and not as more tiles on the grass.

     IT USED TO BE A HALF CIRCLE and it could not be made to fit. The fan swept from straight up,
     through left, to straight down at an 80px radius, so it needed 2x80 + 44 = 204px of its own
     space - 151px on screen at the phone HUD scale - inside the band between the foot of the quest
     tracker and the top of the menu. That band is 137px on a 360px-tall landscape phone, so the
     top seat climbed over the tracker and the bottom two sat on the menu rows.

     It is not a matter of a smaller radius or a narrower arc. Hold the fan to a band of height B
     and the best seat separation any radius and arc can give is (B - 44) / (n - 1): 28px on that
     screen with five characters and the +, for faces that are 44px across. A vertical fan in that
     gap is arithmetically impossible, so the faces leave the gap instead. There is nothing to the
     left of the button but world, and a row of six seats is 6x52 = 312px of it. */
  .hud-switch::before {
    content: "";
    /* --n seats at --fan-step apart, plus a face's width and a margin the soft edge fades across.
       ui.ts writes --n on the switch itself for this; the faces carry their own copy for --i. */
    position: absolute; right: 50%; top: 50%; height: 60px;
    width: calc(var(--n, 1) * var(--fan-step) + 30px);
    border-radius: 30px; transform: translate(14px, -50%) scaleX(.2);
    transform-origin: right center;
    background: linear-gradient(270deg, rgba(6, 8, 12, .72) 70%, rgba(6, 8, 12, 0) 100%);
    opacity: 0; pointer-events: none; transition: transform .18s ease-out, opacity .18s ease-out;
  }
  .hud-switch.is-open::before { opacity: 1; transform: translate(14px, -50%) scaleX(1); }
  .hud-switch.is-open .hud-switch__btn, .hud-chatbtn.is-on { box-shadow: 0 0 0 2px var(--hud-gold), 0 0 8px rgba(242, 200, 106, .6); }

  /* THE CHAT BUTTON: the same well in the bottom-left corner, holding the chat box over it. ui.ts
     lights it (.is-on) while the box is out and marks it (.has-new) when a line lands while it is
     put away. Folded on a phone is gone, so the fold glyph in the box has nothing left to do. */
  .hud .hud-chatbtn { position: absolute; left: var(--hud-edge); bottom: var(--hud-edge); top: auto; right: auto; z-index: 3; }
  .hud-chatbtn.has-new::after {
    content: ""; position: absolute; right: 3px; top: 3px; width: 9px; height: 9px; border-radius: 50%;
    background: var(--hud-hp); box-shadow: 0 0 0 1px rgba(0, 0, 0, .8);
  }
  .hud .hud-chat { bottom: calc(var(--hud-edge) + 52px); }
  .hud .hud-chat.is-folded { display: none; }
  .hud .hud-chat .hud-chat-fold { display: none; }

  /* THE STICK (joystick.ts): a faint ring over the space the chat box used to hold, bottom left and
     clear of the chat button under it, and a knob that follows the thumb. See-through at rest so the
     world reads through it, a step brighter while a thumb is on it. Gone while the chat is out: the
     box stands in the same place, and a thumb reaching for the composer must not walk. */
  .hud .hud-joy {
    left: calc(var(--hud-edge) + 34px); right: auto; top: auto; bottom: calc(var(--hud-edge) + 66px);
    width: 132px; height: 132px; border-radius: 50%; z-index: 2; touch-action: none;
    background: radial-gradient(circle, rgba(255, 255, 255, .05) 0 55%, rgba(255, 255, 255, .12) 70%, rgba(255, 255, 255, .02) 71%);
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .22), 0 0 0 1px rgba(0, 0, 0, .25);
    opacity: .7; transition: opacity .15s linear;
  }
  .hud .hud-joy.is-on { opacity: 1; }
  .hud-joy__knob {
    --jx: 0%; --jy: 0%;
    position: absolute; left: 50%; top: 50%; width: 40%; height: 40%; border-radius: 50%; pointer-events: none;
    transform: translate(calc(-50% + var(--jx) * 2.5), calc(-50% + var(--jy) * 2.5));
    background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .55), rgba(255, 255, 255, .22) 70%);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .35);
  }
  .hud-joy.is-on .hud-joy__knob { background: radial-gradient(circle at 40% 35%, rgba(255, 255, 255, .75), rgba(255, 255, 255, .35) 70%); }
  .hud:has(.hud-chat:not(.is-folded):not([hidden])) .hud-joy { display: none; }
  /* Two thumbs on the glass - the stick and a slot - are play, not a pinch. gs.css gives buttons
     `manipulation`, which still lets the browser zoom the page between two fingers. */
  .hud button, .hud .gs-slot { touch-action: none; }
  /* A fighter's status marks under its now narrower health bar (game.css), a size down with it. */
  .hud .cb-plate .cb-status { gap: 2px 3px; }
  .hud .cb-plate .cb-status .gs-slot { width: 11px; height: 11px; }
  .hud .cb-plate .cb-status .gs-slot__inner .ico { --ico-box: 11px; }
  .hud .hud-switch .hud-account { position: absolute; left: 50%; top: 50%; width: 0; height: 0; margin: 0; padding: 0; display: block; }
  /* One seat every --fan-step to the left, nearest the button first, the + furthest out. Shut,
     they are folded behind the button at 40%; opening slides each to its own seat. The stagger is
     per seat, so the row unrolls outward instead of appearing all at once. */
  .hud-switch { --fan-step: 52px; }
  .hud-switch .hud-account > * {
    position: absolute; left: 0; top: 0;
    transform: translate(-50%, -50%) scale(.4);
    opacity: 0; pointer-events: none;
    transition: transform .18s ease-out, opacity .18s ease-out;
    transition-delay: calc(var(--i, 0) * 20ms);
  }
  .hud-switch.is-open .hud-account > * {
    transform: translate(-50%, -50%) translateX(calc((var(--i, 0) + 1) * var(--fan-step) * -1));
    opacity: 1; pointer-events: auto;
  }

  /* The deployed pet is its face and nothing else: health and mana are two rings round it, health
     outside, and the name, level and figures are the pet window's to say. ui.ts setAttackPet
     writes --pet-hp and --pet-mp on the block. */
  .hud .hud-pet { flex: none; max-width: none; }
  .hud-pet .hud-self__rows { display: none; }
  .hud-pet .hud-self__block--sm { position: relative; padding: 10px; gap: 0; }
  .hud-pet .hud-self__block--sm::before,
  .hud-pet .hud-self__block--sm::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
  }
  .hud-pet .hud-self__block--sm::before {
    background: conic-gradient(var(--hud-hp) var(--pet-hp, 0%), rgba(10, 12, 18, .85) 0);
    -webkit-mask: radial-gradient(circle closest-side, transparent 28px, #000 28.5px);
    mask: radial-gradient(circle closest-side, transparent 28px, #000 28.5px);
  }
  .hud-pet .hud-self__block--sm::after {
    inset: 4px;
    background: conic-gradient(var(--hud-mp) var(--pet-mp, 0%), rgba(10, 12, 18, .85) 0);
    -webkit-mask: radial-gradient(circle closest-side, transparent 24px, #000 24.5px);
    mask: radial-gradient(circle closest-side, transparent 24px, #000 24.5px);
  }
}
.hud-chat-pop:hover { color: var(--hud-gold); }
.hud-chat-pop .ic { width: 12px; height: 12px; fill: currentColor; }

/* THE CHAT IN A WINDOW. The same nodes, moved: the window is only a frame round them, so the
   body carries the chat's own dark face rather than the system's parchment - white talk on cream
   paper cannot be read - and the log grows with the window instead of keeping the rail's height. */
.hud-chatwin { display: flex; flex-direction: column; min-width: 17em; }
.hud-chatwin .win__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  overflow: hidden;
  background: rgba(6, 8, 12, .82);
  --hud-face: var(--face-ui, 'Rajdhani', sans-serif);
  font-family: var(--hud-face);
  font-weight: var(--face-weight, 600);
  color: var(--hud-text);
  text-shadow: var(--hud-shadow);
}
.hud-chatwin .hud-log { flex: 1; height: auto; min-height: 0; }
/* .hud button does this for the rail; inside the window the same buttons are outside .hud. */
.hud-chatwin .win__body button { font: inherit; letter-spacing: inherit; text-shadow: inherit; }
.hud-chatwin .win__close .ic { width: .9em; height: .9em; }

/* The channel picker, on top. Bare words; the live one is underlined. */
.hud-chans {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 5px 10px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, .14);
}
/* Inside .hud-chans, and that is not decoration: `.hud button` sets `color: inherit` for the
   whole rail, so a bare `.hud-ch` rule lost its own colour to it and every tab came out white. */
.hud-chans .hud-ch {
  appearance: none;
  padding: 0 0 2px;
  border: 0;
  background: transparent;
  box-shadow: none;
  font-size: 12px;
  letter-spacing: .01em;
  color: var(--hud-dim);
  cursor: var(--cur-hand, pointer);
}
.hud-chans .hud-ch:hover { color: var(--hud-text); }
/* A channel you may not speak on is still on the strip, in its own place, so the tabs never move
   under the hand that is reaching for one. It is a dead word: dimmer, no hover, and the card it
   raises says why. */
.hud-chans .hud-ch.is-off { color: rgba(214, 206, 190, .32); cursor: var(--cur-arrow, default); }
.hud-chans .hud-ch.is-off:hover { color: rgba(214, 206, 190, .32); }
/* NO UNDERLINE. The channel you are on used to carry a blue bar under it, which put a piece of
   web chrome on the one strip that sits straight on the world; brightness alone says it, the way
   every other quiet-to-lit control in the HUD says it. */
.hud-chans .hud-ch.is-active { color: var(--hud-text); }

.hud-log {
  height: 172px;
  padding: 6px 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* The log scrolls, and it SHOWS that it scrolls. Two things were in the way. The box used to
     pin its lines with `justify-content: flex-end`, and a flex container that packs to the end
     leaves everything above its top edge OUT of scrollHeight - the backlog was really there and
     really unreachable, by wheel or by bar. The floor is held by the first child's `margin-top:
     auto` instead, which overflows downward and scrolls. Then the bar itself was hidden, so
     nothing on screen said there was anything above the top line; a thin gold thumb on a dark
     gutter says it. ui.ts already refuses to yank a reader back down while they are up in the
     scrollback, and `contain` keeps a wheel over the box off the page behind it. */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(214, 170, 78, .55) rgba(6, 8, 12, .45);
}
.hud-log[hidden] { display: none; }
/* The floor: the first child takes all the slack, so a short log sits at the bottom of the box
   and a full one runs off the top and scrolls. */
.hud-log > :first-child { margin-top: auto; }
.hud-log .gs-chat-line {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: 5px;
  padding: 0;
  background: none;
  font-family: var(--hud-face);
  font-size: 13px;
  line-height: 1.3;
  text-shadow: none;
}
/* The speaker's head, or their initial until the paperdoll frame table is in - and for good on a
   line from a body this client has never seen. */
.hud-msg__face {
  position: relative;
  flex: none;
  width: 28px;
  height: 28px;
  overflow: hidden;
  border-radius: 2px;
  background: rgba(0, 0, 0, .55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--hud-dim);
}
/* The bubble. Pale and opaque: the words are the one thing in the HUD read as a block of prose,
   and every other surface here is dark, so this is where the eye goes. */
.hud-msg__bubble {
  position: relative;
  min-width: 0;
  max-width: calc(100% - 74px);
  padding: 2px 7px 3px;
  border-radius: 2px;
  background: rgba(238, 236, 228, .93);
  color: #1b1d22;
  overflow-wrap: anywhere;
}
/* The little tab pointing back at the face. */
.hud-msg__face + .hud-msg__bubble::before {
  content: "";
  position: absolute;
  left: -4px;
  top: 8px;
  border: 4px solid transparent;
  border-right-color: rgba(238, 236, 228, .93);
  border-left: 0;
}
.hud-msg__face--system { background: rgba(0, 0, 0, .5); }
.hud-msg__face--system::after {
  content: "";
  position: absolute;
  inset: 3px;
  background: url(/data/art/ui/chat-system.6ae51116.webp) center / contain no-repeat;
  opacity: .92;
}
/* A notice has no bubble: it is printed on the dark like the rest of the HUD, which is what keeps
   it from reading as somebody in the room saying something. */
.hud-log .gs-chat-line--system .hud-msg__bubble {
  padding: 2px 0 0;
  background: none;
  color: #f0e2b4;
  text-shadow: var(--hud-shadow);
}
.hud-log .gs-chat-line--system .hud-msg__bubble::before { content: none; }
.hud-msg__who { display: block; font-weight: 500; color: #3d4756; }
.hud-msg__body { display: block; }
/* Channel colour rides the NAME, not the words: the words are on a pale bubble now, where the
   saturated channel hues used for dark ground fall apart. A system line has no name, so it takes
   the colour on its body instead - it is the one kind of line nobody said. */
.hud-log .gs-chat-line--team    .hud-msg__who { color: #1f5fae; }
.hud-log .gs-chat-line--guild   .hud-msg__who { color: #2f7a34; }
.hud-log .gs-chat-line--world   .hud-msg__who { color: #6a3ba8; }
.hud-log .gs-chat-line--private .hud-msg__who { color: #a03a7a; }
.hud-log .gs-chat-line--system  .hud-msg__body { color: #f0e2b4; }
.hud-log .gs-chat-line--alert   .hud-msg__body { color: #a52a1e; }
.hud-log .gs-chat-line--loot    .hud-msg__body { color: #2f7a34; }
.hud-log .gs-chat-line--fade { opacity: .55; }
.hud-log .gs-chat-line em { font-style: normal; font-weight: 500; }
.hud-log .gs-chat-link { text-shadow: none; }
/* A line nobody said starts in the same column as the ones that were. */
.hud-log .gs-chat-line:not(:has(.hud-msg__face)) .hud-msg__bubble { margin-left: 33px; }
/* The clock sits outside the bubble, on the dark, and takes the world shadow back. */
.hud-msg__at {
  flex: none;
  align-self: flex-end;
  font-style: normal;
  font-size: 11px;
  color: var(--hud-dim);
  text-shadow: var(--hud-shadow);
}

/* The composer: the field, how much of the cap is spent, and the send arrow. */
.hud-composer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 5px;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.hud-composer .chat-input {
  flex: 1;
  min-width: 0;
  height: 26px;
  padding: 0 2px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  font: inherit;
  font-size: 14px;
  color: var(--hud-text);
  text-shadow: var(--hud-shadow);
}
.hud-composer .chat-input::placeholder { color: var(--hud-dim); }
.hud-composer .chat-input:focus { outline: 0; }
.hud-send {
  appearance: none;
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: rgba(255, 255, 255, .12);
  box-shadow: none;
  clip-path: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--hud-text);
  cursor: var(--cur-hand, pointer);
}
.hud-send::before, .hud-send::after { content: none; }
.hud-send svg { width: 15px; height: 15px; }
.hud-send:hover { background: rgba(134, 188, 255, .35); }

/* ---------------------------------------------------------------- THE BARS */
/* Bottom centre. Loose square slots with a gap of world between them; the toggles are loose
   round icons after the last slot. */
.hud-bar { left: 50%; bottom: 18px; transform: translateX(-50%); display: flex; align-items: center; gap: 4px; }
.hud-bar__tray { display: flex; gap: 4px; padding: 0; background: none; border: 0; box-shadow: none; }
.hud-bar__tray .gs-slot, .hud-bar__tray .gs-slot:first-child { margin: 0; }
/* The slot: a dark translucent square under a painted grunge frame (arts/ui-icons/slot-frame.png,
   a chipped stone-beige rim on alpha, drawn over the art on ::before so the icon sits inside it).
   An empty slot shows the worn plus (slot-plus.png) and nothing else. Both are codex imagegen
   output from the retail reference, 2026-09-08. */
.hud .gs-slot {
  position: relative;
  width: var(--hud-slot);
  height: var(--hud-slot);
  border: 0;
  border-radius: 0;
  background: rgba(22, 16, 12, .72);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
  overflow: visible;
  cursor: var(--cur-hand, pointer);
}
.hud .gs-slot::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: url(/data/art/ui/slot-frame.04f1cd77.webp) center / 100% 100% no-repeat;
  pointer-events: none;
}
.hud .gs-slot__inner { position: absolute; inset: 3px; width: auto; height: auto; background: transparent; box-shadow: none; border-radius: 0; overflow: hidden; color: transparent; }
.hud .gs-slot__inner .gs-ico { transform: scale(1.12); }
.hud .gs-slot__inner img, .hud .gs-slot__inner .gs-slot__art { width: 100%; height: 100%; object-fit: cover; }
.hud .gs-slot--empty .gs-slot__inner .gs-icon { display: none; }
.hud .gs-slot--empty .gs-slot__inner::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url(/data/art/ui/slot-plus.fc76ed61.webp) center / 78% 78% no-repeat;
  opacity: .92;
}
.hud .gs-slot--empty { cursor: var(--cur-arrow, default); }
.hud .gs-slot--locked .gs-slot__inner { filter: grayscale(1) brightness(.6); }
/* The key, centred on the bottom edge, white. */
.hud .gs-slot__key {
  left: 50%;
  right: auto;
  top: auto;
  bottom: -8px;
  transform: translateX(-50%);
  width: 16px;
  height: 16px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The sans face, not the display serif: a key cap is a plain stamped figure, and the serif
     read as ornament on the one part of the bar a player reads at a glance. */
  font-family: var(--hud-face-sans);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  color: #d8d0be;
  text-shadow: 0 1px 1px #000;
  z-index: 3;
}
/* The key's plate: the system menu's grunge diamond itself, shipped small as slot-key.png with
   its thin cream rim dimmed well down, so the key sits quietly under the slot instead of
   competing with the art above it. */
.hud .gs-slot__key::before {
  content: "";
  position: absolute;
  inset: 0; /* the plate is the 16px box itself, no overhang: it sits under the slot, not on it */
  z-index: -1;
  background: url(/data/art/ui/slot-key.5a6cd7d8.webp) center / 100% 100% no-repeat;
  opacity: .92;
}
/* The stack count, bottom right, white. */
.hud .gs-slot__count {
  right: 3px;
  bottom: 2px;
  padding: 0;
  border: 0;
  background: none;
  line-height: 1;
  font-family: var(--hud-face);
  font-size: 12px;
  font-weight: 500;
  color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
}
/* The travelling light. A bound skill slot is not a painted square: one bright arc runs round its
   rim, forever, so the bar reads as live the way the rest of the screen does. It is a rotating
   conic gradient masked down to the rim itself - the arc is the only lit part of the ring, so what
   the eye sees is a light going round a border and nothing else. Every slot runs the same clock,
   which makes the row read as one object instead of twelve animations.

   Only a slot with a SKILL on it: an empty cell has no border to light and a slot whose skill the
   character can no longer cast is not live. The light dims while the slot is cooling, so "ready"
   and "not yet" are the same signal at two strengths rather than two unrelated marks.

   The angle is a registered custom property because an unregistered one cannot be interpolated -
   without @property the gradient would jump from 0 to 360 in one step and never move. */
@property --hud-spark-a { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@keyframes hud-slot-spark { to { --hud-spark-a: 360deg; } }
.hud .hud-slot-spark {
  position: absolute;
  inset: 0;
  z-index: 2;
  padding: 3px; /* the ring thickness: the mask below keeps the padding box and drops the middle */
  opacity: 0;
  pointer-events: none;
  /* Screen, and a bloom: the frame art is a pale stone beige, so a light merely painted over it
     reads as a lighter patch of stone. Screened on with a halo it reads as light. */
  mix-blend-mode: screen;
  filter: drop-shadow(0 0 3px rgba(255, 214, 120, .9));
  background: conic-gradient(from var(--hud-spark-a),
    transparent 0 54%, rgba(255, 200, 90, .25) 64%, rgba(255, 228, 150, .8) 72%,
    #fffaf0 78%, rgba(255, 228, 150, .8) 84%, rgba(255, 200, 90, .25) 92%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
/* ONLY THE OPENER. The spark ran on every filled, castable slot, which meant a bar with two
   skills on it wore two travelling gold rings - and gold on this bar already means one thing,
   "this is the skill a fight opens with". Two of them said there were two defaults, and there
   is exactly one. So the light is the default's own mark now: it turns with the slot the player
   chose, and a slot that is merely ready is simply ready. */
.hud .gs-slot--opener:not(.gs-slot--empty):not(.gs-slot--locked) .hud-slot-spark {
  opacity: 1;
  animation: hud-slot-spark 3.2s linear infinite;
}
.hud .gs-slot.is-cooling .hud-slot-spark { opacity: .28; }

/* The opener: gold rim. Bright when chosen, soft when merely current. */
.hud .gs-slot--opener { box-shadow: 0 0 0 1px rgba(242, 200, 106, .8), 0 0 10px rgba(242, 200, 106, .55); animation: none; filter: none; transform: none; }
.hud .gs-slot--opener .gs-slot__inner, .hud .gs-slot--opener-soft .gs-slot__inner { background: transparent; box-shadow: none; }
.hud .gs-slot--opener-soft { box-shadow: 0 0 0 1px rgba(242, 200, 106, .45); filter: none; }
.hud .gs-slot--opener::after { content: none; }
.hud .gs-slot:hover::before { filter: brightness(1.25); }
.hud .gs-slot__cooldown { border-radius: 1px; }
.hud .gs-slot__cd-digits { font-family: var(--hud-face); font-size: 14px; font-weight: 500; color: #fff; text-shadow: 0 1px 2px #000, 0 0 3px #000; }

/* The toggles: loose round icons, lit when on. */
.hud-auto {
  appearance: none;
  position: relative;
  width: 36px;
  height: 36px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(22, 16, 12, .55);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
  clip-path: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, .55);
  cursor: var(--cur-hand, pointer);
}
.hud-auto::before, .hud-auto::after { content: none; }
.hud-auto:hover { color: #fff; }
.hud-auto.is-active { color: #fff; box-shadow: 0 0 0 1px rgba(255, 255, 255, .35), 0 0 8px rgba(134, 188, 255, .45); }
.hud-mobile-skill-page, .hud-mobile-pet-switch { display: none; }
.hud-mobile-skill-page[hidden], .hud-mobile-pet-switch[hidden] { display: none; }

/* The pet strip: a second, smaller row of the same slots above the player's. */
.hud-petbar { left: 50%; bottom: 72px; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 3px; }
.hud-petbar[hidden] { display: none; }
.hud-petbar__row { display: flex; align-items: center; gap: 6px; }
.hud-petbar .gs-slot { width: 34px; height: 34px; cursor: var(--cur-grab, grab); }
.hud-petbar .gs-slot--empty { cursor: var(--cur-arrow, default); }
.hud-petbar .gs-slot__inner .gs-ico { transform: scale(1); }
.hud-petbar .hud-auto { width: 34px; height: 34px; }
/* The toggles are painted round stone medallions: the medallion IS the button, with no plate,
   lamp or glow behind it. Bright when on and dimmed when off. Pet position swaps the picture
   (Nimbus facing you in front, seen from behind when behind) and is always bright. */
.hud-auto--art { background: none; box-shadow: none; border-radius: 50%; width: 36px; height: 36px; }
.hud-auto--art.is-active { box-shadow: none; }
.hud-auto--art .hud-auto__art { width: 100%; height: 100%; display: block; object-fit: contain; transition: filter .15s; }
.hud-auto--art:not(.is-active) .hud-auto__art { filter: brightness(.45) saturate(.6); }
.hud-auto--art:not(.is-active):hover .hud-auto__art { filter: brightness(.7) saturate(.8); }
/* Pet position is the exception and has to say so twice, because the hover rule above carries
   one class more than a plain [data-petfront] rule and was winning: back is not "off". The two
   states are two PICTURES, Nimbus facing you or seen from behind, and both are a real answer -
   dimming one of them says the button is unset when it is simply set the other way. */
.hud-auto--art[data-petfront] .hud-auto__art,
.hud-auto--art[data-petfront]:hover .hud-auto__art { filter: none; }
.hud-petbar .hud-auto--art { width: 36px; height: 36px; }

/* ---------------------------------------------------------------- THE MENU */
/* Bottom right: the systems as a column of bare painted icons on one wash. */
/* One line across the bottom-right corner, no wash: every icon carries its own diamond plate. */
.hud-menu {
  right: var(--hud-edge);
  bottom: var(--hud-edge);
  display: flex;
  gap: 3px;
  padding: 0;
  background: none;
}
.hud-menu .hud-ibtn { width: 38px; height: 38px; }
/* A phone held sideways is laid out wider than 1300 (orientation.ts) but has a laptop's height
   at most, so its menu folds into the same two rows and leaves the hotbar its middle. */
@media (min-width: 761px) and (max-width: 1300px), (hover: none) and (pointer: coarse) and (min-width: 761px) {
  .hud-menu { display: grid; grid-template-columns: repeat(5, 38px); }
}

/* ---------------------------------------------------------- THE WORLD LAYER */
/* The name over a body, and everything that hangs off it. It lives HERE, with the rest of the
   always-on screen, rather than in gs.css: a nameplate is not a surface the design system dresses
   - there is no face behind it to bevel and no contrast floor to measure against, because the
   background is whatever the world happens to be painting. gs.css owns the paper windows now.

   PX, and only here: main.ts and combat.ts place a plate at raw toScreen() pixels over a body
   whose size is a fact about the camera, the layer is never HUD-scaled, and a health bar that
   grew with the HUD's font would stop lining up with the body under it.

   ONE drawing for every kind of body - a character, a pet walking behind its owner, a monster, an
   NPC at a counter - because all of them are read in the same glance across a crowded map, and
   any difference between them therefore has to MEAN something. What differs is the colour of the
   NAME, which says what this body is to you, and how many lines it gets: a player carries a guild
   line and an emblem well, a pet and a creature are a name and a level, an NPC is a name. */
/* ------------------------------------------------------- PUT-AWAY CONVERSATIONS */
/* Direct Message windows folded down to a face each, stacked along the bottom-right
   corner directly over the menu row. Placed here and not in place.css because what
   it has to clear is the menu row, whose height is this file's own number: --hud-dm-row
   is that row, so the offset follows it instead of restating it. The heads themselves
   are a design-system component (.talk-head) - this rule only says where they stand.
   Inside .hud, so it takes the same --gs-hud-scale zoom every other cluster does. */
.hud { --hud-dm-row: 44px; }
.hud-dm { right: var(--hud-edge); bottom: calc(var(--hud-edge) + var(--hud-dm-row)); }
.hud-dm:empty { display: none; }
@media (min-width: 761px) and (max-width: 1300px), (hover: none) and (pointer: coarse) and (min-width: 761px) {
  /* Two rows of tiles down there, so the heads stand twice as high. */
  .hud { --hud-dm-row: 85px; }
}

/* Never scaled: main.ts places plates in screen pixels. z-index 0 so a nameplate paints under an
   open panel instead of over it. */
.hud-plates { inset: 0; pointer-events: none; zoom: 1; z-index: 0; }
.hud-plates .gs-plate__hit { position: absolute; left: 50%; bottom: 100%; }
.gs-plate { width: 92px; text-align: center; }
/* On the world a plate is `width: auto` so a long name never wraps; 92px stays the minimum, and
   it is the width every bar under the name is fixed to. */
.hud-plates .gs-plate {
  position: absolute;
  transform: translate(-50%, -100%);
  width: auto;
  min-width: 92px;
  white-space: nowrap;
}
/* The identity block: the emblem well at the left, the two lines beside it. A flex row, so the
   lines keep one left edge whether or not a crest is hanging there. */
.gs-plate__id { display: flex; align-items: center; justify-content: center; gap: 5px; }
/* The guild's crest, at the one size the server re-encodes for a plate (24) drawn at 20. A
   background image and not an <img>: an emblem that failed to arrive leaves a gap in the row
   rather than a broken-picture glyph over somebody's head. Hidden - not merely empty - when there
   is no guild or no emblem, so the name goes back to being centred on the body.
   The glow is what a PICTURE over the world takes where a text rim has nothing to bite on. */
.gs-plate__emblem {
  flex: none;
  width: 20px;
  height: 20px;
  background: center / contain no-repeat;
  filter: drop-shadow(0 0 1px rgba(18, 10, 6, .95)) drop-shadow(0 1px 2px rgba(0, 0, 0, .8));
}
.gs-plate__emblem[hidden] { display: none; }
.gs-plate__lines { min-width: 0; }
/* The name row. A flex line rather than inline text: the level and the nickname sit on one
   baseline whatever either of them is, and the gap is a real gap instead of a margin. */
.gs-plate__name {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
  /* The one cursive in the game. A name over a body is the world's own label rather than a
     piece of chrome, and the serif is what separates the two at a glance: everything drawn in
     the sans is a control, everything drawn in this is somebody standing there. */
  font-family: var(--gs-face-cursive);
  /* Its own size, not --gs-size-caption. That token is 11px because it dresses section headers
     inside a window, read at arm's length against a flat panel; a name over a body is read
     across a field, at whatever distance the camera happens to sit, over grass and water and
     another player walking through it. At the caption size the row measured under 13px for a
     player and under 10px for the level beside it, which is smaller than anything else the game
     asks anyone to read. */
  font-size: 13px;
  /* 600, and not the 700 the plate wore under the old mono. We ship exactly one Grenze file and
     it is the 600, so 600 is the only weight that arrives drawn; anything else is a SYNTHETIC
     bold - the glyph smeared sideways - and at 11px that fills in the serifs and turns the face
     back into an anonymous blob, which is the whole reason to have a second face. The four-sided
     rim below is what carries the row over grass; the weight never was. */
  font-weight: 600;
  color: var(--gs-plate-ink, #f6efe1);
  /* The world voice: a hard rim on all four sides so the glyphs survive grass, snow and a white
     robe walking underneath, and one soft halo to seat the whole row on the scene. The rim is the
     darkest ink of the family rather than pure black, so world text still belongs to the drawing.
     Every plate takes it - a monster used to get a blur the eye had to work through. */
  text-shadow:
    1px 0 0 #120a06, -1px 0 0 #120a06,
    0 1px 0 #120a06, 0 -1px 0 #120a06,
    0 2px 4px rgba(0, 0, 0, .9);
}
/* A character is the body you look for in a crowd - your own and everyone else's - so it is the
   one that is read first. An NPC's sign is read the same way: it is what a town is navigated by.
   The bump is on the ROW and not on the name inside it, so the level goes up with the name and
   the two never drift apart again. */
.gs-plate--player .gs-plate__name,
.gs-plate--npc .gs-plate__name { font-size: 1.15em; }
/* The level, AFTER the name and in plain muted gold: a fact about the body, not part of what it
   is called. It had a dark chip with a lit rim and it led the row, which put a piece of menu
   chrome on every head in town; the gold is quiet enough that "Adam 42" still reads as one name
   and one number. One colour for every kind of body, so the level is never mistaken for the
   name's own hue saying whose side this is. */
/* NO font-size of its own: the level is set in the same face at the same size as the name it
   follows, so "Nik 42" reads as one line somebody wrote rather than as a name with a footnote.
   The muted gold is what separates the two, and colour is enough. */
.gs-plate__lvl { color: #e0c68f; font-weight: 600; }
.gs-plate__lvl[hidden] { display: none; } /* an NPC has no level, and [hidden] loses to display */
/* The Tamer Pass emblem, in the name row and only when the Pass is LIVE. There is no inactive
   badge: a world plate never says what somebody does not have, and a crowd of grey padlocks over
   every body would be exactly that. The owner's own status is on the HUD chip, which has room to
   say all four states in words. The cell comes out of the shared icon atlas through `.ico`, so
   there is no second drawing to keep in step. */
.gs-plate__pass {
  --ico-box: 1.45em;
  margin-inline: -1px 0;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .85));
}
.gs-plate__pass[hidden] { display: none; } /* same reason the level needs it: .ico sets display */
/* The guild under the name, and only when there IS one: writeGuild draws no line for a guildless
   body rather than printing "No Guild" over most of the map. Quieter than the name and tracked
   out, so it reads as a caption belonging to the name above it rather than as a second name. The
   position rides in it, in literal brackets, for every guild and not only your own. */
.gs-plate__guild {
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: .04em;
  color: #e0c68f;
  margin-top: 1px;
}
.gs-plate__guild[hidden] { display: none; }
/* The same small line carrying a refusal instead - "Too far" under a band the reticle will
   not swing at. The world's red, the one colour that already means "this is about a fight". */
.gs-plate__guild--warn { color: #ff9d86; }
/* Who this body is to the reader, in one token per kind, set on the plate and used by the name.
   Brass for a character, the living green for a pet, the world's red for anything that is a
   target - and the same red for an outlaw and for a guild-war foe, because one colour has to
   mean "you may swing at this". The outlaw and foe rules are keyed on the layer, which makes them
   one class heavier than the plain kinds above and is what lets them win. */
.gs-plate--ally, .gs-plate--player, .gs-plate--npc { --gs-plate-ink: #f6efe1; --gs-plate-rim: rgba(242, 200, 106, .85); }
.gs-plate--hostile { --gs-plate-ink: #ff9d86; --gs-plate-rim: rgba(226, 110, 84, .9); }
.hud-plates .gs-plate--outlaw,
.hud-plates .gs-plate--foe { --gs-plate-ink: #ff9d86; --gs-plate-rim: rgba(226, 110, 84, .9); }
/* The one you are aiming at. Gold on the name and a gold ring on the bar: the plate you are about
   to hit is the only one that changes colour while you are looking at it. It is said twice
   because the outlaw and foe rules above are a class heavier and would otherwise hold the red on
   the very plate the player is about to swing at. */
.gs-plate.is-targeted { --gs-plate-ink: var(--gs-gold-bright); --gs-plate-rim: var(--gs-gold-bright); }
.hud-plates .gs-plate.is-targeted { --gs-plate-ink: var(--gs-gold-bright); --gs-plate-rim: var(--gs-gold-bright); }
.gs-plate.is-targeted .gs-bar { box-shadow: inset 0 0 0 1px var(--gs-gold-bright); }
/* The bar is the game's one bar - the shape and the wash .gs-bar, design.css's .bar and this
   sheet's own .hud-vital already draw - at the plate's own height. Only the hue changes here. */
.gs-plate .gs-bar { height: 6px; }
.gs-plate--ally .gs-bar__fill { background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(255, 255, 255, .26)), var(--moss-500); box-shadow: inset 0 1px 0 0 var(--moss-400); }
.gs-plate--hostile .gs-bar__fill { background: linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(255, 255, 255, .26)), var(--ember-500); box-shadow: inset 0 1px 0 0 var(--ember-400); }

/* The siege swing, over the head of whoever is swinging. Brass, not the moss green a plate's
   health bar wears: this is a clock counting up to a blow, not a pool draining, and one colour
   for the two would read as the character taking damage from the wall it is hitting.
   Fixed 92px like the combat plate's health bar, and centred - a swing timer that got wider
   with the name above it would be a readout of name length. */
.gs-plate__siege { width: 92px; margin: 0 auto 2px; }
.gs-plate__siege .gs-bar__fill {
  background: var(--gs-gold-bright);
  box-shadow: inset 0 1px 0 0 var(--brass-300);
  transition: none; /* the fill IS the animation below; the bar transition would fight its reset */
}
/* Filled by the server's own swing length, handed in as the animation duration, so the bar and
   the blow cannot drift apart. */
@keyframes gs-siege-fill { from { width: 0%; } to { width: 100%; } }

/* --------------------------------------------------------- MOBILE PORTRAIT */
/* The same blocks, pinned by hand for a narrow screen: identity, quests and chat on the left, map,
   menu and combat on the right. ui.ts placeTouchHud re-pins them numerically on real touch devices.
   Narrow only: a phone is held sideways and laid out wide (orientation.ts), and takes the desktop
   placement above with the hotbar centred under the world. */
@media (max-width: 760px) {
  .hud {
    --hud-edge: 6px;
    --hud-slot: 38px;
    width: 100dvw;
    height: 100dvh;
    overflow: hidden;
    --hud-x: max(6px, env(safe-area-inset-left));
    --hud-r: max(6px, env(safe-area-inset-right));
    --hud-t: max(6px, env(safe-area-inset-top));
    --hud-b: max(6px, env(safe-area-inset-bottom));
  }
  .hud > *:not(.hud-plates) { zoom: 1; }
  .hud-left, .hud-map, .hud-mapcol, .hud-primary, .hud-primary-menu, .hud-reward,
  .hud-bar, .hud-petbar, .hud-menu, .hud-chat, .hud-conn, .hud-track { position: fixed; box-sizing: border-box; }
  .hud-mapcol > *, .hud-primary > * { position: fixed; }
  .hud-left { left: var(--hud-x); top: var(--hud-t); width: min(200px, 48vw); }
  .hud-self__name { font-size: 15px; }
  /* 200px of rail cannot spare the sigil's full standoff, so it gives back a little of
     its own size and the block leans closer to the screen edge. */
  .hud-self__block { padding-left: 16px; }
  .hud-self__block--sm { padding-left: 24px; }
  .hud-portrait::before { inset: calc(var(--hud-sigil, -22%) * .84); }
  .hud-top { flex-direction: column; align-items: stretch; }
  .hud-selfcol { flex: none; }
  .hud-pet { flex: none; width: 100%; max-width: none; }
  .hud-mapcol { width: auto; display: contents; }
  .hud-map { right: var(--hud-r); top: var(--hud-t); }
  .hud-map canvas { width: 104px; height: 104px; }
  .hud-conn { right: var(--hud-r); bottom: var(--hud-b); top: auto; }
  .hud-track {
    left: var(--hud-x);
    bottom: calc(var(--hud-b) + 150px);
    width: min(260px, 60vw);
    max-height: 24dvh;
    overflow: hidden;
  }
  .hud-primary { display: contents; }
  .hud-primary-menu {
    right: calc(var(--hud-r) + 110px);
    top: var(--hud-t);
    display: grid;
    grid-template-columns: repeat(2, 40px);
    padding: 3px;
    background: none;
  }
  .hud-reward { right: calc(var(--hud-r) + 78px); top: calc(var(--hud-t) + 122px); max-width: 160px; }
  .hud-bar {
    left: auto;
    right: var(--hud-r);
    bottom: var(--hud-b);
    transform: none;
    flex-direction: column-reverse;
    align-items: flex-end;
    gap: 4px;
  }
  .hud-bar__tray { display: grid; grid-template-columns: repeat(2, var(--hud-slot)); gap: 4px; }
  .hud-mobile-skill-page, .hud-mobile-pet-switch {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(22, 16, 12, .55);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
    clip-path: none;
    line-height: 1;
    font-size: 10px;
  }
  .hud-mobile-skill-page::before, .hud-mobile-skill-page::after, .hud-mobile-pet-switch::before, .hud-mobile-pet-switch::after { content: none; }
  .hud-mobile-skill-page .gs-caption { font: inherit; font-size: 8px; }
  .hud-mobile-pet-switch .gs-icon { width: 22px; height: 22px; }
  .hud-bar > .hud-bar__tray .gs-slot { display: none; }
  .hud[data-mobile-skill-page="0"] .hud-bar > .hud-bar__tray .gs-slot:nth-child(-n + 4),
  .hud[data-mobile-skill-page="1"] .hud-bar > .hud-bar__tray .gs-slot:nth-child(n + 5):nth-child(-n + 8),
  .hud[data-mobile-skill-page="2"] .hud-bar > .hud-bar__tray .gs-slot:nth-child(n + 9) { display: block; }
  .hud-menu {
    right: var(--hud-r);
    top: calc(var(--hud-t) + 114px);
    bottom: auto;
    display: grid;
    grid-template-columns: repeat(2, 36px);
    padding: 4px;
    max-height: calc(100dvh - var(--hud-t) - var(--hud-b) - 150px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  .hud-menu::-webkit-scrollbar { display: none; }
  .hud-menu .hud-ibtn { width: 36px; height: 36px; }
  .hud-petbar {
    left: auto;
    right: var(--hud-r);
    bottom: calc(var(--hud-b) + 44px);
    transform: none;
    align-items: flex-end;
    visibility: hidden;
    pointer-events: none;
  }
  .hud-petbar__row { flex-direction: column; align-items: flex-end; gap: 4px; }
  .hud-petbar .hud-bar__tray { grid-template-columns: repeat(2, 32px); }
  .hud-petbar .gs-slot { width: 30px; height: 30px; }
  .hud.is-mobile-petbar .hud-petbar:not([hidden]) { visibility: visible; pointer-events: auto; }
  .hud.is-mobile-petbar .hud-bar > .hud-bar__tray,
  .hud.is-mobile-petbar .hud-bar > [data-auto],
  .hud.is-mobile-petbar .hud-bar > [data-fly],
  .hud.is-mobile-petbar .hud-mobile-skill-page { display: none; }
  .hud-chat { left: var(--hud-x); bottom: var(--hud-b); width: min(270px, 62vw); }
  .hud-log { height: 96px; }
  .hud-log .gs-chat-line { font-size: 12px; }
  .hud-msg__face { width: 22px; height: 22px; }
  .hud-log .gs-chat-line:not(:has(.hud-msg__face)) .hud-msg__bubble { margin-left: 27px; }
  .hud-chans { gap: 8px; overflow-x: auto; scrollbar-width: none; }
  .hud-chans::-webkit-scrollbar { display: none; }
  .hud-composer .chat-input { height: 30px; }
}
@media (max-width: 760px) and (max-height: 520px) {
  .hud-log { height: 44px; }
  .hud-track__list { display: none; }
  .hud-pet { display: none; }
}

/* ------------------------------------------------------------------- FOCUS */
/* No ring, anywhere. Nothing in the HUD or in a window is reached with the keyboard - Tab is
   swallowed while the game is up (src/client/ui.ts) and every control here is clicked - so a ring
   left behind by a click is a lamp saying "still armed" on a button that already fired. gs.css
   draws one on :focus-visible for the whole .gs family; this is the always-on layer answering
   for itself, and it loads after. The composer keeps the caret, which is the only focus a player
   ever wants to see on this screen. */
.hud :focus, .hud :focus-visible, .win :focus, .win :focus-visible { outline: none; }
