/* height:100dvh for the same reason as the select screen: a fixed inset:0
   element sizes to iOS's large viewport, which put the hotbar and the quick-eat
   button behind Safari's toolbar on a phone. */
#hud { position: fixed; inset: 0; height: 100dvh; pointer-events: none;
       color: #e8e2f7; font-family: Georgia, serif; }
#hud.hidden { display: none; }
#hud > * { pointer-events: auto; }

/* character panel: portrait + name + hp bar + levels */
@property --xp { syntax: '<number>'; inherits: true; initial-value: 0; }
#char-panel { position: absolute; top: 16px; left: 16px; }
/* xp progress wraps the portrait as a golden ring */
#portrait-wrap::before { content: ''; position: absolute; inset: -6px; border-radius: 50%;
  background: conic-gradient(from 0turn,
    #8a6410 0,
    #d4a94f calc(var(--xp, 0) * 0.45turn),
    #ffd166 calc(var(--xp, 0) * 0.8turn),
    #ffe9a0 calc(var(--xp, 0) * 1turn),
    rgba(58, 44, 18, .55) 0);
  -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%);
  mask: radial-gradient(circle, transparent 61%, #000 62%);
  transition: --xp .5s ease; pointer-events: none;
  animation: ring-breathe 2.8s ease-in-out infinite; }
@keyframes ring-breathe {
  0%, 100% { box-shadow: 0 0 8px rgba(255, 209, 102, .3); filter: brightness(1); }
  50% { box-shadow: 0 0 16px rgba(255, 209, 102, .6); filter: brightness(1.15); }
}
/* a soft glint sweeping around the ring */
#portrait-wrap::after { content: ''; position: absolute; inset: -6px; border-radius: 50%;
  background: conic-gradient(transparent 0 86%, rgba(255, 250, 225, .9) 93%, transparent 100%);
  -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%);
  mask: radial-gradient(circle, transparent 61%, #000 62%);
  animation: ring-glint 3.4s linear infinite; opacity: .45; pointer-events: none; }
@keyframes ring-glint { to { transform: rotate(1turn); } }
/* charging spark riding the arc's tip */
#xp-spark { position: absolute; inset: -6px; pointer-events: none; display: none;
  transform: rotate(calc(var(--xp, 0) * 1turn)); transition: transform .5s ease; }
#portrait-wrap.charged #xp-spark { display: block; }
#xp-spark::before { content: ''; position: absolute; top: -3px; left: 50%; margin-left: -4px;
  width: 8px; height: 8px; border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, #ffd166 55%, transparent 78%);
  box-shadow: 0 0 8px #ffd166, 0 0 18px rgba(255, 184, 77, .8);
  animation: spark-twinkle .85s ease-in-out infinite; }
@keyframes spark-twinkle {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.55); opacity: .75; }
}
#portrait-wrap { position: relative; flex-shrink: 0; }
/* LEVEL BADGE: a small dark disc with a gold rim, sitting on the portrait's
   lower right. Same gold vocabulary as the ring and the panel's level dial,
   but small and quiet enough not to compete with the XP arc it sits on. */
#lvl-badge { position: absolute; right: -4px; bottom: -4px; z-index: 3;
  min-width: 24px; height: 24px; padding: 0 4px; box-sizing: border-box;
  border-radius: 999px; border: 2px solid #cfa54e;
  background: radial-gradient(circle at 50% 35%, #241a3f, #120b22 70%);
  color: #ffd98a; font-size: 12px; font-weight: 700; line-height: 20px;
  text-align: center; letter-spacing: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .8);
  box-shadow: 0 0 8px rgba(212, 169, 79, .45), inset 0 0 6px rgba(0, 0, 0, .6);
  pointer-events: none; }
/* it flashes gold for a beat when the number changes */
#lvl-badge.bump { animation: lvl-bump .7s ease-out; }
@keyframes lvl-bump {
  0% { transform: scale(1); box-shadow: 0 0 8px rgba(212, 169, 79, .45); }
  35% { transform: scale(1.35); box-shadow: 0 0 18px rgba(255, 216, 138, .95); }
  100% { transform: scale(1); box-shadow: 0 0 8px rgba(212, 169, 79, .45); }
}
#portrait { width: 66px; height: 66px; border-radius: 50%; border: 2px solid #6a5acd;
  box-shadow: 0 0 10px rgba(106, 90, 205, .4); display: block; object-fit: cover; }

/* portrait panel bars: HP loud, XP quiet */
#hp-bar2 { position: relative; margin-top: 6px; height: 12px; border-radius: 6px;
  background: #170d14; border: 1px solid #5c2a36; overflow: hidden; }
#hp-fill2 { height: 100%; width: 100%; border-radius: 5px;
  background: linear-gradient(90deg, #ff5d73, #d92645 60%, #a3122e);
  transition: width .25s ease; }
#hp-num { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 9px; font-weight: 700; color: #ffe3e8;
  text-shadow: 0 1px 2px #000; letter-spacing: .5px; }

/* stats popup (portrait tap) */
#char-panel { cursor: pointer; }
#stats-panel { position: absolute; top: 128px; left: 14px; width: 292px; z-index: 5;
  max-height: calc(100dvh - 150px); overflow-y: auto;
  background: linear-gradient(160deg, rgba(18, 10, 40, .94), rgba(8, 5, 22, .94));
  border: 1px solid #4b3d7a; border-radius: 14px; padding: 12px 14px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, .5);
  transform-origin: top left; transition: transform .18s ease, opacity .18s ease;
  font-size: 12.5px; }
#stats-panel.closed { transform: scale(.7); opacity: 0; pointer-events: none; }
/* No backdrop-filter anywhere. These panels are already ~94% opaque, so the
   blur had nothing to reveal - all it did was smear a soft halo of the scene
   just outside each rounded corner, which read as a dirty translucent border
   around every popup. */
#stats-header { display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 9px; }
#stats-name { font-size: 14px; color: #fff; }
#stats-rarity { font-size: 11px; color: #ffd166; margin-left: 8px; line-height: 16px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 3px 8px; }
#stats-rarity .ident { white-space: nowrap; }
/* The duel rating, in the identity line beside class and rank. Given its own
   colour and a chip so it reads as a STANDING, not another trait: it is the
   one number on this panel that another player can take off you.
   Inline-flex and centred on the text line: as an inline span its border
   box hung below the baseline and the chip sat half a line under the words
   beside it (Alan: "the unrated badge is not fitting well"). */
.duel-rank { color: #d8a7ff; font-weight: 700; white-space: nowrap;
  display: inline-flex; align-items: center; height: 16px; line-height: 16px;
  padding: 0 7px; border-radius: 999px; font-size: 10.5px;
  background: rgba(120, 70, 180, .22); border: 1px solid rgba(190, 140, 255, .35); }
#stats-panel[data-cls] #stats-rarity .duel-rank { color: #d8a7ff; }
#stats-panel[data-cls="warrior"] #stats-rarity { color: #ff9d5c; }
#stats-panel[data-cls="mystic"] #stats-rarity { color: #7f9dff; }
#stats-panel[data-cls="ranger"] #stats-rarity { color: #5cbf7a; }
.skill-row { margin-bottom: 8px; }
.skill-row .skill-line { display: flex; justify-content: space-between;
  color: #cabffa; margin-bottom: 3px; }
.skill-row .skill-xp { color: #8f83bd; font-size: 10.5px; }
.skill-bar { height: 6px; border-radius: 4px; background: #170d24;
  border: 1px solid #2c2250; overflow: hidden; }
.skill-fill { height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, #8a6410, #d4a94f 45%, #ffd166 75%, #ffe9a0);
  box-shadow: 0 0 6px rgba(255, 209, 102, .45), inset 0 1px 1px rgba(255, 255, 255, .25); }
/* character sheet: sprite + equipment side by side (WoW-style) */
#stats-close { margin-left: auto; background: none; border: none; color: #8f83bd;
  font-size: 14px; cursor: pointer; padding: 0 0 0 8px; }
#stats-body { display: flex; align-items: center; margin-bottom: 9px;
  justify-content: space-evenly; gap: 0; }
#stats-sprite { height: 118px; width: auto; flex-shrink: 0;
  animation: breathe-sheet 2.6s ease-in-out infinite; transform-origin: bottom center; }
@keyframes breathe-sheet {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(1.03) scaleX(0.99); }
}
#stats-eq { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 1; }
.stats-eq-slot { display: flex; gap: 8px; align-items: center;
  background: #17102e; border: 1px dashed #3d3166; border-radius: 9px;
  padding: 6px 8px; color: #6f639c; }
.stats-eq-slot.filled { border-style: solid; border-color: #6a5acd; color: #e8e2f7;
  cursor: pointer; background: linear-gradient(160deg, #241a4a, #17102e);
  box-shadow: 0 0 8px rgba(106, 90, 205, .35); }
.stats-eq-slot.filled:hover { background: #221740; }
.seq-ico { font-size: 16px; }
.seq-txt { min-width: 0; }
.seq-txt b { display: block; font-size: 10px; letter-spacing: 1px; color: #8f83bd;
  text-transform: uppercase; }
.seq-txt span { font-size: 11.5px; display: block; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
#stats-grid { border-top: 1px solid #2c2250; margin-top: 9px; padding-top: 7px; }
.stat-row { display: flex; justify-content: space-between; padding: 2.5px 0;
  color: #a99ed4; }
.stat-row b { font-weight: 700; }
#stats-traits { border-top: 1px solid #2c2250; margin-top: 8px; padding-top: 8px;
  display: flex; flex-wrap: wrap; gap: 4px; }
.trait-chip { background: #221741; border: 1px solid #3a2d6b; border-radius: 8px;
  padding: 2px 8px; font-size: 10.5px; color: #b7aede; }


/* floating xp drop chips */
#xp-drops { position: absolute; top: 142px; right: 16px; display: flex;
  flex-direction: column; align-items: flex-end; gap: 3px; pointer-events: none; }
.xp-drop { background: rgba(34, 23, 65, .92); border: 1px solid #6a5acd;
  border-radius: 8px; padding: 2px 10px; font-size: 12px; color: #cabffa;
  animation: xpdrop 1.6s ease forwards; }
@keyframes xpdrop { 0% { opacity: 0; transform: translateY(6px); }
  15% { opacity: 1; transform: none; } 70% { opacity: 1; }
  100% { opacity: 0; transform: translateY(-14px); } }

/* docked minimap: always visible, tap to expand */
#mini-dock { position: absolute; top: 14px; right: 14px; width: 118px; height: 118px;
  border-radius: 12px; overflow: hidden; border: 1px solid #4b3d7a; cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .5); }
#mini-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* blocked tiles, drawn 1px-per-tile and scaled up: crisp edges, no blur */
#mini-walls { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; image-rendering: pixelated; pointer-events: none; }
#mini-dots { position: absolute; inset: 0; }
#inv-gold { float: right; color: #ffd166; font-size: 12px; letter-spacing: 0; text-transform: none; }

/* bag button: right-edge utility column with minimap + mute */
#bag-btn { position: absolute; top: 138px; right: 66px; width: 36px; height: 36px;
  border-radius: 50%; font-size: 16px; cursor: pointer;
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.1), transparent 45%), rgba(16, 10, 34, .92);
  color: #fff; border: 1.5px solid #cfa54e;
  box-shadow: 0 0 8px rgba(212, 169, 79, .3), 0 2px 8px rgba(0, 0, 0, .5);
  transition: transform .12s ease; z-index: 6; }
#bag-btn:hover, #settings-btn:hover { border-color: #8a6cff; box-shadow: 0 0 10px rgba(138, 108, 255, .4); }
#bag-btn:hover { transform: scale(1.08); }
#bag-btn:active { transform: scale(.94); }
#bag-count { position: absolute; top: -6px; right: -6px; min-width: 16px; height: 16px;
  border-radius: 8px; background: #d92645; color: #fff; font-size: 9.5px;
  line-height: 16px; padding: 0 3px; font-family: Georgia, serif;
  border: 1px solid #ff8a9a; }
#inv-panel { position: absolute; top: 174px; right: 18px;
  background: linear-gradient(160deg, rgba(18, 10, 40, .92), rgba(8, 5, 22, .92));
  border: 1px solid #4b3d7a; border-radius: 14px; padding: 10px 12px 12px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, .5);
  transform-origin: bottom right; transition: transform .18s ease, opacity .18s ease; }
#inv-panel.closed { transform: scale(.6); opacity: 0; pointer-events: none; }
#inv-panel { transform-origin: top right; }
#inv-title { font-size: 12px; color: #a99ed4; letter-spacing: 1.5px;
  text-transform: uppercase; margin-bottom: 8px; }
#inventory { display: grid; grid-template-columns: repeat(5, 46px); gap: 6px; }
.inv-slot { width: 46px; height: 46px; border-radius: 9px; background: #17102e;
  border: 1px solid #2c2250; display: flex; flex-direction: column; align-items: center;
  justify-content: center; font-size: 20px; cursor: default; color: #cabffa; }
.inv-slot .slot-label { font-size: 8px; color: #8f83bd; margin-top: 1px; }
.inv-slot.food { cursor: pointer; border-color: #6a5acd; }
.inv-slot.food:hover { background: #251a4a; box-shadow: 0 0 8px rgba(106, 90, 205, .4); }

#log { position: absolute; bottom: 82px; left: 50%; transform: translateX(-50%);
  width: min(460px, 60vw); overflow: hidden; display: flex;
  flex-direction: column-reverse; font-size: 13px; color: #cfc6ee;
  text-shadow: 0 1px 2px #000, 0 0 4px rgba(0, 0, 0, .85); pointer-events: none;
  /* The old scrim was a big soft radial ellipse, which over lit ground read
     as a dirty smear behind the text rather than as anything deliberate.
     A mask does the same job from the other side: the oldest line FADES OUT
     as it leaves instead of being sliced through, so there is nothing to
     hide and no scrim needed. */
  -webkit-mask-image: linear-gradient(to top, #000 58%, rgba(0, 0, 0, 0));
  mask-image: linear-gradient(to top, #000 58%, rgba(0, 0, 0, 0));
  padding: 0 14px;
  /* max-height MUST be an exact multiple of line-height or the top line is
     cut through the middle of the glyphs - 96 / 18 was 5.33 lines */
  line-height: 17px; max-height: 102px; }   /* whole lines only — 6 of them */
#log div { padding: 0; }
#toasts { position: absolute; bottom: 150px; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column-reverse; gap: 6px; align-items: center;
  pointer-events: none; }
.toast { background: rgba(20, 12, 44, .9); border: 1px solid #6a5acd; border-radius: 10px;
  padding: 6px 16px; font-size: 14px; animation: toast 2.6s forwards;
  box-shadow: 0 2px 12px rgba(0, 0, 0, .4); }
@keyframes toast { 0% { opacity: 0; transform: translateY(-8px); }
  12% { opacity: 1; transform: none; } 80% { opacity: 1; } 100% { opacity: 0; } }

/* quest tracker */
/* the tracker fills the gap between the portrait and the minimap, so it can
   never grow under the minimap or off the right edge of a phone */
#quest-tracker { position: absolute; top: 20px; left: 102px; right: 146px; min-width: 170px;
  background: rgba(10, 6, 26, .78); border: 1px solid #4b3d7a; border-radius: 12px;
  padding: 8px 13px; }
#qt-title { font-size: 9px; letter-spacing: 2.5px; color: #6f639c; }
#qt-name { font-size: 13px; color: #ffd166; margin: 3px 0 2px; }
#qt-progress { font-size: 12px; color: #b7aede; }

/* npc dialogue */
#dialogue[hidden] { display: none !important; }
#dialogue { position: absolute; bottom: 96px; left: 50%; transform: translateX(-50%);
  width: min(430px, 88vw); background: linear-gradient(160deg, rgba(18, 10, 40, .95), rgba(8, 5, 22, .95));
  border: 1px solid #6a5acd; border-radius: 14px; padding: 14px 16px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .6); }
#dlg-name { font-size: 14px; color: #ffd166; margin-bottom: 7px; letter-spacing: .5px; }
#dlg-lines { font-size: 13.5px; color: #d9d2f0; line-height: 1.55; font-style: italic; }
#dlg-lines p { margin: 0 0 7px; }
#dlg-close { margin-top: 6px; background: #2c2250; color: #e8e2f7; border: 1px solid #6a5acd;
  border-radius: 9px; padding: 6px 18px; font-size: 13px; cursor: pointer; font-family: inherit; }
#dlg-close:hover { background: #3a2d6b; }

/* minimap */

#map-panel { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(340px, 88vw); background: linear-gradient(160deg, rgba(18, 10, 40, .96), rgba(8, 5, 22, .96));
  border: 1px solid #6a5acd; border-radius: 16px; padding: 12px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .65); transition: transform .18s ease, opacity .18s ease; }
#map-panel.closed { transform: translate(-50%, -50%) scale(.75); opacity: 0; pointer-events: none; }
#map-title { font-size: 13px; letter-spacing: 2.5px; color: #fff; margin: 2px 2px 9px;
  text-shadow: 0 0 10px rgba(138, 108, 255, .6); }
#map-title span { color: #8f83bd; letter-spacing: 1px; font-size: 11px; }
#map-view { position: relative; border-radius: 10px; overflow: hidden;
  border: 1px solid #3a2d6b; }
#map-img { display: block; width: 100%; height: auto; }
#map-walls { position: absolute; inset: 0; width: 100%; height: 100%;
  image-rendering: pixelated; pointer-events: none; }
#map-dots { position: absolute; inset: 0; }
.map-dot { position: absolute; border-radius: 50%; transform: translate(-50%, -50%); }
.map-dot.you { width: 11px; height: 11px; background: #fff;
  box-shadow: 0 0 8px #fff, 0 0 16px #8a6cff; animation: youpulse 1.4s ease-in-out infinite; }
.map-dot.npc { width: 8px; height: 8px; background: #ffd166; box-shadow: 0 0 6px #ffd166; }
.map-dot.mob { width: 7px; height: 7px; background: #ff5d73; box-shadow: 0 0 5px #ff5d73; }
@keyframes youpulse { 0%, 100% { transform: translate(-50%, -50%) scale(1); }
  50% { transform: translate(-50%, -50%) scale(1.35); } }
/* Seven entries do not fit one line in a 302px panel - it used to run off the
   edge and break "hand in" across two rows mid-phrase. */
#map-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; justify-content: center;
  margin-top: 9px; font-size: 11px; color: #a99ed4; align-items: center; }
#map-legend span { display: inline-flex; align-items: center; white-space: nowrap; }
#map-legend .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 4px; }
#map-legend .you { background: #fff; }
#map-legend .npc { background: #ffd166; }
#map-legend .mob { background: #ff5d73; }

/* phones: compact panels, no collisions */
@media (max-width: 600px) {
  #char-panel { gap: 8px; padding: 8px 12px 8px 8px; border-radius: 12px; }
  #portrait { width: 48px; height: 48px; }
        #hp-bar { height: 12px; margin-top: 4px; }
  #hp-text { font-size: 9px; line-height: 12px; }
  #xp-bar { height: 8px; }
  #xp-label { font-size: 7.5px; line-height: 8px; }
  #quest-tracker { top: 18px; left: 96px; right: 116px; min-width: 0; padding: 6px 10px; }
  #qt-name { font-size: 11.5px; }
  #qt-progress { font-size: 10.5px; }
  /* The character panel ends ABOVE the WALK ring and scrolls inside. It ran
     461px tall from 104px and sat on the stick (Alan: "the character button
     is overlapping with the walk button"). The ring's zone is 100px + 172px
     from the bottom; 8px of air above it. */
  #stats-panel { top: 104px; width: min(250px, 78vw);
                 max-height: calc(100dvh - 104px - 280px); overflow-y: auto;
                 -webkit-overflow-scrolling: touch; }
  .tmap-node { width: 74px; }
  .tmap-node .tmn-name { font-size: 8.5px; }
  #gear-grid { grid-template-columns: 1fr; }
  #gold-panel { padding: 5px 10px; font-size: 13px; }
  #xp-drops { top: 120px; }
  #mini-dock { width: 92px; height: 92px; }
  .xp-drop { font-size: 10.5px; padding: 1px 8px; }
  #log { width: min(340px, calc(100vw - 130px)); font-size: 11.5px;
    line-height: 16px; max-height: 96px; }   /* 6 whole lines */
  .toast { font-size: 12px; padding: 4px 12px; }
  #toasts { bottom: 130px; }
  #dialogue { bottom: 84px; }
}

/* low-hp warning vignette */
#low-vignette { position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 55%, rgba(180, 20, 40, .38) 100%);
  animation: lowpulse 1.1s ease-in-out infinite; }
@keyframes lowpulse { 0%, 100% { opacity: .35; } 50% { opacity: .9; } }

/* death flash */
#death-flash[hidden] { display: none !important; } /* author display:flex would beat the hidden attr */
#death-flash { position: absolute; inset: 0; pointer-events: auto; display: flex;
  flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  background: radial-gradient(ellipse at center, rgba(70, 0, 14, .55) 0%, rgba(12, 0, 4, .94) 100%);
  animation: deathin 2.2s ease forwards; }
#death-flash span { font-size: clamp(34px, 9vw, 58px); letter-spacing: 12px; color: #ffb3c0;
  font-weight: bold; text-shadow: 0 0 30px rgba(255, 40, 70, .7), 0 2px 5px #000;
  animation: death-title 2.4s ease both; }
@keyframes death-title { 0% { opacity: 0; transform: scale(1.12); letter-spacing: 22px; }
  100% { opacity: 1; transform: scale(1); letter-spacing: 12px; } }
#death-flash small { font-size: 14px; color: #d8a9b6; font-style: italic;
  text-shadow: 0 1px 3px #000; }
@keyframes deathin { 0% { opacity: 0; } 100% { opacity: 1; } }
#respawn-btn { margin-top: 16px; background: rgba(20, 10, 16, .6); color: #ffd166;
  border: 1.5px solid #cfa54e; border-radius: 12px; padding: 12px 34px; font-size: 16px;
  font-family: Georgia, serif; letter-spacing: 1px; cursor: pointer;
  box-shadow: 0 0 16px rgba(212, 169, 79, .35); animation: deathin 2.8s ease both; }
#respawn-btn:hover { background: rgba(60, 34, 20, .75); box-shadow: 0 0 24px rgba(212, 169, 79, .6); }

.map-dot.small.you { width: 7px; height: 7px; }
.map-dot.small.npc { width: 5px; height: 5px; }
.map-dot.small.mob { width: 5px; height: 5px; }

/* --- equipment row (in the pack panel) --- */
#equip-row { display: flex; gap: 8px; padding: 6px 0 8px; }
.equip-slot { flex: 1; display: flex; align-items: center; gap: 7px;
  background: #17102e; border: 1px dashed #3d3166; border-radius: 9px;
  padding: 6px 9px; color: #6f639c; font-size: 12px; cursor: default; }
.equip-slot.filled { border-style: solid; border-color: #6a5acd; color: #e8e2f7;
  cursor: pointer; }
.equip-slot.filled:hover { background: #221740; }
.eq-icon { font-size: 15px; }
.eq-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-slot.gear { border-color: #7a5acd; }

/* --- shop panel --- */
#shop-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(360px, calc(100vw - 24px)); max-height: min(520px, 80vh); overflow-y: auto;
  background: rgba(12, 7, 28, .96); border: 1px solid #6a5acd; border-radius: 14px;
  padding: 12px 14px; z-index: 40; color: #e8e2f7; font-family: Georgia, serif; }
#shop-panel.closed { display: none; }
#shop-title { font-size: 14px; letter-spacing: 2px; color: #ffd166;
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
#shop-gold { margin-left: auto; color: #ffd166; font-size: 13px; }
#shop-close { background: none; border: none; color: #8f83bd; font-size: 15px;
  cursor: pointer; padding: 2px 4px; }
.shop-row { display: flex; align-items: center; gap: 9px; padding: 5px 2px;
  border-bottom: 1px solid rgba(106, 90, 205, .15); }
.shop-ico { font-size: 17px; width: 22px; text-align: center; }
.shop-name { flex: 1; font-size: 13.5px; min-width: 0; }
.shop-name small { display: block; color: #8f83bd; font-size: 11px; }
.shop-buy { background: #2c2250; color: #ffd166; border: 1px solid #6a5acd;
  border-radius: 8px; padding: 4px 10px; font-size: 12.5px; cursor: pointer;
  font-family: inherit; white-space: nowrap; }
.shop-buy:hover { background: #3a2d6b; }
.shop-buy.cant { opacity: .45; }
#shop-sell-title { margin: 10px 0 4px; font-size: 12px; color: #8f83bd;
  letter-spacing: 1px; }
#shop-sell-title small { color: #6f639c; }
.shop-empty { color: #6f639c; font-size: 12px; padding: 4px 2px; }

/* --- ability hotbar: arcane orbs --- */
@property --cd { syntax: '<number>'; inherits: true; initial-value: 0; }
#hotbar { position: fixed; bottom: max(12px, env(safe-area-inset-bottom));
  left: 50%; transform: translateX(-50%); display: flex; gap: 13px; z-index: 30;
  transition: opacity .35s ease, transform .35s ease;
  --hb-rim: #6a5acd; --hb-glow: rgba(138, 108, 255, .6); --hb-core: rgba(84, 58, 178, .5); }
#hotbar[data-cls="warrior"] { --hb-rim: #d98a4a; --hb-glow: rgba(255, 150, 70, .6); --hb-core: rgba(170, 78, 26, .48); }
#hotbar[data-cls="mystic"]  { --hb-rim: #7f9dff; --hb-glow: rgba(120, 160, 255, .65); --hb-core: rgba(64, 88, 200, .5); }
#hotbar[data-cls="ranger"]  { --hb-rim: #5cbf7a; --hb-glow: rgba(96, 214, 138, .6); --hb-core: rgba(28, 130, 70, .48); }
.hb-slot { position: relative; width: 56px; height: 56px; border-radius: 50%;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--hb-rim);
  background:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, .18), transparent 44%),
    radial-gradient(circle at 50% 68%, var(--hb-core), rgba(12, 7, 28, .96) 80%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .55), inset 0 0 14px rgba(0, 0, 0, .5);
  transition: transform .09s ease, --cd .32s linear; }
.hb-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  border-radius: 50%; z-index: 1; pointer-events: none;
  box-shadow: inset 0 0 16px rgba(0, 0, 0, .65); }
.hb-slot.has-art .hb-icon { display: none; }
.hb-slot.has-art { background: #0c071c; }
/* slow-turning rune ring around ready skills */
.hb-slot.ready::after { content: ''; position: absolute; inset: -7px; border-radius: 50%;
  border: 1.5px dashed var(--hb-rim); opacity: .75; pointer-events: none;
  animation: hb-runering 10s linear infinite; }
@keyframes hb-runering { to { transform: rotate(1turn); } }
.hb-slot:active { transform: scale(.88); filter: brightness(1.5); }
/* ready to fire: soft breathing glow */
.hb-slot.ready { animation: hb-breathe 3.6s ease-in-out infinite; }
@keyframes hb-breathe {
  0%, 100% { box-shadow: 0 0 5px var(--hb-glow), 0 3px 10px rgba(0, 0, 0, .55), inset 0 0 14px rgba(0, 0, 0, .5); }
  50% { box-shadow: 0 0 15px var(--hb-glow), 0 0 32px var(--hb-glow), 0 3px 10px rgba(0, 0, 0, .55), inset 0 0 10px rgba(0, 0, 0, .35); }
}
/* the moment a cooldown finishes: bright ignition ring */
.hb-slot.ignite { animation: hb-ignite .5s ease-out; }
@keyframes hb-ignite {
  0% { box-shadow: 0 0 0 0 var(--hb-glow), 0 0 24px var(--hb-glow); filter: brightness(2); }
  100% { box-shadow: 0 0 0 14px rgba(0, 0, 0, 0), 0 0 8px var(--hb-glow); filter: brightness(1); }
}
.hb-icon { font-size: 23px; filter: drop-shadow(0 0 4px rgba(0, 0, 0, .8)); z-index: 2; }
.hb-key { position: absolute; top: 4px; left: 9px; font-size: 9px; color: #b9aee6;
  z-index: 4; text-shadow: 0 1px 2px #000; }
/* pressing the key should look like pressing the button, or the keyboard
   feels like it is doing nothing */
.hb-slot.keyed, #attack-btn.keyed { animation: key-press .18s ease-out; }
@keyframes key-press {
  0% { transform: scale(.86); filter: brightness(1.6); }
  100% { transform: scale(1); filter: none; }
}
#atk-key { position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  font-size: 9px; letter-spacing: .08em; color: #ffd9c2; z-index: 4;
  text-shadow: 0 1px 2px #000; pointer-events: none; }
.hb-cd { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 15px; font-weight: 700; color: #ffd166;
  pointer-events: none; z-index: 3; text-shadow: 0 1px 3px #000; }
/* radial cooldown sweep - --cd runs 1 (just used) down to 0 (ready) */
/* channeling: every other skill is refused by the sim - dim them so the
   lock reads as a rule, not a bug. Moving is the cancel, not another button. */
.hb-slot.chan-locked { opacity: .38; filter: grayscale(.75); transform: scale(.94);
  transition: opacity .18s ease, transform .18s ease; }
.hb-slot.chan-locked .hb-icon, .hb-slot.chan-locked .hb-art { opacity: .6; }
.hb-slot.cooling::before { content: ''; position: absolute; inset: -1px; z-index: 2;
  background: conic-gradient(rgba(4, 2, 12, .82) calc(var(--cd, 0) * 1turn), transparent 0);
  border-radius: 50%; }
.hb-slot.cooling .hb-icon { opacity: .45; }
.hb-slot.casting { border-color: #baf0ff; animation: hb-cast .7s ease-in-out infinite; }
@keyframes hb-cast {
  0%, 100% { box-shadow: 0 0 8px rgba(154, 216, 255, .5); }
  50% { box-shadow: 0 0 22px rgba(154, 216, 255, .95); }
}
.hb-slot.locked { opacity: .45; filter: grayscale(.9); }
.hb-slot.locked .hb-cd { font-size: 10px; color: #cbc2ee; background: rgba(5, 3, 15, .55); border-radius: 50%; }
/* THE TOUCH LAYOUT IS A GUESS; A KEYPRESS IS PROOF.
   Every touch-UI rule below is gated on body:not(.kb): main.js adds .kb on the
   first trusted keydown and remembers it, because media queries lie - Alan's
   laptop reports (pointer: coarse) and got the thumb fan instead of the 12345
   hotbar. A phone never presses a key, so it keeps the fan; anything with a
   keyboard proves itself out of it forever. */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* centered thumb fan: primary in the middle where the thumb rests,
     skills curving up either side - no corner reach after tapping a mob */
  body:not(.kb) #hotbar { left: 50%; transform: translateX(-50%); right: auto; width: 330px;
    height: 108px; bottom: max(20vh, env(safe-area-inset-bottom)); display: block; }
  body:not(.kb) .hb-slot { position: absolute; width: 50px; height: 50px; }
  body:not(.kb) .hb-slot:nth-child(1) { width: 64px; height: 64px; left: 133px; bottom: 2px; }
  body:not(.kb) .hb-slot:nth-child(1) .hb-icon { font-size: 30px; }
  body:not(.kb) .hb-slot:nth-child(2) { left: 72px; bottom: 12px; }
  body:not(.kb) .hb-slot:nth-child(3) { left: 12px; bottom: 34px; }
  body:not(.kb) .hb-slot:nth-child(4) { left: 208px; bottom: 12px; }
  body:not(.kb) .hb-slot:nth-child(5) { left: 268px; bottom: 34px; }
  body:not(.kb) .hb-key { display: none; }
}

/* no double-tap zoom anywhere in the game UI */
html, body { touch-action: manipulation; }

/* sound toggle: under the minimap dock */
#settings-btn { position: absolute; top: 138px; right: 22px; width: 36px; height: 36px;
  border-radius: 50%; background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.1), transparent 45%), rgba(16, 10, 34, .92);
  border: 1.5px solid #cfa54e; box-shadow: 0 0 8px rgba(212, 169, 79, .3), 0 2px 8px rgba(0,0,0,.5);
  font-size: 16px; color: #ffd166; cursor: pointer; z-index: 6; }
/* --- item menu: tap a bag slot, choose what happens to the item --- */
#item-menu { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 250px; z-index: 60;
  border: 18px solid transparent; border-image: url('../assets/ui/panel_frame.png') 100 stretch;
  background: linear-gradient(160deg, rgba(13, 9, 30, .97), rgba(6, 4, 18, .97)) padding-box;
  padding: 12px 14px; font-family: Georgia, serif; text-align: center;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .6); }
/* drag-to-rotate 3D weapon preview above the item name. Square so the model can be spun to
   any orientation without clipping. Only the four weapons with procedural models get one;
   everything else keeps the flat icon and this stays hidden. */
#im-preview { width: 190px; height: 190px; margin: -2px auto 8px; border-radius: 10px;
  background: radial-gradient(ellipse at 50% 42%, rgba(90,70,130,.30), rgba(8,6,20,.55) 70%);
  box-shadow: inset 0 0 22px rgba(0,0,0,.55); overflow: hidden; position: relative; }
#im-preview[hidden] { display: none; }
#im-preview::after { content: 'drag to turn'; position: absolute; left: 0; right: 0; bottom: 5px;
  font-size: 10px; letter-spacing: .08em; color: #7d739c; pointer-events: none; }
#im-canvas { display: block; width: 190px; height: 190px; cursor: grab;
  touch-action: none; -webkit-user-select: none; user-select: none; }
#im-canvas.dragging { cursor: grabbing; }
#im-name { font-size: 15px; color: #f0e9ff; letter-spacing: .03em; margin-bottom: 4px; }
#im-name[data-rarity="uncommon"] { color: #8ee6a1; }
#im-name[data-rarity="rare"] { color: #7f9dff; }
#im-name[data-rarity="epic"] { color: #c99aff; }
#im-name[data-rarity="legendary"] { color: #ffd166; }
#im-info { font-size: 12px; color: #a99cd6; margin-bottom: 4px; white-space: pre-line; }
#im-diff { font-size: 12px; color: #8f83bd; margin-bottom: 10px; }
#im-diff:empty { display: none; }
#item-menu .up { color: #8ee6a1; }
#item-menu .down { color: #ff9d9d; }
/* the slot the open menu belongs to keeps a gold ring */
.im-target { outline: 2px solid #ffd166; outline-offset: 1px; border-radius: 10px; }
#item-menu button { display: block; width: 100%; margin: 6px 0; padding: 8px 0;
  border-radius: 9px; font-family: inherit; font-size: 13.5px; cursor: pointer; }
#im-act { background: #2c2250; color: #ffd166; border: 1px solid #6a5acd; }
#im-act:hover { background: #3a2d6b; }
#im-drop { background: rgba(90, 24, 34, .55); color: #ff9d9d; border: 1px solid #8a3a48; }
#im-drop:hover { background: rgba(122, 30, 44, .7); }
#im-cancel { background: transparent; color: #8f83bd; border: 1px solid #3a3060; }
#im-cancel:hover { color: #cfc3f2; }

#settings-panel { position: absolute; top: 184px; right: 14px; width: 224px; z-index: 8;
  border: 18px solid transparent; border-image: url('../assets/ui/panel_frame.png') 100 stretch;
  background: linear-gradient(160deg, rgba(13, 9, 30, .96), rgba(6, 4, 18, .96)) padding-box;
  padding: 10px 12px; font-family: Georgia, serif;
  transform-origin: top right; transition: transform .18s ease, opacity .18s ease;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .55); }
#settings-panel.closed { transform: scale(.7); opacity: 0; pointer-events: none; }
#set-title { display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; letter-spacing: 1.5px; text-transform: uppercase; color: #a99ed4;
  margin-bottom: 9px; }
#set-close { background: none; border: none; color: #8f83bd; font-size: 14px; cursor: pointer; }
.set-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; margin-bottom: 6px; border-radius: 10px; cursor: pointer;
  background: #17102e; border: 1px solid #2c2250; color: #e8e2f7;
  font-family: Georgia, serif; font-size: 13px; }
.set-row:hover { border-color: #6a5acd; }
.set-row.off { opacity: .5; }
.set-row.off::after { content: 'off'; margin-left: auto; color: #8f83bd; font-size: 11px; }
.set-row:not(.off)::after { content: 'on'; margin-left: auto; color: #7fbf7f; font-size: 11px; }
#set-switch::after { content: '' !important; }
#set-version { text-align: center; color: #6f639c; font-size: 10.5px; margin-top: 4px; }
@media (max-width: 540px) {
  #settings-btn { top: 128px; right: 16px; width: 33px; height: 33px; }
  #bag-btn { top: 128px; right: 57px; width: 33px; height: 33px; font-size: 14px; }
  #inv-panel { top: 184px; }
}

/* game surfaces are not a document: no tap flash, no text/image selection,
   no long-press callout menus */
html, body { -webkit-tap-highlight-color: transparent; }
#game, #hud, #select-screen {
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
#hud img, #select-screen img {
  -webkit-user-drag: none; user-drag: none; pointer-events: none;
}
#select-controls input { user-select: text; -webkit-user-select: text; }

#inv-close { margin-left: auto; background: none; border: none; color: #8f83bd;
  font-size: 15px; cursor: pointer; padding: 2px 6px; }
#inv-close:hover { color: #e8e2f7; }

/* bank panel: same skin as the shop */
#bank-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(360px, calc(100vw - 24px)); max-height: min(520px, 80vh); overflow-y: auto;
  background: rgba(12, 7, 28, .96); border: 1px solid #6a5acd; border-radius: 14px;
  padding: 12px 14px; z-index: 40; color: #e8e2f7; font-family: Georgia, serif; }
#bank-panel.closed { display: none; }
#bank-title { font-size: 14px; letter-spacing: 2px; color: #9ad8ff;
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
#bank-count2 { margin-left: auto; color: #8f83bd; font-size: 12px; }
#bank-close { background: none; border: none; color: #8f83bd; font-size: 15px;
  cursor: pointer; padding: 2px 4px; }
#bank-dep-title { margin: 10px 0 4px; font-size: 12px; color: #8f83bd; letter-spacing: 1px; }
.bank-qty { color: #ffd166; font-size: 12.5px; white-space: nowrap; }

/* cast bar above the hotbar */
#cast-bar { position: fixed; bottom: 74px; left: 50%; transform: translateX(-50%);
  width: 200px; height: 17px; border-radius: 9px; background: #12091e;
  border: 1.5px solid #cfa54e; overflow: hidden; z-index: 31;
  box-shadow: 0 0 12px rgba(212, 169, 79, .35), inset 0 1px 4px rgba(0,0,0,.7); }
#cast-fill { height: 100%; width: 0; transition: width .28s linear;
  background: linear-gradient(90deg, #8a6cff, #9ad8ff 60%, #d8f4ff);
  box-shadow: 0 0 10px rgba(154, 216, 255, .8); }
#cast-label { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 10.5px; color: #fff; letter-spacing: 1px;
  font-family: Georgia, serif; text-shadow: 0 1px 3px rgba(0,0,0,.8); }
.hb-slot.casting { border-color: #9ad8ff; box-shadow: 0 0 12px rgba(154, 216, 255, .5); }
@media (max-width: 540px) { #cast-bar { bottom: 66px; } }

/* loading screen between select and world */
#loading { position: fixed; inset: 0; z-index: 60; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 18px;
  background: radial-gradient(ellipse at 50% 35%, #1a1040 0%, #05030f 70%);
  font-family: Georgia, serif; }
#loading[hidden] { display: none; }
#load-emblem { width: 84px; height: 84px; border-radius: 22%;
  box-shadow: 0 0 26px rgba(122, 160, 255, .55);
  animation: load-pulse 1.8s ease-in-out infinite; }
@keyframes load-pulse { 0%, 100% { transform: scale(1); box-shadow: 0 0 18px rgba(122, 160, 255, .4); }
  50% { transform: scale(1.06); box-shadow: 0 0 38px rgba(122, 160, 255, .75); } }
#load-title { font-size: 24px; letter-spacing: 8px; text-indent: 8px;
  background: linear-gradient(180deg, #fff8e9 20%, #ffd166 55%, #b8860b 90%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 14px rgba(255, 209, 102, .3)); }
#load-bar { width: min(300px, 70vw); height: 9px; border-radius: 6px;
  background: #170d24; border: 1px solid #7a6430; overflow: hidden; }
#load-fill { height: 100%; width: 0; transition: width .2s ease;
  background: linear-gradient(90deg, #ffd166, #fff3c9 60%, #ffd166); }
#load-tip { color: #8f83bd; font-size: 13px; font-style: italic; }

/* --- cinematic dialogue (FF-style portrait scenes) --- */
#cine {
  position: fixed; inset: 0; z-index: 60; cursor: pointer;
  -webkit-user-select: none; user-select: none; overflow: hidden;
  background: linear-gradient(180deg, rgba(2,4,10,.25), rgba(2,4,10,0) 30%,
              rgba(2,4,10,0) 42%, rgba(2,4,10,.72) 68%, rgba(2,4,10,.96) 88%);
  animation: cine-in .28s ease-out;
}
@keyframes cine-in { from { opacity: 0; } to { opacity: 1; } }
#cine-portrait {
  /* Ordinary speakers get a BUST: object-fit cover, with the box aspect set
     per sprite in hud.js so every character crops to the same share of its own
     body. The boss keeps object-fit contain below - his whole figure and that
     greatsword are the point of his scene. */
  position: absolute; right: 1vw; bottom: 0; height: 46vh; max-width: 46vw;
  object-fit: cover; object-position: 50% 0%;
  /* A rim, not a border: stacked drop-shadows trace the alpha silhouette so
     the outline follows the character instead of boxing the image. */
  filter: drop-shadow(1.5px 0 0 #140f26) drop-shadow(-1.5px 0 0 #140f26)
          drop-shadow(0 1.5px 0 #140f26) drop-shadow(0 -1.5px 0 #140f26);
}
#cine-portrait.enter { animation: cine-enter .34s cubic-bezier(.2, .8, .3, 1) both; }
/* wide bust crops (the boss): the 70vw cap shrinks a square image to a
   fraction of a tall one's height, so the wide lane lets him tower. Hung
   well off the right edge so the dialogue text keeps its column. */
/* the boss keeps his full figure - the greatsword and the silhouette are the
   whole reason that scene lands */
#cine-portrait.boss { max-width: 96vw; right: -6vw; height: 62vh; bottom: 0;
  aspect-ratio: auto; object-fit: contain; object-position: bottom right; }
@keyframes cine-enter {
  0% { opacity: 0; transform: translateY(34px) scale(.97); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
#cine-band {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 14px 18px calc(20px + env(safe-area-inset-bottom)) 22px;
  min-height: 21vh; display: flex; flex-direction: column; gap: 8px;
}
#cine-name {
  display: flex; align-items: center; gap: 12px;
  color: #6fd7ff; font-size: 15px; letter-spacing: .06em;
}
#cine-name::after {
  content: ''; flex: 1; height: 1px; max-width: 46vw;
  background: linear-gradient(90deg, rgba(111,215,255,.85), rgba(111,215,255,0));
}
#cine-text {
  color: #f2f6ff; font-size: 17px; line-height: 1.5; max-width: 66vw;
  padding-left: 26px; min-height: 3em;
  text-shadow: 0 1px 3px rgba(0,0,0,.9);
}
#cine-next {
  align-self: center; color: #6fd7ff; font-size: 13px;
  animation: cine-bob 1.1s ease-in-out infinite;
}
#cine-next[hidden] { display: none; }
@keyframes cine-bob {
  0%, 100% { transform: translateY(0); opacity: .95; }
  50% { transform: translateY(4px); opacity: .45; }
}
@media (max-width: 700px) {
  #cine-portrait { right: 4px; bottom: 0; height: 30vh; max-width: 52vw; }
  #cine-text { max-width: 60vw; font-size: 15px; padding-left: 8px; }
  #cine-name::after { max-width: 30vw; }
  #cine-next { align-self: flex-start; margin-left: 24vw; }
}

/* --- quest journal (tap the tracker chip) --- */
#quest-tracker { cursor: pointer; }
#quest-log { position: absolute; top: 128px; left: 14px; width: 302px; z-index: 6;
  max-height: calc(100dvh - 150px); overflow-y: auto;
  background: linear-gradient(160deg, rgba(18, 10, 40, .95), rgba(8, 5, 22, .95));
  border: 1px solid #4b3d7a; border-radius: 14px; padding: 12px 14px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, .5);
  transform-origin: top left; transition: transform .18s ease, opacity .18s ease;
  font-size: 12.5px; }
#quest-log.closed { transform: scale(.7); opacity: 0; pointer-events: none; }
#ql-head { display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; color: #a99ed4; letter-spacing: 1.5px; text-transform: uppercase;
  margin-bottom: 9px; }
#ql-close { background: none; border: none; color: #8f83bd; font-size: 14px;
  cursor: pointer; padding: 0 0 0 8px; }
.ql-row { display: flex; gap: 9px; align-items: baseline; padding: 6px 0;
  border-top: 1px solid #241a45; }
.ql-row:first-child { border-top: none; }
.ql-ico { flex-shrink: 0; font-size: 13px; width: 18px; text-align: center; }
/* the seal carries the state, so it is coloured rather than pictorial */
.ql-row.done .ql-ico { color: #7fbf7f; }
.ql-row.active .ql-ico { color: #ffd166; text-shadow: 0 0 7px rgba(255, 209, 102, .7); }
.ql-row.ready .ql-ico { color: #9ad8ff; text-shadow: 0 0 7px rgba(154, 216, 255, .6); }
.ql-row.locked .ql-ico { color: #6b6190; }
.ql-txt { min-width: 0; flex: 1; }
.ql-txt b { display: block; font-size: 12.5px; color: #e8e2f7; }
.ql-row.done .ql-txt b { color: #7fbf7f; }
.ql-row.locked { opacity: .5; }
.ql-txt small { display: block; color: #8f83bd; font-size: 11px; margin-top: 1px; }
.ql-row.active .ql-txt b { color: #ffd166; }

/* --- the current objective, on every map surface --- */
/* A quest target reads as a mob you WANT: the mob red, ringed in the quest
   gold the tracker and the giver dots already use, and breathing so the eye
   finds it among a dozen ordinary dots. */
.map-dot.quest-mob { background: #ff7a6b; box-shadow: 0 0 0 2px #ffd166,
  0 0 8px rgba(255, 209, 102, .9); animation: qmob 1.5s ease-in-out infinite; }
@keyframes qmob { 0%, 100% { box-shadow: 0 0 0 2px #ffd166, 0 0 7px rgba(255, 209, 102, .8); }
  50% { box-shadow: 0 0 0 3px #ffd166, 0 0 13px rgba(255, 209, 102, 1); } }
.w-node.objective i { box-shadow: 0 0 0 2px #ffd166, 0 0 14px rgba(255, 209, 102, .85); }
.w-node .w-seal { position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  font-size: 12px; font-style: normal; color: #ffd166;
  text-shadow: 0 0 8px rgba(255, 209, 102, .95); animation: qmob 1.5s ease-in-out infinite; }
.w-node.objective span { color: #ffd166; }


/* --- quest journal: acts, and the fog over what is ahead --- */
.ql-act { display: flex; align-items: baseline; gap: 8px; cursor: pointer;
  margin: 10px 0 2px; padding: 5px 7px; border-radius: 7px;
  background: rgba(75, 61, 122, .22); border: 1px solid #3a2f63; }
.ql-act:first-child { margin-top: 0; }
.ql-act-n { flex-shrink: 0; width: 22px; font-size: 10.5px; letter-spacing: 1px;
  color: #8f83bd; text-align: center; }
.ql-act-t { flex: 1; min-width: 0; font-size: 12px; color: #e8e2f7;
  letter-spacing: .6px; }
.ql-act-c { flex-shrink: 0; font-size: 10.5px; color: #8f83bd; }
.ql-act.done { background: rgba(60, 100, 60, .16); border-color: #33532f; }
.ql-act.done .ql-act-t { color: #7fbf7f; }
.ql-act.done .ql-act-n::after { content: ' \2713'; }
/* a sealed act names nothing - it only says the road goes on, and how far */
.ql-act.sealed { background: rgba(20, 14, 40, .5); border-color: #2b2350;
  cursor: default; opacity: .72; }
.ql-act.sealed .ql-act-t { color: #6b6190; font-style: italic; }
.ql-act.open { border-color: #6b5aa8; }
.ql-row.fog { opacity: .42; }
.ql-row.fog .ql-ico { color: #6b6190; }
.ql-row.fog .ql-txt b { color: #8f83bd; letter-spacing: 2px; }
.ql-foot { margin-top: 11px; padding-top: 8px; border-top: 1px solid #241a45;
  font-size: 10.5px; color: #8f83bd; text-align: center; letter-spacing: .8px; }



/* touch layout: lift the cast bar and battle log above the fan */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) #inv-panel { top: 190px; right: 12px; }
  body:not(.kb) #cast-bar { bottom: calc(20vh + 118px); }
  /* The log lives at the bottom centre, ALWAYS - it never moves. The skill
     fan ends ~150px higher and the attack button ends above this band too,
     so the low lane is permanently clear; the earlier lift-in-combat made
     the whole HUD look like it was shuffling. */
  body:not(.kb) #log { left: 50%; transform: translateX(-50%); text-align: center;
    width: min(360px, calc(100vw - 44px));
    bottom: max(22px, calc(env(safe-area-inset-bottom) + 16px));
    font-size: 11px; line-height: 15px; max-height: 45px; }
  body:not(.kb) #log div { padding: 0; }
  body:not(.kb) #toasts { bottom: 186px; }
}


/* --- target frame: what you're fighting, top centre --- */
/* Docked under the player portrait (Alan's call): your target's name and
   health live where every MMO puts them, not floating centre-screen. */
#target-frame { position: absolute; top: 96px; left: 14px; transform: none;
  min-width: 190px; max-width: 40vw; z-index: 7; padding: 8px 14px 9px;
  text-align: left;
  background: linear-gradient(160deg, rgba(40, 14, 22, .9), rgba(16, 6, 14, .9));
  border: 1px solid #a04858; border-radius: 12px; text-align: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .5); pointer-events: none; }
#tf-name { font-size: 13px; color: #ffd7dd; letter-spacing: .5px; margin-bottom: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#tf-hp { position: relative; height: 10px; border-radius: 5px; background: #170d14;
  border: 1px solid #5c2a36; overflow: hidden; }
#tf-fill { height: 100%; width: 100%; border-radius: 4px;
  background: linear-gradient(90deg, #ff5d73, #d92645 60%, #a3122e);
  transition: width .22s ease; }
#tf-num { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 8.5px; font-weight: 700; color: #ffe3e8;
  text-shadow: 0 1px 2px #000; letter-spacing: .5px; }
@media (max-width: 700px) { #target-frame { top: 108px; left: 10px; min-width: 170px; } }

/* --- duel: your hp, the opponent's, and the 3-2-1 countdown --- */
#duel-frame { position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  min-width: 220px; max-width: 64vw; z-index: 7; padding: 10px 14px 9px;
  background: linear-gradient(160deg, rgba(32, 12, 46, .92), rgba(13, 5, 22, .92));
  border: 1px solid #6a3fae; border-radius: 12px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .5); pointer-events: none; }
/* The bell, centre screen. It used to be a 22px line inside the duel panel,
   which is the same place the game puts a target's name - so the moment the
   fight actually starts read as a HUD label rather than an event. It is over
   in three seconds and nothing else is happening yet, so it can afford the
   whole screen. pointer-events: none throughout - the arena is live
   underneath it and a click must never land on the number. */
#duel-countdown { position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center; pointer-events: none; }
#duel-countdown[hidden] { display: none; }
.dc-beat { font-weight: 900; line-height: 1; letter-spacing: 2px;
  color: #ffd166; text-shadow: 0 0 26px rgba(255, 180, 60, .85),
  0 6px 18px rgba(0, 0, 0, .75); will-change: transform, opacity; }
/* a struck number: lands hard, then falls away while the next one lands */
.dc-beat.num { font-size: clamp(88px, 22vw, 200px);
  animation: dc-punch 1s cubic-bezier(.2, .9, .25, 1) both; }
/* FIGHT! is wider than it is tall, so it gets its own smaller scale and a
   held finish rather than a fade to nothing */
.dc-beat.go { font-size: clamp(44px, 11vw, 104px); color: #ff5f52;
  text-shadow: 0 0 30px rgba(255, 70, 50, .9), 0 6px 18px rgba(0, 0, 0, .8);
  animation: dc-go .9s cubic-bezier(.15, .95, .2, 1) both; }
@keyframes dc-punch {
  0%   { transform: scale(2.1); opacity: 0; }
  14%  { transform: scale(1); opacity: 1; }
  70%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(.72); opacity: 0; }
}
@keyframes dc-go {
  0%   { transform: scale(.4) rotate(-6deg); opacity: 0; }
  18%  { transform: scale(1.18) rotate(0deg); opacity: 1; }
  32%  { transform: scale(1); }
  75%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.5); opacity: 0; }
}
/* THE RESULT, at the size of the bell. A duel used to end in a line of chat -
   the one moment the whole thing exists for was the quietest thing on screen.
   Wider than a number, so it takes the same treatment at a smaller scale. */
.dc-beat.result { font-size: clamp(40px, 10vw, 96px); letter-spacing: 6px;
  animation: dc-result 2.6s cubic-bezier(.12, .9, .2, 1) both; }
.dc-beat.result.win  { color: #ffd97a;
  text-shadow: 0 0 34px rgba(255, 200, 90, .9), 0 6px 20px rgba(0, 0, 0, .8); }
.dc-beat.result.loss { color: #8fa2c4;
  text-shadow: 0 0 26px rgba(120, 150, 200, .6), 0 6px 20px rgba(0, 0, 0, .85); }
.dc-beat.result.draw { color: #cdc3e6;
  text-shadow: 0 0 24px rgba(190, 175, 230, .55), 0 6px 20px rgba(0, 0, 0, .8); }
/* the rating line, under the word - the number people actually came for */
.dc-sub { margin-top: 10px; font-size: clamp(14px, 3.4vw, 22px); font-weight: 700;
  letter-spacing: 2px; color: #e8e2f7; text-align: center;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .9);
  animation: dc-sub-in 2.6s ease both; }
@keyframes dc-result {
  0%   { transform: scale(.55); opacity: 0; }
  10%  { transform: scale(1.06); opacity: 1; }
  20%  { transform: scale(1); }
  82%  { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.12); opacity: 0; }
}
@keyframes dc-sub-in {
  0%, 12% { opacity: 0; transform: translateY(6px); }
  24%     { opacity: 1; transform: translateY(0); }
  82%     { opacity: 1; }
  100%    { opacity: 0; }
}
/* Forfeit: deliberately the quietest thing in the duel panel until it is
   armed, and unmistakable once it is. It is the only control here, so it also
   has to take pointer events back - #duel-frame turns them off. */
#duel-quit { display: block; margin: 8px auto 0; pointer-events: auto;
  padding: 4px 14px; font: inherit; font-size: 11px; letter-spacing: 1px;
  color: #b9a8cf; background: rgba(20, 10, 32, .8);
  border: 1px solid #4b3570; border-radius: 8px; cursor: pointer; }
#duel-quit:hover { color: #e8e2f7; border-color: #6a3fae; }
#duel-quit.armed { color: #23121a; background: #e06a5a; border-color: #ff8f7d;
  font-weight: 700; animation: dq-pulse .9s ease-in-out infinite; }
@keyframes dq-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
@media (prefers-reduced-motion: reduce) {
  .dc-beat.num, .dc-beat.go, .dc-beat.result { animation: dc-fade .9s ease both; }
  #duel-quit.armed { animation: none; }
  @keyframes dc-fade { 0% { opacity: 0; } 12% { opacity: 1; }
                       75% { opacity: 1; } 100% { opacity: 0; } }
}
.duel-row { display: flex; align-items: center; gap: 8px; margin-top: 5px; }
.duel-row:first-of-type { margin-top: 0; }
.duel-label { font-size: 11px; color: #e8e2f7; min-width: 44px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.duel-hp { position: relative; flex: 1; height: 10px; border-radius: 5px;
  background: #150d24; border: 1px solid #4a3178; overflow: hidden; }
.duel-hp > div { height: 100%; width: 100%; border-radius: 4px;
  transition: width .18s ease; }
.duel-mine .duel-hp > div { background: linear-gradient(90deg, #6bdc7c, #2ecc40 60%, #1c9e30); }
.duel-foe .duel-hp > div { background: linear-gradient(90deg, #ff5d73, #d92645 60%, #a3122e); }
.duel-hp span { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 8.5px; font-weight: 700; color: #fff;
  text-shadow: 0 1px 2px #000; letter-spacing: .5px; }
@media (max-width: 700px) { #duel-frame { top: 108px; min-width: 190px; } }

/* --- first-minutes hint bubble --- */
#hint-bubble { position: fixed; bottom: max(58px, env(safe-area-inset-bottom)); left: 50%;
  transform: translateX(-50%); max-width: 74vw; z-index: 45; padding: 8px 30px 8px 14px;
  text-align: center; background: rgba(14, 8, 32, .94); border: 1px solid #8a6cff;
  border-radius: 14px; color: #e8e2f7; font-family: Georgia, serif; font-size: 12.5px;
  cursor: pointer; box-shadow: 0 0 18px rgba(138, 108, 255, .35);
  animation: hint-pulse 2.2s ease-in-out infinite; }
#hint-bubble::after { content: '✕'; position: absolute; top: 50%; right: 10px;
  transform: translateY(-50%); color: #8f83bd; font-size: 12px; }
@keyframes hint-pulse { 0%, 100% { box-shadow: 0 0 10px rgba(138, 108, 255, .25); }
  50% { box-shadow: 0 0 24px rgba(138, 108, 255, .55); } }
/* clears the battle log's idle stack (22px up, 45px tall) instead of sitting on it */
@media (max-width: 700px) { #hint-bubble { bottom: max(78px, calc(env(safe-area-inset-bottom) + 72px)); } }


/* --- tutorial bubble: the skip has to be right there --- */
/* The bubble used to be dismiss-on-tap with an X. That clears ONE tip; a
   player who does not want tips wants them all gone, in one action. */
#hint-bubble .hint-text { display: block; }
#hint-bubble .hint-skip { display: block; margin: 6px auto 0; padding: 3px 12px;
  background: rgba(138, 108, 255, .18); border: 1px solid #6b5aa8; border-radius: 9px;
  color: #c9c0ec; font-family: Georgia, serif; font-size: 11px; cursor: pointer; }
#hint-bubble .hint-skip:hover { background: rgba(138, 108, 255, .34); color: #fff; }

/* --- how to play --- */
#help-panel { position: absolute; top: 90px; left: 50%; transform: translateX(-50%);
  width: min(430px, 92vw); max-height: min(70dvh, 560px); overflow-y: auto; z-index: 30;
  background: linear-gradient(160deg, rgba(18, 10, 40, .97), rgba(8, 5, 22, .97));
  border: 1px solid #4b3d7a; border-radius: 14px; padding: 14px 16px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .6); font-size: 12.5px; color: #e8e2f7; }
#help-panel.closed { display: none; }
#help-title { display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; color: #a99ed4; letter-spacing: 1.6px; text-transform: uppercase;
  margin-bottom: 10px; }
#help-close { background: none; border: none; color: #8f83bd; font-size: 14px; cursor: pointer; }
.help-h { margin: 12px 0 5px; color: #ffd166; font-size: 11px; letter-spacing: 1.3px;
  text-transform: uppercase; }
.help-h:first-child { margin-top: 0; }
.help-row { display: flex; gap: 10px; padding: 4px 0; border-top: 1px solid #241a45;
  align-items: baseline; }
.help-row b { flex: 0 0 40%; color: #e8e2f7; font-weight: 600; }
.help-row span { flex: 1; color: #a99ed4; }
.set-row.off { opacity: .45; }

#world-note { margin-top: 8px; text-align: center; font-size: 11.5px; color: #8f83bd; }
#world-note.warn { color: #ffd166; }
/* an unreachable destination still shows, but reads as out of reach */
.w-node.far { opacity: .45; cursor: default; }

/* --- the waypoint prompt --- */
#wp-prompt { position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 172px; z-index: 30; padding: 9px 20px; border-radius: 13px;
  border: 1px solid #ffd166; background: rgba(30, 20, 8, .88); color: #ffe9a8;
  font-family: Georgia, serif; font-size: 14px; cursor: pointer;
  box-shadow: 0 0 18px rgba(255, 209, 102, .45);
  animation: wp-breathe 2.1s ease-in-out infinite; }
#wp-prompt[hidden] { display: none; }
#wp-prompt:hover { background: rgba(52, 36, 12, .95); color: #fff; }
@keyframes wp-breathe { 0%, 100% { box-shadow: 0 0 12px rgba(255, 209, 102, .35); }
  50% { box-shadow: 0 0 26px rgba(255, 209, 102, .7); } }
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) #wp-prompt { bottom: 210px; font-size: 13px; }
}

/* --- loot flying into the bag --- */
.loot-fly { position: fixed; z-index: 80; font-size: 22px; pointer-events: none;
  transition: transform .55s cubic-bezier(.5, -0.2, .9, .6), opacity .55s ease; }

/* quest-giver dot on the maps */
.map-dot.quest { background: #ffd166; box-shadow: 0 0 6px #ffd166; }
.map-dot.portal { background: #7fd0ff; box-shadow: 0 0 5px #7fd0ff; }
.map-dot.party { width: 9px; height: 9px; background: #6ef29b;
  border: 1.5px solid #1f7a44; box-shadow: 0 0 6px #6ef29b; }
.map-dot.small.party { width: 7px; height: 7px; }
#map-legend .dot.quest { background: #ffd166; }
#map-legend .dot.portal { background: #7fd0ff; }
/* the legend swatches are scoped to #map-legend, so the live marker's own
   rule does not reach them - the target swatch needs saying twice */
#map-legend .dot.quest-mob { background: #ff7a6b; box-shadow: 0 0 0 2px #ffd166;
  margin-left: 3px; }

/* --- the quest pin --- */
/* First attempt was a 13px badge with an 11px "!" in it. On a 300px map that
   is a speck: Alan could not see it AT ALL. A marker you have to hunt for is
   not a marker - this is the thing you opened the map to find, so it is drawn
   as a proper pin, sized to be spotted at a glance and anchored by a point at
   the position it marks. */
.map-dot.glyph { width: 22px; height: 22px; border-radius: 50% 50% 50% 0;
  transform: translate(-50%, -100%) rotate(-45deg);
  background: linear-gradient(145deg, #ffe9a8, #ffc42e);
  border: 2px solid #4a3208; box-sizing: border-box;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .65), 0 0 12px rgba(255, 209, 102, .8);
  z-index: 6; animation: qpin 1.7s ease-in-out infinite; }
/* the glyph rides upright inside a pin that is itself rotated 45deg */
.map-dot.glyph::before { content: attr(data-mark); position: absolute;
  inset: 0; transform: rotate(45deg); display: flex;
  align-items: center; justify-content: center;
  font: 800 14px/1 Georgia, serif; color: #2a1c05; }
/* The minimap draws its dots inside a container scaled 2.4x (MINI_ZOOM), so
   these are AUTHORED SMALL to land at roughly 15px on screen - a 17px pin
   became 41px on a 118px dock, a third of the whole minimap. */
.map-dot.small.glyph { width: 6.5px; height: 6.5px; border-width: .9px; }
.map-dot.small.glyph::before { font-size: 4.4px; }
@keyframes qpin {
  0%, 100% { box-shadow: 0 2px 6px rgba(0,0,0,.65), 0 0 9px rgba(255,209,102,.7); }
  50% { box-shadow: 0 2px 6px rgba(0,0,0,.65), 0 0 18px rgba(255,209,102,1); } }
#map-legend .dot.glyph { background: linear-gradient(145deg, #ffe9a8, #ffc42e);
  color: #2a1c05; width: 14px; height: 14px; font: 800 11px/14px Georgia, serif;
  text-align: center; border-radius: 50%; border: 1px solid #4a3208;
  box-sizing: border-box; }



/* --- ornate celestial UI kit (Kling-painted) --- */
#stats-panel, #quest-log, #inv-panel, #bank-panel, #shop-panel, #map-panel, #dialogue {
  border: 18px solid transparent;
  border-image: url('../assets/ui/panel_frame.png') 100 stretch;
  border-radius: 0;
  background:
    linear-gradient(160deg, rgba(13, 9, 30, .96), rgba(6, 4, 18, .96)) padding-box;
}
/* slim gold rings - elegant, no bulk */
#portrait { border: 2px solid #cfa54e; box-shadow: 0 0 8px rgba(212, 169, 79, .45),
  inset 0 0 6px rgba(0, 0, 0, .5); }
#mini-dock { border: none; overflow: hidden; background: transparent; box-shadow: none;
  border-radius: 50%; }
/* THE MINIMAP FILLS ITS RING. Fitting the whole square map inside the circle
   left dead corners and the square's own edge floating in a round frame
   (Alan: "does that look bad?" - it did). The dock now shows a ZOOMED view
   CENTRED ON THE PLAYER, the way an MMO minimap works, so the disc is full
   at any position and no marker has to be faked onto the rim. The whole
   square still lives in the map panel, one tap away. hud.js writes the
   transform on each repaint. */
#mini-img, #mini-walls, #mini-dots { transform-origin: 0 0; will-change: transform; }
#mini-dock { border-radius: 50%; }   /* the dock clips; children must NOT,
     because clip-path resolves before their transform and a scaled clip
     leaves the circle oversized and off-centre (the truncated minimap) */
#mini-dock::after { content: ''; position: absolute; inset: 1px; border-radius: 50%;
  border: 2px solid #cfa54e; pointer-events: none;
  box-shadow: 0 0 10px rgba(212, 169, 79, .4), inset 0 0 8px rgba(0, 0, 0, .55); }
#mini-dock::before { content: ''; position: absolute; inset: -4px; border-radius: 50%;
  border: 1px solid rgba(212, 169, 79, .35); pointer-events: none; }
/* framed panels: kill the blurred halo under the transparent border zone */
#stats-panel, #quest-log, #inv-panel, #bank-panel, #shop-panel, #map-panel, #dialogue {
  box-shadow: 0 8px 26px rgba(0, 0, 0, .55);
}


/* painted item art */
.item-art { width: 32px; height: 32px; border-radius: 8px; object-fit: cover;
  display: block; box-shadow: 0 1px 4px rgba(0, 0, 0, .5); }
.inv-art { width: 34px; height: 34px; border-radius: 8px; object-fit: cover;
  pointer-events: none; box-shadow: 0 1px 4px rgba(0, 0, 0, .5); }
.shop-ico { display: flex; align-items: center; }


/* out of combat the skills rest out of sight */
#hotbar.resting { opacity: 0; transform: translateX(-50%) translateY(26px); pointer-events: none; }
#cast-bar.resting { opacity: 0; }
#cast-bar { transition: opacity .3s ease; }


/* ability compendium rows in the character sheet */
.ab-head { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase;
  color: #8f83bd; margin: 10px 0 6px; border-top: 1px solid #2c2250; padding-top: 9px; }
.ab-row { display: flex; gap: 10px; align-items: flex-start; padding: 5px 0; }
.ab-row.locked { opacity: .45; filter: grayscale(.7); }
.ab-art { width: 38px; height: 38px; border-radius: 50%; object-fit: cover;
  border: 1.5px solid #cfa54e; flex-shrink: 0;
  box-shadow: 0 0 6px rgba(212, 169, 79, .3); }
.ab-mark { width: 38px; height: 38px; display: flex; align-items: center;
  justify-content: center; font-size: 20px; }
.ab-txt { min-width: 0; }
.ab-txt b { display: block; font-size: 12.5px; color: #e8e2f7; }
.ab-txt small { display: block; font-size: 10px; color: #ffd166; margin: 1px 0 2px; }
.ab-txt span { display: block; font-size: 11px; color: #a99ed4; line-height: 1.35; }


/* character sheet tabs */
#stats-tabs { display: flex; gap: 6px; margin-bottom: 10px; }
.stab { flex: 1; padding: 6px 0; border-radius: 9px; font-family: Georgia, serif;
  font-size: 12px; letter-spacing: 1px; cursor: pointer; color: #8f83bd;
  background: #17102e; border: 1px solid #2c2250; }
.stab.active { color: #ffd166; border-color: #cfa54e; background: #221741;
  box-shadow: 0 0 8px rgba(212, 169, 79, .25); }
#stats-tree, #stats-gear { display: none; overflow-x: hidden; }
#stats-panel[data-tab="skills"] #stats-body,
#stats-panel[data-tab="skills"] #stats-grid,
#stats-panel[data-tab="skills"] #stats-traits-box { display: none; }
#stats-panel[data-tab="skills"] #stats-tree { display: block; }
#stats-panel[data-tab="gear"] #stats-body,
#stats-panel[data-tab="gear"] #stats-grid,
#stats-panel[data-tab="gear"] #stats-traits-box { display: none; }
#stats-panel[data-tab="gear"] #stats-gear { display: block; }

/* the Gear tab: seven slots, rarity-coloured names, a running total */
#gear-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; margin-top: 6px; }
.gear-slot { display: flex; gap: 8px; align-items: center; padding: 8px 9px;
  border: 1.5px dashed #3a2f5e; border-radius: 11px; color: #6f639c;
  background: rgba(14, 9, 30, .6); min-width: 0; }
.gear-slot.filled { border-style: solid; border-color: #6a5acd; cursor: pointer;
  background: linear-gradient(170deg, #221741 0%, #120b28 60%, #0d0720 100%);
  box-shadow: inset 0 1px 0 rgba(202, 191, 250, .1), 0 2px 6px rgba(0, 0, 0, .4); }
.gear-slot.filled:hover { background: #221740; }
.gear-slot .gs-ico, .bag-gear .gs-ico { font-size: 18px; flex: none; }
.gear-slot .gs-txt, .bag-gear .gs-txt { min-width: 0; }
.gear-slot .gs-txt small, .bag-gear .gs-txt small { display: block; font-size: 9px; letter-spacing: 1px;
  text-transform: uppercase; color: #6f639c; }
.gear-slot .gs-txt b, .bag-gear .gs-txt b { display: block; font-size: 11px; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gear-slot .gs-txt b.gs-none { color: #4d4374; font-weight: normal; }
.gear-slot .gs-txt span, .bag-gear .gs-txt span { display: block; font-size: 9.5px; color: #8f83bd;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#gear-total { margin-top: 9px; font-size: 11px; color: #ffd166;
  border-top: 1px solid #2c2250; padding-top: 8px; }

/* the bag as square cells, like the inventory - a tapped slot filters them */
.gear-slot.selected { border-color: #ffd166;
  box-shadow: 0 0 8px rgba(255, 209, 102, .4); }
#bag-gear-grid { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 7px; }
.gear-cell { width: 44px; height: 44px; border-radius: 10px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 20px;
  border: 1.5px solid #3a2f5e; background: rgba(10, 6, 26, .7); }
.gear-cell:hover { background: #1a1133; }
.gear-cell.r-rare { border-color: #4a6da8; }
.gear-cell.r-epic { border-color: #8a5cb8; }
.gear-cell.r-legendary { border-color: #cfa54e; }
.gear-cell.selected { border-color: #ffd166;
  box-shadow: 0 0 8px rgba(255, 209, 102, .5); }
.gear-worn-row { display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: 6px; font-size: 11px; color: #8f83bd; }
.gear-worn-row b { font-size: 11.5px; }
.gear-worn-row .tree-learn { margin-top: 0; padding: 4px 10px; }
.gc-name { font-size: 12px; margin-bottom: 3px; }
.gc-name small { color: #8f83bd; font-size: 10px; margin-left: 4px; }
.gear-compare { border: 1px solid #3a2f5e; border-left: 3px solid #ffd166;
  border-radius: 0 10px 10px 10px; padding: 8px 10px; margin: 2px 0 2px 14px;
  background: rgba(16, 10, 34, .8); }
.gc-vs { font-size: 10.5px; color: #8f83bd; }
.gc-vs b { color: #e8e2f7; }
.gc-diff { display: flex; gap: 9px; flex-wrap: wrap; font-size: 11px; margin-top: 4px; }
.gc-diff .up { color: #7fd0a0; }
.gc-diff .down { color: #e0705c; }
.gc-diff .same { color: #8f83bd; }

/* the level dial: a celestial astrolabe - ticked gold bezel, starfield core,
   a gradient xp arc with a burning tip */
#level-orb { position: relative; width: 118px; flex: none; text-align: center;
  align-self: center; }
#level-orb svg { width: 118px; height: 118px; display: block; }
.dial-rim { fill: none; stroke: #cfa54e; stroke-opacity: .38; stroke-width: 1.2; }
.dial-tick { stroke: #cfa54e; stroke-opacity: .35; stroke-width: 1; }
.dial-tick.major { stroke-opacity: .8; stroke-width: 1.6; }
.dial-core { fill: url(#dial-core-g); stroke: #3a2f5e; stroke-width: 1;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .55)); }
.dial-star { fill: #cabffa; opacity: .5; }
.dial-track { fill: none; stroke: rgba(36, 26, 69, .9); stroke-width: 6.5; }
.dial-arc { fill: none; stroke: url(#dial-arc-g); stroke-width: 6.5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 4px rgba(255, 209, 102, .55)); }
.dial-tip { fill: #fff6d9;
  filter: drop-shadow(0 0 5px #ffd166) drop-shadow(0 0 2px #fff); }
.dial-inner { fill: none; stroke: #cfa54e; stroke-opacity: .22; stroke-width: .8;
  stroke-dasharray: 2 3; }
.orb-mid { position: absolute; top: 0; left: 0; width: 118px; height: 118px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; }
.orb-mid small { font-size: 8.5px; letter-spacing: 2.5px; color: #8f83bd; }
.orb-mid b { font-family: Georgia, serif; font-size: 31px; line-height: 1.05;
  background: linear-gradient(180deg, #fff2c9 15%, #ffd166 55%, #b8862e 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)); }
.orb-xp { font-size: 9.5px; color: #8f83bd; margin-top: 3px; }

/* stat captions fold away behind the ? until tapped (or hovered) */
.stat-row.has-cap { cursor: pointer; flex-wrap: wrap; }
.cap-mark { font-style: normal; font-size: 8.5px; color: #6f639c;
  border: 1px solid #3a2f5e; border-radius: 50%; padding: 0 4px; margin-left: 3px; }
.stat-row .cap { display: none; width: 100%; font-size: 9.5px; color: #6f639c;
  margin-top: 2px; }
.stat-row.open .cap { display: block; }
@media (hover: hover) { .stat-row.has-cap:hover .cap { display: block; } }

/* stat allocation, living with the numbers it grows */
.build-pts { font-size: 12.5px; color: #ffd166; margin: 8px 0 4px;
  letter-spacing: .4px; }
.stat-row.build-row small { color: #6f639c; font-size: 9.5px; margin-left: 4px; }
.build-val { color: #ffd166; }
.build-add { width: 26px; height: 26px; margin-left: 8px; border-radius: 8px;
  cursor: pointer; flex: none;
  border: 1px solid #cfa54e; background: rgba(46, 30, 8, .8); color: #ffd166;
  font-size: 15px; line-height: 1; }
.stat-row span small { color: #6f639c; font-size: 9.5px; }

/* collapsed traits, a fold instead of a wall of chips */
#stats-traits-box { margin-top: 8px; border-top: 1px solid #2c2250; padding-top: 6px; }
#stats-traits-box summary { font-size: 11.5px; color: #8f83bd; cursor: pointer;
  letter-spacing: .6px; }
#stats-traits-box[open] summary { color: #cabffa; margin-bottom: 5px; }

/* the skill map: tiers joined by drawn lines, every branch visible */
.tree-pts { font-size: 11px; color: #ffd166; float: right; letter-spacing: 0; }
#tree-map { position: relative; margin: 4px 0 6px; }
#tree-lines { position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: visible; }
.tree-line-glow { fill: none; stroke: #ffd166; stroke-width: 7;
  stroke-opacity: .16; stroke-linecap: round; }
.tree-line-under { fill: none; stroke: #191033; stroke-width: 4.5;
  stroke-linecap: round; }
.tree-line { fill: none; stroke: #453a6e; stroke-width: 2;
  stroke-linecap: round; stroke-dasharray: 5 4; }
.tree-line.lit { stroke: #d9b25e; stroke-dasharray: none; }
.tree-joint { fill: #453a6e; stroke: #191033; stroke-width: 1; }
.tree-joint.lit { fill: #ffd166; }
.tmap-node { position: absolute; width: 86px; transform: translateX(-50%);
  text-align: center; cursor: pointer; padding: 5px 2px 4px; border-radius: 11px;
  border: 1.5px solid #3a2f5e; margin-top: -22px;
  background: linear-gradient(170deg, #221741 0%, #120b28 55%, #0d0720 100%);
  box-shadow: inset 0 1px 0 rgba(202, 191, 250, .1), 0 3px 8px rgba(0, 0, 0, .45); }
.tmap-node.s-known, .tmap-node.s-learned {
  background: linear-gradient(170deg, #2e2148 0%, #1a1030 55%, #140a26 100%);
  box-shadow: inset 0 1px 0 rgba(255, 209, 102, .18), 0 3px 8px rgba(0, 0, 0, .45),
    0 0 9px rgba(207, 165, 78, .18); }
.tmap-node .tmn-ico { display: block; font-size: 17px; line-height: 1.2; color: #cabffa; }
/* painted skill art on the tree, dimmed while the node is locked */
.tmap-node .tmn-art { display: block; width: 30px; height: 30px; border-radius: 8px;
  margin: 0 auto 2px; object-fit: cover; box-shadow: 0 0 0 1px rgba(0, 0, 0, .45); }
.tmap-node.s-level .tmn-art, .tmap-node.s-parent .tmn-art {
  filter: grayscale(.85) brightness(.55); }
.lo-slot .lo-art { width: 34px; height: 34px; border-radius: 8px; object-fit: cover; }
.tc-head .tn-art { width: 34px; height: 34px; border-radius: 9px; object-fit: cover;
  flex: none; }
.gear-slot .gs-art, .bag-gear .gs-art, .gear-cell .gs-art { width: 28px; height: 28px;
  border-radius: 7px; object-fit: cover; flex: none; }
.gear-slot .gs-empty { width: 22px; height: 22px; border-radius: 7px; flex: none;
  border: 1.5px dashed #3a3060; }
.tmap-node .tmn-name { display: block; font-size: 9px; color: #a99ed4;
  line-height: 1.15; letter-spacing: .2px; }
/* state is worn, not written: a corner badge at most, the rest is light */
.tmn-badge { position: absolute; top: -7px; right: -1px; font-size: 8px;
  line-height: 1; padding: 2px 5px; border-radius: 7px; color: #8f83bd;
  background: #17102e; border: 1px solid #3a2f5e; letter-spacing: .3px; }
.tmn-badge.can { color: #0d2a18; background: #7fd0a0; border-color: #a5e8c2;
  font-weight: bold; }
.tmn-badge.pad { width: 8px; height: 7px; padding: 0; top: -6px;
  background: #4d4374; border: none; border-radius: 2px; }
.tmn-badge.pad::before { content: ''; position: absolute; top: -4px; left: 1px;
  width: 4px; height: 4px; border: 1.5px solid #4d4374; border-bottom: none;
  border-radius: 4px 4px 0 0; }
.tmap-node.s-level, .tmap-node.s-parent { opacity: 1;
  filter: grayscale(.85) brightness(.62); }
.tmap-node.s-level .tmn-name, .tmap-node.s-parent .tmn-name { color: #6f639c; }
.tmap-node.s-learnable { border-color: #7fd0a0;
  animation: node-breathe 2.2s ease-in-out infinite; }
@keyframes node-breathe {
  0%, 100% { box-shadow: 0 0 5px rgba(127, 208, 160, .25); }
  50% { box-shadow: 0 0 12px rgba(127, 208, 160, .6); }
}
.tc-got { font-size: 10.5px; color: #7fd0a0; margin-top: 5px; }
.tmap-node.s-known, .tmap-node.s-learned { border-color: #cfa54e; }
.tmap-node.s-known .tmn-ico, .tmap-node.s-learned .tmn-ico { color: #ffd166; }
.tmap-node.on-bar::after { content: '●'; position: absolute; top: 1px; right: 4px;
  font-size: 7px; color: #7fd0a0; }
.tmap-node.selected { border-color: #ffd166;
  box-shadow: 0 0 9px rgba(255, 209, 102, .5); }

/* the card: one tapped skill, all its truth, and what you can do about it */
.tree-card { border: 1px solid #3a2f5e; border-radius: 12px; padding: 9px 10px;
  background: rgba(16, 10, 34, .7); margin-top: 4px; }
.tree-card .tc-head { display: flex; gap: 9px; align-items: center; }
.tree-card .tn-icon { font-size: 19px; width: 26px; text-align: center; color: #ffd166; }
.tree-card .tn-txt b { font-size: 13px; color: #e8e2f7; display: block; }
.tree-card .tn-txt small { font-size: 10px; color: #ffd166; }
.tc-desc { font-size: 11px; color: #a99ed4; line-height: 1.4; margin: 5px 0 2px; }
.tc-lock { font-size: 10.5px; color: #c98a7a; margin-top: 5px; }
.tc-slots { display: flex; align-items: center; gap: 6px; margin-top: 7px; }
.tc-slots span { font-size: 10.5px; color: #8f83bd; }
.tc-slot { width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid #3a2f5e; background: rgba(10, 6, 26, .7); color: #cabffa;
  font-size: 12px; }
.tc-slot.here { border-color: #7fd0a0; color: #7fd0a0; }
.tree-learn { padding: 6px 12px; border-radius: 8px; cursor: pointer;
  border: 1px solid #cfa54e; background: rgba(46, 30, 8, .8); color: #ffd166;
  font-size: 11.5px; margin-top: 7px; }
.tree-learn:disabled { opacity: .35; cursor: default; }
.lo-title { font-size: 12px; color: #cabffa; margin: 12px 0 6px;
  letter-spacing: .5px; }
#lo-slots { display: flex; gap: 7px; margin-bottom: 8px; }
.lo-slot { width: 44px; height: 44px; border-radius: 10px; cursor: pointer;
  border: 1.5px solid #3a2f5e; background: rgba(10, 6, 26, .7);
  display: flex; align-items: center; justify-content: center;
  font-size: 19px; color: #cabffa; position: relative; }
.lo-slot .lo-key { position: absolute; top: 1px; left: 4px; font-size: 9px;
  color: #6f639c; }
.lo-slot.picking { border-color: #ffd166;
  box-shadow: 0 0 8px rgba(255, 209, 102, .5); }
.lo-hint { font-size: 10.5px; color: #8f83bd; margin-bottom: 6px; }


/* day cycle: an 8-minute warm/cool breath over the whole scene */
#day-tint { position: fixed; inset: 0; pointer-events: none; z-index: 1;
  animation: daycycle 480s ease-in-out infinite; }
@keyframes daycycle {
  0%, 100% { background: rgba(255, 178, 96, .045); }
  50% { background: rgba(96, 128, 255, .055); }
}


/* quick-eat orb beside the combat fan */
#quick-eat { position: fixed; bottom: max(18px, env(safe-area-inset-bottom)); left: 50%;
  transform: translateX(-190px); width: 50px; height: 50px; border-radius: 50%;
  border: 2px solid #7fbf7f; cursor: pointer; z-index: 30; padding: 0;
  background: #0c1c0e; overflow: visible;
  box-shadow: 0 0 10px rgba(127, 191, 127, .4), 0 3px 10px rgba(0, 0, 0, .5);
  transition: opacity .35s ease, transform .35s ease; }
#quick-eat.resting { opacity: 0; pointer-events: none; transform: translateX(-190px) translateY(26px); }
#qe-art { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; pointer-events: none; }
#qe-count { position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px;
  border-radius: 9px; background: #2c5a2e; border: 1px solid #7fbf7f; color: #d9ffd9;
  font-size: 10px; line-height: 16px; }
#qe-heal { position: absolute; bottom: -7px; left: 50%; transform: translateX(-50%);
  font-size: 10px; color: #8fe89b; text-shadow: 0 1px 2px #000; }
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* DOCKED to the combat fan - crowning its left column, not marooned in a
     corner. It rests and wakes with the same combat state as the fan. */
  body:not(.kb) #quick-eat { bottom: calc(max(20vh, env(safe-area-inset-bottom)) + 96px);
    left: calc(50% - 157px); right: auto; transform: none; }
  body:not(.kb) #quick-eat.resting { transform: translateY(26px); }
}

/* gear delta badges */
.inv-delta { position: absolute; top: 2px; right: 3px; font-size: 10px; }
.inv-delta.up { color: #7fe87f; text-shadow: 0 0 4px rgba(90, 230, 90, .8); }
.inv-delta.down { color: #ff7f8a; text-shadow: 0 0 4px rgba(255, 90, 110, .7); }
.inv-slot { position: relative; }

/* gold pop when the count changes */
#inv-gold.pop { animation: gold-pop .45s ease; }
@keyframes gold-pop { 0% { transform: scale(1); } 30% { transform: scale(1.35); color: #fff3c9; } 100% { transform: scale(1); } }
#inv-gold { display: inline-block; }

/* low-hp vignette pulses with the heartbeat */
#low-vignette { animation: vignette-pulse 1s ease-in-out infinite !important; }
@keyframes vignette-pulse {
  0%, 100% { opacity: .75; } 45% { opacity: 1; } 60% { opacity: .8; }
}


/* cinematic zone title card */
#zone-title { position: fixed; top: 22vh; left: 50%;
  z-index: 46; text-align: center; pointer-events: none; width: max-content; max-width: 92vw; }
#zt-name { font-family: Georgia, serif; font-size: clamp(24px, 6vw, 42px);
  background: linear-gradient(180deg, #fff8e9 20%, #ffd166 55%, #b8860b 90%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 16px rgba(255, 209, 102, .35)) drop-shadow(0 2px 4px rgba(0,0,0,.8)); }
#zt-sub { font-family: Georgia, serif; font-style: italic; font-size: clamp(11px, 2.6vw, 15px);
  color: #cabffa; margin-top: 2px; text-shadow: 0 1px 3px #000; letter-spacing: 2px; }
.zt-rule { height: 1px; max-width: 60vw; margin: 6px auto;
  background: linear-gradient(90deg, transparent, rgba(255, 209, 102, .8), transparent); }
#zone-title { opacity: 0; transform: translateY(10px) translateX(-50%);
  transition: opacity .5s ease, transform .8s ease; will-change: opacity, transform; }
#zone-title.on { opacity: 1; transform: translateY(0) translateX(-50%); }
#zt-name { letter-spacing: 6px; }


/* boss cast bar: sits under the target frame, unmistakably hostile */
#boss-cast { position: absolute; top: 92px; left: 50%; transform: translateX(-50%);
  width: min(280px, 62vw); height: 20px; border-radius: 10px; z-index: 8;
  background: #1a0a08; border: 1.5px solid #ff7a3a; overflow: hidden;
  box-shadow: 0 0 14px rgba(255, 90, 42, .45), inset 0 1px 4px rgba(0,0,0,.7); }
#bc-fill { height: 100%; width: 0; border-radius: 9px;
  background: linear-gradient(90deg, #7a1d0a, #ff5a2a 60%, #ffb14a);
  /* no width transition: the fill is interpolated per frame in JS, and CSS
     easing on top is what made it stall-then-teleport between sim ticks */
  box-shadow: 0 0 12px rgba(255, 122, 58, .9); }
#bc-label { position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-family: Georgia, serif; font-size: 11.5px;
  letter-spacing: 1.5px; color: #fff; text-shadow: 0 1px 3px #000, 0 0 8px rgba(255,90,42,.9); }
@media (max-width: 700px) { #boss-cast { top: 186px; } }

/* berserk: the screen itself catches fire at the edges */
#hud.berserk::after { content: ''; position: fixed; inset: 0; pointer-events: none;
  z-index: 2; box-shadow: inset 0 0 120px rgba(255, 70, 20, .55),
  inset 0 0 40px rgba(255, 140, 60, .35);
  animation: berserk-breathe 1.1s ease-in-out infinite; }
@keyframes berserk-breathe {
  0%, 100% { opacity: .55; } 50% { opacity: 1; }
}


/* burning: standing inside the Hollow King's fire scorches the screen edges */
#hud::before { content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 3;
  opacity: var(--burn, 0); transition: opacity .12s linear;
  box-shadow: inset 0 0 90px rgba(255, 110, 30, .85),
              inset 0 0 200px rgba(255, 60, 10, .55); }


/* primary attack: the button that means you never tap the middle of the
   screen. Sits under the resting thumb, right of the skill fan. */
#attack-btn { position: fixed; bottom: max(16px, env(safe-area-inset-bottom));
  right: 18px; width: 84px; height: 84px; border-radius: 50%; z-index: 31;
  cursor: pointer; padding: 0; border: none; background: transparent;
  filter: drop-shadow(0 0 14px rgba(255, 132, 60, .5)) drop-shadow(0 4px 10px rgba(0,0,0,.65));
  transition: opacity .3s ease, transform .12s ease; }
#atk-icon { display: block; width: 100%; height: 100%;
  background: url('../assets/ui/attack_btn.png') center / contain no-repeat;
  font-size: 0; }
#attack-btn:active { transform: scale(.9); filter: brightness(1.45); }
#attack-btn.resting { opacity: 0; pointer-events: none; transform: translateY(24px); }
/* ready-to-swing pulse so the rhythm is visible without watching numbers */
#atk-ring { position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid rgba(255, 205, 140, .95); pointer-events: none; opacity: 0;
  box-shadow: 0 0 12px rgba(255, 170, 90, .8); }
#attack-btn.ready #atk-ring { animation: atk-ready 1.1s ease-out infinite; }
@keyframes atk-ready {
  0% { transform: scale(.92); opacity: .9; }
  100% { transform: scale(1.18); opacity: 0; }
}
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) #attack-btn { width: 82px; height: 82px; bottom: 74px; right: 14px; }
}


/* --- item rarity: a legendary should never look like a snack --- */
.inv-slot[data-rarity], .shop-row[data-rarity] { --rar: #6f7ba0; --rar-glow: rgba(111,123,160,.25); }
.inv-slot[data-rarity="rare"], .shop-row[data-rarity="rare"] { --rar: #7fd0ff; --rar-glow: rgba(127,208,255,.4); }
.inv-slot[data-rarity="epic"], .shop-row[data-rarity="epic"] { --rar: #c9a5ff; --rar-glow: rgba(201,165,255,.45); }
.inv-slot[data-rarity="legendary"], .shop-row[data-rarity="legendary"] { --rar: #ffd166; --rar-glow: rgba(255,209,102,.55); }

.inv-slot[data-rarity] { border-color: var(--rar);
  background: radial-gradient(circle at 50% 118%, var(--rar-glow), #17102e 72%);
  box-shadow: 0 0 8px var(--rar-glow); }
.inv-slot[data-rarity="legendary"] { animation: rar-pulse 2.6s ease-in-out infinite; }
@keyframes rar-pulse {
  0%, 100% { box-shadow: 0 0 8px rgba(255, 209, 102, .45); }
  50% { box-shadow: 0 0 18px rgba(255, 209, 102, .85); }
}
.shop-row[data-rarity] .shop-name { color: var(--rar); }
.shop-row[data-rarity] { border-left: 2px solid var(--rar); padding-left: 6px; }

/* empty pack slots read as forged sockets rather than dead boxes */
.inv-slot:not([data-rarity]) {
  background:
    radial-gradient(circle at 50% 42%, rgba(106, 90, 205, .10), transparent 62%),
    linear-gradient(160deg, #150e2a, #0d0819);
  border-color: #241a45;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, .65), inset 0 -1px 0 rgba(120, 100, 200, .12); }
.inv-slot:not([data-rarity])::after {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: rgba(138, 108, 255, .16);
  box-shadow: 0 0 6px rgba(138, 108, 255, .12); }

/* empty equipment sockets show the shape of what belongs there */
.equip-slot:not(.filled) .eq-icon, .stats-eq-slot:not(.filled) .seq-ico { opacity: .28; }


/* --- quest tracker: a sealed scroll, not a grey box --- */
#quest-tracker { display: flex; align-items: flex-start; gap: 11px;
  overflow: hidden;   /* position stays absolute: it already anchors the spine */
  background:
    linear-gradient(180deg, rgba(30, 20, 10, .55), rgba(10, 6, 26, .82)) padding-box,
    rgba(10, 6, 26, .82);
  border: 1px solid #6a5730 !important;
  box-shadow: 0 4px 16px rgba(0,0,0,.5), inset 0 1px 0 rgba(255, 214, 130, .12); }
#qt-seal { color: #cfa54e; font-size: 13px; line-height: 1.1; margin-top: 2px;
  filter: drop-shadow(0 0 5px rgba(212, 165, 78, .55)); }
#qt-body { min-width: 0; flex: 1; }
#qt-title { color: #8a7440 !important; }
/* long objectives clip rather than stretching the chip past the minimap */
#qt-name, #qt-progress { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a thin thread of progress under the objective */
#qt-bar { height: 3px; margin-top: 5px; border-radius: 2px;
  background: rgba(90, 74, 40, .5); overflow: hidden; }
#qt-fill { height: 100%; width: 0; border-radius: 2px;
  background: linear-gradient(90deg, #8a6410, #ffd166);
  box-shadow: 0 0 6px rgba(255, 209, 102, .6); transition: width .35s ease; }
/* objective complete: the seal lights and the whole chip breathes */
#quest-tracker.turnin { animation: qt-turnin 2.2s ease-in-out infinite; }
#quest-tracker.turnin #qt-seal { color: #fff3c9; }
#quest-tracker.turnin #qt-progress { color: #ffd166; }
@keyframes qt-turnin {
  0%, 100% { box-shadow: 0 4px 16px rgba(0,0,0,.5), 0 0 0 rgba(255, 209, 102, 0); }
  50% { box-shadow: 0 4px 16px rgba(0,0,0,.5), 0 0 18px rgba(255, 209, 102, .55); }
}
#quest-tracker.ready #qt-seal { color: #9ad8ff; }
#quest-tracker.ready #qt-bar { display: none; }

/* --- map dots: shape carries meaning, not just colour --- */
.map-dot.you { border-radius: 50%; }
.map-dot.npc { border-radius: 2px; transform: translate(-50%, -50%) rotate(45deg);
  width: 7px !important; height: 7px !important; }   /* folk: a diamond */
.map-dot.quest { border-radius: 2px !important;
  transform: translate(-50%, -50%) rotate(45deg);
  width: 9px !important; height: 9px !important;
  animation: dot-quest 1.6s ease-in-out infinite; }  /* business waiting: it pulses */
@keyframes dot-quest {
  0%, 100% { box-shadow: 0 0 5px #ffd166; }
  50% { box-shadow: 0 0 13px #ffd166, 0 0 22px rgba(255, 209, 102, .6); }
}
.map-dot.portal { border-radius: 50% !important; width: 9px !important; height: 9px !important;
  background: transparent !important;
  border: 2px solid #7fd0ff;                          /* gates: a ring you step through */
  box-shadow: 0 0 7px #7fd0ff, inset 0 0 5px rgba(127, 208, 255, .8) !important; }
.map-dot.mob { border-radius: 1px; transform: translate(-50%, -50%) rotate(45deg); }
#map-legend .dot.npc { transform: rotate(45deg); border-radius: 2px; }
#map-legend .dot.portal { background: transparent; border: 2px solid #7fd0ff; }
.map-dot.small.quest { width: 7px !important; height: 7px !important; }
/* NOTE: the .quest rules above are !important, so the quest PIN deliberately
   does not wear that class - see the add() call in hud.js. */
.map-dot.small.portal { width: 7px !important; height: 7px !important; border-width: 1.5px; }


/* --- character sheet polish --- */
/* name over its rarity line, close button held to the right — the two never
   compete for the same row, so neither phrase breaks mid-word */
#stats-header { border-bottom: 1px solid rgba(207, 165, 78, .28); padding-bottom: 8px;
  display: grid; grid-template-columns: 1fr auto; align-items: start;
  column-gap: 8px; row-gap: 1px; }
#stats-name { white-space: nowrap; grid-column: 1; }
#stats-rarity { grid-column: 1; margin-left: 0; line-height: 1.35; }
#stats-close { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
#stats-name { font-size: 15px;
  background: linear-gradient(180deg, #fff8e9 25%, #ffd166 70%, #c8951f 95%);
  -webkit-background-clip: text; background-clip: text; color: transparent; }
/* the portrait sprite gets a lit plinth so it reads as a display, not a cutout */
#stats-body { position: relative; padding: 10px 4px 12px; }
#stats-body::before { content: ''; position: absolute; left: 6px; bottom: 6px;
  width: 118px; height: 26px; border-radius: 50%;
  background: radial-gradient(ellipse, rgba(138, 108, 255, .35), transparent 70%);
  filter: blur(1px); }
#stats-sprite { position: relative; }
/* stat rows alternate and gain a gold key column */
.stat-row { padding: 4px 8px; border-radius: 6px; }
.stat-row:nth-child(odd) { background: rgba(138, 108, 255, .07); }
.stat-row b { color: #ffd166; }
#stats-grid { border-top: 1px solid rgba(207, 165, 78, .28); padding-top: 9px; }
/* the ability compendium reads as a list of cards */
.ab-row { padding: 7px 8px; border-radius: 10px; background: rgba(23, 16, 46, .55);
  border: 1px solid #2c2250; margin-bottom: 6px; }
.ab-row:not(.locked) { border-color: rgba(207, 165, 78, .35); }
.ab-head { color: #cfa54e !important; border-top-color: rgba(207, 165, 78, .28) !important; }
/* traits become chips with a little more presence */
.trait-chip { border-color: #4b3d7a; background: rgba(34, 23, 65, .8); }
#stats-traits { border-top-color: rgba(207, 165, 78, .28) !important; }

/* --- painted UI icons (no emoji anywhere) --- */
#bag-btn, #settings-btn { display: flex; align-items: center; justify-content: center; }
.btn-ico { width: 21px; height: 21px; object-fit: contain; display: block;
  filter: drop-shadow(0 0 4px rgba(255, 209, 102, .35)); }
#settings-btn .btn-ico { transition: transform .5s ease; }
#settings-btn:hover .btn-ico { transform: rotate(60deg); }
.set-ico { width: 22px; height: 22px; object-fit: contain; flex-shrink: 0;
  filter: drop-shadow(0 0 4px rgba(255, 209, 102, .3)); }
.set-row.off .set-ico { filter: grayscale(1); }
.eq-icon, .seq-ico { width: 20px; height: 20px; object-fit: contain; flex-shrink: 0; }
.seq-ico { width: 22px; height: 22px; }

/* ---------- desktop / wide-screen layout ----------
   The HUD was tuned on a 390px phone; on a wide desktop the same rules leave
   panels running the full window height and the cinematic band scaled to a
   fraction of a 1700px-wide screen. */
@media (min-width: 900px) {
  /* quest log panel: a readable card, not a full-height column */
  #quest-log { width: 340px; max-height: min(52vh, 520px); }
  #ql-list { max-height: none; }

  /* the always-on quest tracker was stretched between two pixel offsets, so
     on a 1700px screen it ran nearly the full width. Cap it. */
  #quest-tracker { right: auto; width: 340px; }

  /* cinematic: the portrait is anchored to the right and the text column has
     room, instead of the band being sized in vw on a very wide viewport */
  #cine-band { min-height: 200px; padding-bottom: 26px; }
  #cine-text { max-width: 780px; font-size: 19px; min-height: 3.2em; }
  #cine-name::after { max-width: 420px; }
  #cine-portrait { height: 48vh; max-width: 38vw; right: 2vw; bottom: 0; }
  /* the boss bust is wide; keep it FULLY on screen with object-fit contain so
     nothing is cut at the right edge or the floor */
  #cine-portrait.boss { height: 94vh; max-width: 54vw; right: 1vw; bottom: 0;
    aspect-ratio: auto; object-fit: contain; object-position: bottom right; }

  /* The log sits bottom-LEFT on a desktop. Centre-bottom is where it goes on
     a phone because that is the only clear lane; on a wide screen it lands in
     the middle of the play space, over the character you are looking at. */
  #log { left: auto; right: 64px; bottom: 96px; transform: none; text-align: right;
    width: min(430px, 32vw); }

  /* Food sat at translateX(-190px) from centre, which on the desktop row
     layout is directly under the leftmost skill orb - the hotbar covered it.
     Park it clear of the row's left end and label its key. */
  #quick-eat { transform: translateX(-268px); }
  #quick-eat.resting { transform: translateX(-268px) translateY(26px); }
  #qe-key { position: absolute; bottom: -16px; left: 50%; transform: translateX(-50%);
    font-size: 9px; letter-spacing: .08em; color: #b7e8bd;
    text-shadow: 0 1px 2px #000; pointer-events: none; }

  /* the other panels get sane caps too */
  #inv-panel, #bank-panel, #shop-panel { max-height: min(64vh, 640px); }
  /* never taller than the window leaves it - 780px on a laptop overshot the
     app's short embedded viewport and the tree spilled over the chat bar */
  #stats-panel { max-height: min(calc(100dvh - 148px), 780px); }

  /* Settings is the one panel reached by a KEY as well as a button, and Escape
     has no on-screen anchor to fly out of. On a phone the panel hanging off
     the cogwheel is fine because the screen is a hand's width; on a desktop
     the eye is at the middle of the window, so that is where it opens - with a
     dimmed backdrop, since it is the one panel that is modal in feel. */
  #settings-panel { top: 50%; left: 50%; right: auto;
    width: 300px; transform: translate(-50%, -50%);
    transform-origin: center; padding: 14px 16px; }
  #settings-panel.closed { transform: translate(-50%, -50%) scale(.88); }
  #settings-panel::before { content: ''; position: fixed; inset: 0; z-index: -1;
    background: rgba(4, 2, 12, .45); }
  #settings-panel.closed::before { display: none; }
  #set-title { font-size: 13px; margin-bottom: 12px; }
  .set-row { font-size: 14px; padding: 11px 12px; }

  /* the portrait was sized for a 390px phone and reads as a thumbnail on a
     1700px screen; the name and bars beside it already have the room */
  #portrait { width: 92px; height: 92px; }
  /* ...and the tracker's left offset was measured against the 66px portrait,
     so it now starts underneath it. The panel ends at x=112 and its xp ring
     overhangs to 118, hence 132. */
  #quest-tracker { left: 132px; }
}

/* --- multiplayer chat ---------------------------------------------------
   Bottom-LEFT, above the log, and deliberately narrow: chat must never grow
   into the space the hotbar and the character occupy, and on a phone the
   bottom-centre is where the thumb lives. */
#chat { position: fixed; left: 14px; bottom: 92px; width: min(340px, 46vw);
  z-index: 7; display: flex; flex-direction: column; gap: 6px;
  font-family: Georgia, serif; }
#chat[hidden] { display: none; }
#chat-log { display: flex; flex-direction: column; gap: 3px;
  max-height: 26vh; overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; touch-action: pan-y;
  scrollbar-width: thin; scrollbar-color: #6b58b0 transparent; }
/* The scrollbar was thin, default-coloured and drawn over a dark game, so it
   was invisible - a log that scrolls but shows no sign of it reads as a log
   that does not scroll. */
#chat-log::-webkit-scrollbar { width: 8px; }
#chat-log::-webkit-scrollbar-thumb { background: #6b58b0; border-radius: 4px; }
#chat-log::-webkit-scrollbar-track { background: rgba(8, 5, 20, .5); }
/* HISTORY: the log shows about six lines and keeps three hundred. This is the
   only way to reach the rest of them. */
#chat-hist { align-self: flex-start; border: 1px solid #4b3d7a;
  background: rgba(8, 5, 20, .82); color: #9d86ff; border-radius: 8px;
  padding: 2px 9px; font-family: Georgia, serif; font-size: 11px;
  cursor: pointer; opacity: .6; }
#chat-hist:hover { opacity: 1; }
#chat-hist.unread { opacity: 1; border-color: #8a6cff; color: #ffd166; }
/* Read mode. Over a 3D world, unbacked text is unreadable at this height, so
   the tall log takes a panel of its own. */
#chat.tall #chat-log { max-height: 58vh; background: rgba(8, 5, 20, .88);
  border: 1px solid #4b3d7a; border-radius: 10px; padding: 8px 10px; }
#chat.tall #chat-hist { opacity: 1; }
.chat-line { font-size: 11.5px; line-height: 1.4; color: #e8e2f7;
  text-shadow: 0 1px 3px #000, 0 0 8px rgba(0,0,0,.8); overflow-wrap: break-word; }
.chat-line .zone { color: #6f639c; font-size: 11px; }
.chat-line .who { color: #ffd166; }
.chat-line.party { color: #8fd8ff; }
.chat-line .party-tag { color: #5db8e8; font-size: 11px; }
.chat-line.sys { color: #8f83bd; font-style: italic; }
#chat-form { display: flex; }
#chat-input { flex: 1; min-width: 0; background: rgba(8, 5, 20, .82);
  border: 1px solid #4b3d7a; border-radius: 9px; padding: 7px 10px;
  color: #e8e2f7; font-family: Georgia, serif; font-size: 13px; }
#chat-input:focus { outline: none; border-color: #8a6cff; }
/* the input only takes the keyboard while it has focus - otherwise WASD would
   type instead of walking */
#chat-input:not(:focus) { opacity: .55; }
/* a visible send key - the phone keyboard's tick is not reliably a submit,
   and on desktop it doubles as the discoverable "this box sends" affordance */
#chat-send { margin-left: 5px; width: 36px; flex: none; border-radius: 9px;
  border: 1px solid #4b3d7a; background: rgba(8, 5, 20, .82); color: #9d86ff;
  font-size: 14px; cursor: pointer; }
#chat-send:active { background: rgba(138, 108, 255, .25); }

#net-status { position: fixed; top: 8px; left: 50%; transform: translateX(-50%);
  z-index: 9; font-family: Georgia, serif; font-size: 11px; letter-spacing: 1px;
  color: #b7aede; background: rgba(8, 5, 20, .8); border: 1px solid #4b3d7a;
  border-radius: 999px; padding: 4px 12px; pointer-events: none; }
#net-status[hidden] { display: none; }
/* The idle notice is the one the player is meant to ACT on, so it takes
   pointer events back (the banner is inert otherwise, to stay out of the way
   of the world underneath it) and looks like something you can press. */
#net-status.clickable { pointer-events: auto; cursor: pointer;
  color: #ffe9a8; border-color: #8a6f2e; background: rgba(28, 18, 6, .92); }
#net-status.clickable:hover { background: rgba(48, 32, 10, .95); }

@media (max-width: 540px) {
  #chat-log { max-height: 18vh; }
}

/* --- mobile chat: out of the way until asked for ------------------------
   The input spanned the bottom of a phone screen ON TOP of the game, and
   tapping anywhere near it dragged up the keyboard. On narrow screens the
   box now lives behind a toggle: the log shows the latest lines faintly,
   the input only exists while the player wants it. */
#chat-toggle { position: fixed; left: 12px; bottom: 96px; z-index: 8;
  width: 40px; height: 40px; border-radius: 50%; font-size: 17px;
  background: rgba(16, 10, 34, .92); border: 1.5px solid #4b3d7a;
  color: #e8e2f7; cursor: pointer; }
#chat-toggle.unread { border-color: #ffd166;
  box-shadow: 0 0 10px rgba(212, 169, 79, .45); }
#chat-toggle[hidden] { display: none; }

@media (max-width: 700px) {
  /* THE PHONE'S BOTTOM IS LANES, and chat gets the one above the skill fan.
     From the floor up: battle log (0-60px), the thumb row - chat toggle
     left, food centre, attack right (74-156px), the skill fan (20vh to
     20vh+108px), then chat. At bottom:148px the chat box sat INSIDE the fan
     and the toggle sat on the input (Alan: "the chat box etc all seem to be
     in odd spots"). A keyboard user on a narrow window has the plain hotbar
     row instead of the fan, so chat drops back to the desktop height. */
  /* half the screen, hugging the left: full width ran under the jump button
     and the attack column on the right (Alan; 75% was still "too long") */
  /* THE CHAT NEVER MOVES ON ITS OWN. It used to lift above the fan when a
     fight started (Alan: "annoying that it keeps moving"). Now bubble and
     box are one draggable unit: main.js sets --chat-x / --chat-b on <body>
     when the bubble is dragged, the box rides 52px above the bubble, and
     the spot is remembered. Nothing else touches its position. */
  /* v2.91.10-11 (Alan): the chat lives UNDER the walk ring - bubble in the
     bottom-left corner, the box just above it (ring starts at 140px, the
     box ends under that). Drag the bubble, the box follows. */
  #chat { left: var(--chat-x, 12px); right: auto; width: 56vw;
    bottom: calc(var(--chat-b, 24px) + 48px); }
  body.kb #chat { left: 14px; bottom: 92px; }
  #chat-toggle { left: var(--chat-x, 12px); bottom: var(--chat-b, 24px); }
  /* HISTORY RIDES BESIDE THE BUBBLE ON A PHONE. In the box it sat over the
     joystick (Alan: "its too close to the joystick and it overlaps"), and the
     box is draggable, so it has to travel with the bubble rather than with
     the log. Same lane, same size, one bubble to the right. */
  #chat-hist { position: fixed; z-index: 8; pointer-events: auto;
    left: calc(var(--chat-x, 12px) + 48px); bottom: var(--chat-b, 24px);
    width: 40px; height: 40px; border-radius: 50%; padding: 0;
    line-height: 1; opacity: 1;
    /* OPAQUE. At .92 the world showed through the glyph's own bubble, so the
       icon read as sitting on a hole rather than on a button (Alan: "does not
       cover the button behind it so u can still see a bit of it"). */
    background: #100a22; border: 1.5px solid #4b3d7a;
    color: #e8e2f7;
    /* the word does not fit a 40px bubble, and the label is set by script:
       hide the text and draw a glyph for whichever state it is in. FLEX, not
       a bare inline ::after - an inline box inside a font-size:0 button sits
       on the baseline near the bottom of the circle, which is what made the
       glyph look half-off its own button. */
    display: flex; align-items: center; justify-content: center;
    font-size: 0; color: transparent; }
  /* the literal characters, NOT CSS unicode escapes: `content: 'F553'`
     needs a terminating space or the parser eats the next token, and mine
     rendered as the text "1 F553" */
  #chat-hist::after { font-size: 17px; line-height: 1; color: #e8e2f7;
    content: '◴'; }
  #chat.tall #chat-hist::after { content: '✕'; font-size: 15px; }
  /* the collapsed box swallows nothing, but its History bubble must still
     take a tap - it is the only way back into the log */
  #chat.collapsed { pointer-events: none; }
  #chat.collapsed #chat-hist { pointer-events: auto; }
  /* THREE LINES WHEN IT IS NOT BEING READ (Alan: "when the history isnt
     active maybe just show like 3 lines? otherwise it blocks the joystick").
     A px cap, not vh: three lines is three lines on every phone, and 16vh on
     a tall screen was seven of them sitting across the stick. 3 x 16px of
     line plus the two 2px gaps. The cap itself is at the END of this file,
     where it can outrank the collapsed rules below. */
  .chat-line { font-size: 11px; }
  /* the battle log stays at the very bottom (Alan), clear of the bubble */
  body:not(.kb) #log { left: 60px; right: 60px; width: auto; transform: none;
    bottom: 20px; text-align: center; }
  #chat-log { max-height: 16vh; }
  /* collapsed: a whisper of recent chat, no input, taps fall through */
  #chat.collapsed { pointer-events: none; }
  #chat.collapsed #chat-form { display: none; }
  #chat.collapsed #chat-log { max-height: 12vh; opacity: .55; }
}

/* --- party ---------------------------------------------------------------
   Under the portrait, where the eye already goes for vital signs. Rows are
   name plus a thin hp bar; a dead member's bar sits empty, which says it. */
#party-panel { position: fixed; left: 14px; top: 132px; z-index: 6;
  width: 168px; background: rgba(10, 6, 26, .82); border: 1px solid #4b3d7a;
  border-radius: 12px; padding: 8px 10px; font-family: Georgia, serif; }
#party-panel[hidden] { display: none; }
.party-row { margin-bottom: 6px; }
.party-name { font-size: 11.5px; color: #e8e2f7; letter-spacing: .4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.party-name .pzone { color: #6f639c; font-size: 10px; }
.party-hp { height: 4px; border-radius: 2px; background: #170d14;
  border: 1px solid #3a2530; overflow: hidden; margin-top: 2px; }
/* healthy green, worried amber under 60, screaming red under 30 - the class
   is set alongside the width wherever the bar is written */
.party-hp i { display: block; height: 100%;
  background: linear-gradient(90deg, #6ef29b, #2f9e57);
  transition: width .3s ease, background .3s ease; }
.party-hp i.mid { background: linear-gradient(90deg, #ffd166, #d99b2b); }
.party-hp i.low { background: linear-gradient(90deg, #ff5d73, #d92645); }
#party-leave { width: 100%; margin-top: 2px; padding: 4px; font-size: 10.5px;
  background: none; border: 1px solid #3a2f5e; border-radius: 8px;
  color: #8f83bd; cursor: pointer; font-family: Georgia, serif; }
#party-leave:hover { border-color: #a4614f; color: #d79b8a; }

/* the player menu: same gold-on-midnight kit as the invite banner */
#player-menu { position: fixed; z-index: 10; min-width: 150px;
  background: rgba(16, 10, 34, .96); border: 1.5px solid #cfa54e;
  border-radius: 12px; padding: 5px; font-family: Georgia, serif;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .55), 0 0 14px rgba(212, 169, 79, .25); }
#player-menu[hidden] { display: none; }
.pm-name { font-size: 11.5px; color: #ffd166; letter-spacing: .6px;
  padding: 5px 10px 5px; border-bottom: 1px solid rgba(207, 165, 78, .25);
  margin-bottom: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-item { display: block; width: 100%; text-align: left; background: none;
  border: 0; color: #e8e2f7; font-family: Georgia, serif; font-size: 13px;
  padding: 8px 10px; border-radius: 8px; cursor: pointer; }
.pm-item:hover { background: rgba(207, 165, 78, .16); }
.pm-item.off { color: #6f639c; cursor: default; }

#party-invite { position: fixed; top: 54px; left: 50%; transform: translateX(-50%);
  z-index: 9; display: flex; gap: 10px; align-items: center;
  background: rgba(16, 10, 34, .95); border: 1.5px solid #cfa54e;
  border-radius: 12px; padding: 9px 14px; font-family: Georgia, serif;
  box-shadow: 0 0 18px rgba(212, 169, 79, .35); }
#party-invite[hidden] { display: none; }
#pi-text { font-size: 13px; color: #e8e2f7; }
#party-invite button { padding: 5px 14px; border-radius: 8px; cursor: pointer;
  font-family: Georgia, serif; font-size: 12.5px; }
#pi-yes { background: linear-gradient(180deg, rgba(46,30,8,.9), rgba(24,14,4,.95));
  border: 1px solid #cfa54e; color: #ffd166; }
#pi-no { background: none; border: 1px solid #3a2f5e; color: #8f83bd; }

/* the duel challenge banner: same kit as the party invite above, offset a
   little lower so the two can never overlap if both happen to be up */
#duel-invite { position: fixed; top: 94px; left: 50%; transform: translateX(-50%);
  z-index: 9; display: flex; gap: 10px; align-items: center;
  background: rgba(16, 10, 34, .95); border: 1.5px solid #a04858;
  border-radius: 12px; padding: 9px 14px; font-family: Georgia, serif;
  box-shadow: 0 0 18px rgba(160, 72, 88, .35); }
#duel-invite[hidden] { display: none; }
#di-text { font-size: 13px; color: #e8e2f7; }
#duel-invite button { padding: 5px 14px; border-radius: 8px; cursor: pointer;
  font-family: Georgia, serif; font-size: 12.5px; }
#di-yes { background: linear-gradient(180deg, rgba(46,12,16,.9), rgba(24,6,8,.95));
  border: 1px solid #a04858; color: #ff9daa; }
#di-no { background: none; border: 1px solid #3a2f5e; color: #8f83bd; }

@media (max-width: 700px) {
  /* under the portrait like on desktop, but SLIM: one line per member -
     name and a short hp bar side by side, zone dropped, leave key small */
  #party-panel { top: 96px; left: 8px; right: auto; width: 118px; padding: 5px 7px; }
  .party-row { display: flex; align-items: center; gap: 5px; margin-bottom: 3px; }
  .party-name { flex: 1; font-size: 10px; letter-spacing: .2px; }
  .party-name .pzone { display: none; }
  .party-hp { flex: none; width: 34px; margin-top: 0; }
  #party-leave { margin-top: 1px; padding: 2px 4px; font-size: 9px; border-radius: 6px; }
}

/* An open NPC dialogue owns the bottom of the screen; the chat column under
   it read as clutter, so it steps aside until the conversation closes. */
body:has(#dialogue:not([hidden])) #chat,
body:has(#dialogue:not([hidden])) #chat-toggle { display: none; }
/* the character sheet is tall; chat stamping over its lower rows reads as
   broken at ANY viewport, not just phones - it steps aside while it's open */
body:has(#stats-panel:not(.closed)) #chat,
body:has(#stats-panel:not(.closed)) #chat-toggle { display: none; }

@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* an open panel owns a phone's screen - the combat fan and attack key
     were stamping over the character sheet's lower rows */
  body:has(#stats-panel:not(.closed)) #hotbar,
  body:has(#stats-panel:not(.closed)) #attack-btn,
  body:has(#stats-panel:not(.closed)) #chat,
  body:has(#stats-panel:not(.closed)) #chat-toggle,
  body:has(#stats-panel:not(.closed)) #quick-eat,
  body:has(#inv-panel:not(.closed)) #hotbar,
  body:has(#inv-panel:not(.closed)) #attack-btn,
  body:has(#inv-panel:not(.closed)) #quick-eat,
  body:has(#quest-log:not(.closed)) #hotbar,
  body:has(#quest-log:not(.closed)) #attack-btn,
  body:not(.kb) body:has(#quest-log:not(.closed)) #quick-eat { display: none; }
}

.bv-split { font-size: 9px; color: #6f639c; font-weight: normal; }

/* the world map: tabs, the continent, and its waypoint pins */
#map-tabs { display: flex; gap: 6px; margin: 6px 0; }
.mtab { flex: 1; padding: 5px 0; border-radius: 8px; font-family: Georgia, serif;
  font-size: 11.5px; letter-spacing: 1px; cursor: pointer; color: #8f83bd;
  background: #17102e; border: 1px solid #2c2250; }
.mtab.active { color: #ffd166; border-color: #cfa54e; background: #221741; }
#world-view { position: relative; border-radius: 12px; overflow: hidden;
  border: 1px solid #3a2f5e; }
#world-img { display: block; width: 100%; height: auto; }
.w-node { position: absolute; transform: translate(-50%, -50%); text-align: center;
  cursor: default; }
.w-node i { display: block; width: 13px; height: 13px; border-radius: 50%;
  margin: 0 auto; border: 2px solid #6f639c; background: rgba(10, 6, 26, .8); }
.w-node span { display: block; font-size: 9.5px; color: #cabffa; margin-top: 2px;
  text-shadow: 0 1px 3px #000, 0 0 6px #000; letter-spacing: .4px; white-space: nowrap; }
.w-node.open { cursor: pointer; }
.w-node.open i { border-color: #ffd166; background: rgba(60, 44, 10, .9);
  box-shadow: 0 0 8px rgba(255, 209, 102, .6); }
.w-node.open:hover i { background: #ffd166; }
.w-node.here i { border-color: #8fe6a1; background: rgba(20, 50, 26, .9);
  animation: here-pulse 1.6s ease-in-out infinite; }
@keyframes here-pulse {
  0%, 100% { box-shadow: 0 0 5px rgba(143, 230, 161, .5); }
  50% { box-shadow: 0 0 13px rgba(143, 230, 161, .95); }
}
.w-node.locked i { opacity: .55; }
.w-node.locked span { color: #6f639c; }
#map-legend[hidden], #map-view[hidden] { display: none; }


/* --- draggable HUD panels (WoW-style) -----------------------------------
   Chat and the battle log are the two panels whose ideal spot depends on
   the screen and the player, not on any breakpoint I can guess (Alan: they
   overlap at some widths). Each carries a grip; drag it and the position is
   remembered per panel. Settings has a reset row for when it goes wrong. */
.pan-grip { position: absolute; top: -2px; right: 2px; z-index: 3;
  font-size: 13px; line-height: 1; padding: 2px 4px; border-radius: 5px;
  color: #6f639c; background: rgba(10, 6, 26, .55); cursor: grab;
  opacity: 0; transition: opacity .15s ease; pointer-events: auto;
  touch-action: none; user-select: none; }
#log { pointer-events: none; }          /* text stays click-through... */
#log .pan-grip { pointer-events: auto; } /* ...the grip does not */
#log:hover .pan-grip, #chat:hover .pan-grip, .pan-grip.dragging { opacity: 1; }
.pan-grip.dragging { cursor: grabbing; color: #ffd166; }
/* a dragged panel is positioned from the top-left and must drop its
   centring transform, or it jumps by half its width on the first move */
#log.pan-moved, #chat.pan-moved { transform: none; right: auto; bottom: auto; }
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) .pan-grip { font-size: 15px; padding: 4px 6px; }
}
/* On a phone there is no hover, so the grips sat there permanently - a
   drag handle advertising itself over the battle log at all times (Alan).
   Show them only while the chat panel is open, which is when someone is
   actually arranging their panels; they vanish again when it closes. */
@media ((hover: none) and (pointer: coarse)) {
  /* The hover rule above wins on specificity (0,1,1 beats 0,1,0), and a TAP
     on a phone leaves :hover stuck on - so the grip lit up and stayed lit,
     which is why hiding `.pan-grip` alone did nothing (Alan saw it again).
     Kill the hover rules at equal specificity here, then re-show only while
     the chat panel is actually open. */
  #log:hover .pan-grip, #chat:hover .pan-grip { opacity: 0; pointer-events: none; }
  .pan-grip { opacity: 0; pointer-events: none; }
  body:has(#chat:not([hidden])) .pan-grip,
  body:has(#chat:not([hidden])) #log:hover .pan-grip,
  body:has(#chat:not([hidden])) #chat:hover .pan-grip {
    opacity: .55; pointer-events: auto; }
  /* The grip sits INSIDE #log, which is overflow:hidden - so it cannot be
     lifted above the box without being clipped. Instead the text gets out of
     its way, and only while the grip is actually on screen: the last word of
     "You are in the world." was disappearing under it (Alan). Full width
     returns the moment chat closes and the grip goes. */
  body:has(#chat:not([hidden])) #log { padding-right: 32px; box-sizing: border-box; }
  .pan-grip.dragging { opacity: 1; pointer-events: auto; }
}

/* Odd Jobs: the side-quest block. Diamond rather than a numeral because it is
   NOT act seven - it sits beside the chain, not after it. Violet keeps it
   visually separate from the gold of the main story. */
.ql-act.ql-side .ql-act-n { color: #d8a7ff; }
.ql-act.ql-side .ql-act-t { color: #e8dcff; }
.ql-row.side-locked .ql-ico { color: #8a7aa8; }


/* a primed chain: the orb holds a bonus blow and says so */
.hb-slot.chain-primed { border-color: #7fe0d4;
  animation: chain-hum .8s ease-in-out infinite; }
@keyframes chain-hum {
  0%, 100% { box-shadow: 0 0 8px rgba(127, 224, 212, .5); }
  50% { box-shadow: 0 0 20px rgba(127, 224, 212, .95); }
}


/* --- LIMIT BREAK - a blade of light on the right edge -------------------- */
/* No box, no border: a slim skewed track that FILLS with light and ignites
   when ready. It used to lie centred above the hotbar, but the open-world
   camera stands the character at bottom-centre of the frame and the bar sat
   straight across him (Alan: "in the way"). Touch already solved this years
   of bug reports ago: VERTICAL on the right edge, owning empty screen in
   every zone. Desktop now uses the same answer - rotate(-90deg) turns the
   track's x-axis into "up", so the width-percent fill from hud.js climbs
   the bar unchanged. */
#limit-btn { position: fixed; width: 220px; height: 14px;
  left: auto; right: -78px;       /* centre lands 32px in from the right edge */
  top: 44%; bottom: auto;
  transform: rotate(-90deg) skewX(-14deg);
  padding: 0; border: 0; background: none;
  cursor: pointer; z-index: 22; transition: opacity .3s, transform .3s; }
#limit-btn.resting { opacity: 0; pointer-events: none;
  transform: rotate(-90deg) skewX(-14deg) translateY(14px); }
#limit-track { position: absolute; inset: 0; background: rgba(12, 8, 28, .72);
  box-shadow: inset 0 0 6px rgba(0, 0, 0, .8), 0 0 8px rgba(90, 70, 160, .35);
  overflow: hidden; }
#limit-fill { position: absolute; top: 0; bottom: 0; left: 0; width: 0%;
  background: linear-gradient(90deg, #3d2a6e, #7a5cff 70%, #b9a4ff);
  transition: width .25s ease-out; }
#limit-tip { position: absolute; top: -3px; bottom: -3px; width: 10px;
  transform: translateX(-5px); background: radial-gradient(closest-side, #fff, transparent);
  opacity: .0; transition: left .25s ease-out, opacity .3s; pointer-events: none; }
#limit-btn.ready #limit-fill { background: linear-gradient(90deg, #b3541e, #ff8547 55%, #ffd166); }
#limit-btn.ready #limit-track { animation: limit-blaze .9s ease-in-out infinite; }
@keyframes limit-blaze {
  0%, 100% { box-shadow: inset 0 0 6px rgba(0, 0, 0, .8), 0 0 10px rgba(255, 160, 70, .55); }
  50% { box-shadow: inset 0 0 4px rgba(60, 20, 0, .6), 0 0 26px rgba(255, 209, 102, .95); }
}
#limit-label { position: absolute; left: 50%; top: -16px;
  transform: translateX(-50%) skewX(14deg);
  font: 700 10px/1 'Cinzel', serif;
  letter-spacing: .22em; color: #ffd166; opacity: 0; transition: opacity .3s;
  text-shadow: 0 0 8px rgba(255, 180, 80, .8); white-space: nowrap; }
#limit-btn.ready #limit-label { opacity: 1; }
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* ON TOUCH THE BAR STANDS UP. Horizontal, it sat in the traffic between
     the fan, quick-eat and the target frame (Alan: "so hard to see with
     stuff in between"). Vertical on the LEFT EDGE it owns empty screen:
     rotate(-90deg) turns the track's x-axis into "up", so the same
     width-percent fill from hud.js climbs the bar unchanged. */
  /* THE LIMIT ORB (Alan: "maybe it can just be a button as well and it
     charges up?"). A round button on top of the skill column, right edge.
     The track is turned -90deg inside it so hud.js's width-% fill rises
     from the bottom like a filling glass; the circle clips it. Ready = the
     same blaze as the bar had. */
  body:not(.kb) #limit-btn { width: 56px; height: 56px; right: 14px; left: auto; top: auto;
    bottom: 530px; transform: none; border-radius: 50%; overflow: hidden;
    border: 2px solid #4b3d7a; box-shadow: 0 3px 10px rgba(0, 0, 0, .55); }
  body:not(.kb) #limit-btn.resting { transform: translateY(12px); }
  body:not(.kb) #limit-btn.ready { border-color: #ffd166; }
  body:not(.kb) #limit-track { inset: 0; border-radius: 50%; transform: rotate(-90deg); }
  body:not(.kb) #limit-tip { display: none; }
  body:not(.kb) #limit-label { top: 50%; left: 50%; transform: translate(-50%, -50%); opacity: 1;
    font-size: 8px; letter-spacing: .12em; }
}

/* --- COMBO METER - the chain, written across the screen ------------------ */
/* Fighting-game grammar: a number that PUNCHES bigger with every hit, climbs
   through heat colours as the chain grows, and falls away when it breaks. */
#combo-meter { position: fixed; right: 26px; top: 30%; z-index: 24;
  pointer-events: none; text-align: right;
  transform: rotate(4deg); transition: opacity .45s ease; }
#combo-meter.resting { opacity: 0; }
#combo-n { display: block; font: 900 58px/0.9 'Cinzel', serif;
  color: var(--combo-col, #dfe6ff);
  text-shadow: 0 0 14px var(--combo-glow, rgba(160, 180, 255, .7)),
               2px 3px 0 rgba(10, 5, 25, .9); }
#combo-word { display: block; font: 700 13px/1 'Cinzel', serif;
  letter-spacing: .34em; color: var(--combo-col, #dfe6ff); opacity: .85;
  text-shadow: 1px 2px 0 rgba(10, 5, 25, .9); }
#combo-meter.pop #combo-n { animation: combo-pop .28s cubic-bezier(.2, 2.2, .4, 1); }
@keyframes combo-pop {
  0% { transform: scale(1.55); } 100% { transform: scale(1); }
}
/* heat tiers */
#combo-meter[data-tier="1"] { --combo-col: #dfe6ff; --combo-glow: rgba(160, 180, 255, .7); }
#combo-meter[data-tier="2"] { --combo-col: #ffd166; --combo-glow: rgba(255, 200, 90, .8); }
#combo-meter[data-tier="3"] { --combo-col: #ff8547; --combo-glow: rgba(255, 130, 60, .85); }
#combo-meter[data-tier="4"] { --combo-col: #c9a2ff; --combo-glow: rgba(190, 140, 255, .95); }
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) #combo-meter { right: 14px; top: 24%; }
  body:not(.kb) #combo-n { font-size: 44px; }
}


/* compass: the minimap stays north-up while the camera orbits, so a needle
   says which way the LENS points. Hidden until the view actually rotates. */
#mini-north { position: absolute; left: 50%; top: 50%; width: 0; height: 0;
  pointer-events: none; opacity: 0; transition: opacity .3s; z-index: 3; }
#mini-north::before { content: 'N'; position: absolute; left: -6px; top: -47px;
  font: 700 10px/1 'Cinzel', serif; color: #ffd166;
  text-shadow: 0 0 6px rgba(0,0,0,.9), 0 0 10px rgba(255,190,80,.6); }
#mini-dock.rotated #mini-north { opacity: 1; }


/* mid-fight boss lines: a banner over the action, never a modal. The fight
   stays fully playable underneath - that is the whole point. */
#boss-say { position: fixed; left: 50%; top: 16%; transform: translateX(-50%);
  max-width: min(560px, 86vw); text-align: center; pointer-events: none;
  font: 600 17px/1.45 'Cinzel', serif; color: #e8dcff; opacity: 0; z-index: 26;
  text-shadow: 0 2px 8px rgba(5, 3, 15, .95), 0 0 22px rgba(120, 70, 200, .5);
  transition: opacity .35s ease; }
#boss-say.on { opacity: 1; animation: boss-say-in .45s ease-out; }
#boss-say[data-who="you"] { color: #ffe9b3;
  text-shadow: 0 2px 8px rgba(5, 3, 15, .95), 0 0 22px rgba(255, 190, 80, .45); }
@keyframes boss-say-in {
  from { transform: translateX(-50%) translateY(10px); opacity: 0; }
  to { transform: translateX(-50%) translateY(0); opacity: 1; }
}

/* --- mobile camera cluster: eye = first person, arrow = reset ---------- */
/* Touch only: desktop has the wheel, right-drag and V, and needs no help. */
/* LEFT edge: the right edge mid-height belongs to the LIMIT BAR (fixed,
   vertical, centred ~42-44%), which only appears charged - the first
   placement sat straight under it and shipped overlapping (Alan). The
   left edge at this height is empty on every HUD state. */
#cam-btns { display: none; position: absolute; left: 14px; top: 46%;
  transform: translateY(-50%); flex-direction: column; gap: 10px; z-index: 6; }
@media (pointer: coarse) { #cam-btns { display: flex; } }
#cam-btns button { width: 40px; height: 40px; border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.1), transparent 45%), rgba(16, 10, 34, .82);
  color: #cabffa; border: 1.5px solid #4b3d7a; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5); }
#cam-btns button:active { transform: scale(.92); }
/* lit while the lens is inside first person, so the mode reads as ON */
#cam-fpv.on { border-color: #cfa54e; color: #ffd98a;
  box-shadow: 0 0 10px rgba(212, 169, 79, .4), 0 2px 8px rgba(0, 0, 0, .5); }
/* autorun toggle joins the camera cluster; lit while running */
#cam-run.on { border-color: #7ec48c; color: #8fe0a4;
  box-shadow: 0 0 10px rgba(99, 176, 119, .45), 0 2px 8px rgba(0, 0, 0, .5); }

/* --- trading -------------------------------------------------------------
   The invite banner reuses the duel challenge's shape on purpose: answering
   one is answering any, and a player should not have to learn a second
   vocabulary of buttons under time pressure. */
#trade-invite { position: fixed; top: 94px; left: 50%; transform: translateX(-50%);
  z-index: 9; display: flex; gap: 10px; align-items: center;
  background: rgba(16, 10, 34, .95); border: 1.5px solid #4b7f6a;
  border-radius: 12px; padding: 9px 14px; font-family: Georgia, serif;
  box-shadow: 0 0 18px rgba(75, 127, 106, .35); }
#trade-invite[hidden] { display: none; }
#ti-text { font-size: 13px; color: #e8e2f7; }
#trade-invite button { padding: 5px 14px; border-radius: 8px; cursor: pointer;
  font-family: Georgia, serif; font-size: 12.5px; }
#tti-yes { background: linear-gradient(180deg, rgba(14,40,30,.9), rgba(6,20,15,.95));
  border: 1px solid #4b7f6a; color: #9fe3c4; }
#tti-no { background: none; border: 1px solid #3a2f5e; color: #8f83bd; }

/* THE WINDOW ITSELF. Sized for a phone first and merely allowed to be wider:
   two people run this at once, standing in a square, on whatever they have in
   their hands. Nothing here needs hover and nothing needs a right-click. */
#trade-window { position: fixed; inset: 0; z-index: 30; display: flex;
  align-items: center; justify-content: center;
  background: rgba(6, 4, 14, .62); }
#trade-window[hidden] { display: none; }
.tw-card { width: min(94vw, 460px); max-height: 88vh; overflow-y: auto;
  background: rgba(16, 10, 34, .98); border: 1.5px solid #4b7f6a;
  border-radius: 14px; padding: 12px 14px 10px; font-family: Georgia, serif;
  color: #e8e2f7; box-shadow: 0 0 26px rgba(75, 127, 106, .3); }
.tw-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.tw-title { font-size: 16px; letter-spacing: .4px; }
.tw-foe { font-size: 12px; color: #9d92c6; }
.tw-cols { display: flex; gap: 10px; }
.tw-side { flex: 1 1 0; min-width: 0; background: rgba(255,255,255,.03);
  border: 1px solid #2e2650; border-radius: 10px; padding: 7px 8px; }
.tw-label { font-size: 11px; color: #9d92c6; margin-bottom: 5px;
  display: flex; align-items: center; gap: 6px; }
/* the confirm state, said out loud on both sides - a confirm you no longer
   hold is what the old swap scam relied on nobody noticing */
.tw-ok { color: #9fe3c4; border: 1px solid #4b7f6a; border-radius: 6px;
  padding: 0 5px; font-size: 10px; }
.tw-items { min-height: 46px; display: flex; flex-wrap: wrap; gap: 4px; }
.tw-bag { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
.tw-bagline { margin-top: 10px; }
.tw-chip { font-family: Georgia, serif; font-size: 11.5px; color: #d9d2f0;
  background: rgba(255,255,255,.05); border: 1px solid #3a2f5e;
  border-radius: 8px; padding: 5px 9px; cursor: pointer;
  /* a real tap target on a phone, not a link-sized sliver */
  min-height: 30px; }
.tw-chip.on { border-color: #4b7f6a; color: #9fe3c4;
  background: rgba(75, 127, 106, .16); }
.tw-chip:disabled { cursor: default; opacity: .85; }
.tw-empty { font-size: 11px; color: #6f659a; padding: 4px 2px; }
.tw-gold { display: block; margin-top: 6px; font-size: 11px; color: #9d92c6; }
.tw-gold input { width: 100%; margin-top: 3px; padding: 5px 7px; font-size: 13px;
  font-family: Georgia, serif; color: #ffd166; background: rgba(0,0,0,.35);
  border: 1px solid #3a2f5e; border-radius: 7px; }
.tw-gold-read b { color: #ffd166; font-weight: normal; }
.tw-foot { display: flex; gap: 8px; margin-top: 10px; }
.tw-foot button { flex: 1; padding: 9px 0; border-radius: 9px; cursor: pointer;
  font-family: Georgia, serif; font-size: 13px; min-height: 40px; }
.tw-cancel { background: none; border: 1px solid #3a2f5e; color: #8f83bd; }
.tw-confirm { background: linear-gradient(180deg, rgba(14,40,30,.9), rgba(6,20,15,.95));
  border: 1px solid #4b7f6a; color: #9fe3c4; }
.tw-confirm.on { background: linear-gradient(180deg, rgba(28,74,56,.95), rgba(12,38,28,.98));
  color: #cdf5e2; }
/* the two-second hold before the goods move: the window says so and stops
   taking edits, because a pause nobody explains reads as a bug */
#trade-window.locked .tw-chip,
#trade-window.locked .tw-gold input,
#trade-window.locked .tw-confirm { pointer-events: none; opacity: .55; }
.tw-note { margin-top: 7px; min-height: 15px; font-size: 11.5px; color: #9fe3c4; }

/* Adrift from the world: items live on the server now, so a dropped socket
   is a pause rather than a single-player game. */
body.offline #hotbar, body.offline #inv-panel { filter: grayscale(.5); opacity: .75; }

@media (max-width: 700px) {
  .tw-card { width: 96vw; padding: 10px 11px 9px; }
  .tw-cols { gap: 7px; }
  .tw-chip { font-size: 11px; padding: 5px 8px; }
  .tw-title { font-size: 15px; }
}

/* --- FPV THUMBSTICK (src/touchStick.js) ---------------------------------
   First person on a phone: the left half of the screen is a floating
   joystick, the right half a look-drag. Shown only while the lens is in
   first person (body.fpv, set by main.js from the real zoom), only on coarse
   pointers, and never for anyone who has proved a keyboard (body.kb).
   z-index sits ABOVE the canvas and BELOW every button - the camera
   cluster (6), the skill fan (30) and the attack button (31) keep their
   taps; everything else on the left half is stick. */
/* v2.91.11: the zone is the RING plus a little leeway, not half the screen
   - a thumb reaching across for a camera swipe kept landing in it (Alan:
   "the movement should be mostly contained in that button"). The ring's
   centre sits at (70px, 186px from the bottom); the zone is 150x172 around
   it, and the floating base is clamped inside that box. */
#stick-zone { display: none; position: fixed; left: 0; bottom: 100px; width: 150px; height: 172px;
  z-index: 5; touch-action: none; -webkit-user-select: none; user-select: none; }
/* v2.91.9: the stick is there in THIRD PERSON too (Alan: "i have to like
   move across the whole screen which makes it hard to control, rather than
   just contained into the joystick"). Tap-to-move still works anywhere
   outside the zone; a swipe still turns the camera. */
@media (pointer: coarse) { body:not(.kb) #stick-zone { display: block; } }
/* A panel is the screen while it is open. On a phone the WALK ring and the
   jump button step aside (Alan: "should there even be the walk button when
   char screen is open?") and the character panel may use the height they
   were reserving. main.js sets body.panel-open from the panels' own state. */
@media (pointer: coarse) {
  body.panel-open:not(.kb) #stick-zone, body.panel-open:not(.kb) #jump-btn { display: none !important; }
  body.panel-open:not(.kb) #stats-panel { max-height: calc(100dvh - 104px - 96px); }
}
#stick-base { position: absolute; width: 112px; height: 112px; margin: -56px 0 0 -56px;
  border-radius: 50%; display: none; pointer-events: none;
  border: 1.5px solid rgba(203, 191, 250, .45);
  background: radial-gradient(circle, rgba(16, 10, 34, .55), rgba(16, 10, 34, .25) 68%, transparent 72%);
  box-shadow: 0 0 18px rgba(0, 0, 0, .35); }
#stick-base.on { display: block; }
/* RESTING HINT. A floating stick has nothing on screen until a thumb lands,
   so nobody knows it is there (Alan: "i dont see the joystick on mobile?").
   A faint ring with a walk glyph sits where the thumb naturally rests and
   fades out while the stick is in use. */
#stick-zone::before { content: ''; position: absolute; left: 70px; bottom: 86px;
  width: 92px; height: 92px; margin: 0 0 -46px -46px; border-radius: 50%;
  border: 2px dashed rgba(215, 205, 255, .85);
  background: radial-gradient(circle, rgba(16, 10, 34, .55), rgba(16, 10, 34, .25) 60%, transparent 72%);
  box-shadow: 0 0 14px rgba(0, 0, 0, .5), inset 0 0 18px rgba(0, 0, 0, .35);
  pointer-events: none; opacity: .9; transition: opacity .25s ease; }
#stick-zone::after { content: 'WALK'; position: absolute; left: 70px; bottom: 86px;
  transform: translate(-50%, 50%); font-size: 12px; font-weight: 700; letter-spacing: .2em;
  color: #e8e2f7; text-shadow: 0 1px 3px #000, 0 0 8px rgba(0, 0, 0, .8); pointer-events: none;
  opacity: .9; transition: opacity .25s ease; }
#stick-zone:has(#stick-base.on)::before, #stick-zone:has(#stick-base.on)::after { opacity: 0; }
/* the hint also steps aside for an OPEN chat box and for the combat fan -
   it is a hint, not furniture, and the stick itself keeps working */
body:has(#chat:not(.collapsed):not([hidden])) #stick-zone::before,
body:has(#chat:not(.collapsed):not([hidden])) #stick-zone::after { opacity: 0; }
/* IN A FIGHT THE STICK STAYS. It used to hide its ring the moment combat
   started (the fan owned that band) and Alan read that as the walk going
   away: "what if u need to dodge stuff like in the boss battle?" So in first
   person the skill fan leaves the left half entirely and stands as a column
   on the right edge above the attack button; the ring stays lit. */
@media (pointer: coarse) {
  /* ...and (Alan, an hour later) in THIRD PERSON TOO: one right-hand cluster
     in both modes - attack, jump, the skill column, the limit orb on top. */
  body:not(.kb) #hotbar { left: auto; right: 14px; transform: none; width: 56px; height: auto;
    bottom: 246px; display: flex; flex-direction: column-reverse; gap: 8px; align-items: center; }
  /* RELATIVE, never static: the slot's art (.hb-art, inset 0) and the
     rotating ready-ring (::after) are absolutely positioned INSIDE it. Static
     handed them the whole column as their box - a 280px slab of skill art
     with a giant spinning ring, "the green thing" (Alan). */
  body:not(.kb) #hotbar .hb-slot { position: relative; width: 48px; height: 48px; left: auto; bottom: auto; }
  body:not(.kb) #hotbar .hb-slot:nth-child(1) { width: 56px; height: 56px; }
  body:not(.kb) #hotbar .hb-slot:nth-child(1) .hb-icon { font-size: 26px; }
}
#stick-knob { position: absolute; left: 50%; top: 50%; width: 52px; height: 52px; margin: -26px 0 0 -26px;
  border-radius: 50%; border: 1.5px solid #cfa54e;
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, .22), transparent 45%), rgba(84, 58, 178, .78);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .6), 0 0 10px rgba(212, 169, 79, .35); }

/* --- JUMP BUTTON (touch) ------------------------------------------------
   Space jumps on a keyboard; a phone had no way to (Alan: "is it possible
   to add the jump button as well?"). It sits on the right edge above the
   attack button, in the thumb's reach, and steps aside during a fight - the
   skill fan owns that band then, and nobody jumps mid-swing. Coarse
   pointers only, never for a proven keyboard. */
#jump-btn { display: none; position: fixed; right: 16px; bottom: 172px; width: 58px; height: 58px;
  border-radius: 50%; z-index: 31; cursor: pointer; padding: 0;
  /* MATCHES THE WALK RING (Alan: "more transparent like the walk joystick").
     It was an opaque plum disc with a hard border - furniture, sitting over
     the world at the same weight as a panel. Same dashed ring, same
     see-through fill, same soft glow as the joystick hint opposite it. */
  background: radial-gradient(circle, rgba(16, 10, 34, .55), rgba(16, 10, 34, .25) 60%, transparent 72%);
  color: #e8e2f7; border: 2px dashed rgba(215, 205, 255, .85);
  box-shadow: 0 0 14px rgba(0, 0, 0, .5), inset 0 0 18px rgba(0, 0, 0, .35);
  opacity: .9; align-items: center; justify-content: center;
  transition: transform .1s ease, opacity .25s ease; }
#jump-btn:active { transform: scale(.9); opacity: 1; color: #fff;
  background: radial-gradient(circle, rgba(84, 58, 178, .6), rgba(84, 58, 178, .3) 60%, transparent 72%);
  border-color: rgba(255, 255, 255, .9); }
/* shown only in first person - see the FEWER BUTTONS block below */
body:has(#stats-panel:not(.closed)) #jump-btn,
body:has(#inv-panel:not(.closed)) #jump-btn,
body:has(#dialogue:not([hidden])) #jump-btn { display: none !important; }

/* --- FEWER BUTTONS (v2.91.0) --------------------------------------------
   Alan, on a phone with ten controls in view: "thats alot of buttons
   around... is that the best way?" No. The left-edge camera cluster and the
   jump button are gone on touch; their jobs moved onto things already there:
     first person  -> the eye on the minimap's rim (#mini-fpv)
     reset camera  -> tap the N that appears on the minimap when it is turned
     autorun       -> push the thumbstick past its rim and let go
     jump          -> flick the thumbstick up
   The old elements stay in the DOM (main.js still reads #cam-fpv's state)
   but never draw on a touch screen. */
#cam-btns { display: none !important; }
/* ...except JUMP, which came back within the hour: "u cant have it tap on
   the button because it stops the walking... it needs to move and jump
   together" - the left thumb is on the stick, so jump belongs under the
   RIGHT thumb. First person only (that is where the stick is), right edge
   above the attack button; the skill column starts above it. */
@media (pointer: coarse) {
  body:not(.kb) #jump-btn { display: flex; }                                   /* third person too (Alan) */
}

#mini-fpv { display: none; position: absolute; z-index: 7; width: 34px; height: 34px;
  border-radius: 50%; padding: 0; cursor: pointer; align-items: center; justify-content: center;
  top: 92px; right: 96px;      /* tucked under the minimap's lower-left rim (92px dock) */
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.1), transparent 45%), rgba(16, 10, 34, .9);
  color: #cabffa; border: 1.5px solid #4b3d7a; box-shadow: 0 2px 8px rgba(0, 0, 0, .5); }
@media (pointer: coarse) { body:not(.kb) #mini-fpv { display: flex; } }
@media (min-width: 701px) { #mini-fpv { top: 118px; right: 122px; } }   /* 118px dock */
#mini-fpv:active { transform: scale(.92); }
body.fpv #mini-fpv { border-color: #cfa54e; color: #ffd98a;
  box-shadow: 0 0 10px rgba(212, 169, 79, .4), 0 2px 8px rgba(0, 0, 0, .5); }
/* the N is a button once the camera has turned: tap it to face north again */
#mini-dock.rotated #mini-north { pointer-events: auto; cursor: pointer; }
#mini-dock.rotated #mini-north::before { padding: 6px 8px; margin: -6px -8px; }
/* thumbstick cues: past the rim = "let go to run"; running = gold ring */
/* Past the rim the ring warms; the LOCK METER then fills over the dwell, so
   autorun visibly arms instead of firing by surprise. A conic gradient is the
   whole cue - no prompt to read, no toast to dismiss. */
#stick-base.past { border-color: #cfa54e; box-shadow: 0 0 16px rgba(212, 169, 79, .45); }
#stick-base.past::after {
  content: ''; position: absolute; inset: -5px; border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(rgba(212, 169, 79, .85) calc(var(--lock, 0) * 360deg),
                             rgba(212, 169, 79, 0) 0);
  -webkit-mask: radial-gradient(circle, transparent 0 calc(100% - 4px), #000 calc(100% - 4px));
          mask: radial-gradient(circle, transparent 0 calc(100% - 4px), #000 calc(100% - 4px)); }
#stick-base.armed { border-color: #ffd98a;
  box-shadow: 0 0 22px rgba(255, 217, 138, .65); }
body.autorun #stick-zone::before { border-style: solid; border-color: #cfa54e;
  box-shadow: 0 0 16px rgba(212, 169, 79, .5); opacity: .95 !important; }
body.autorun #stick-zone::after { content: 'AUTO-WALK'; font-size: 9.5px; letter-spacing: .1em; color: #ffd98a; opacity: .95 !important; }
/* the idle notice sat on the quest banner on phones */
@media (max-width: 700px) { #net-status { top: 84px; } }

/* autorun in third person: nothing on screen said you were running and there
   was no way to stop (Alan). A pill above the log; any tap on the ground ends
   the run (renderer3d pointerdown -> onCancelRun). Touch screens only. */
#run-pill { display: none; position: fixed; left: 50%; bottom: 66px; transform: translateX(-50%);
  z-index: 8; padding: 5px 14px; border-radius: 999px; font-family: Georgia, serif; font-size: 11px;
  letter-spacing: .14em; color: #ffd98a; background: rgba(16, 10, 34, .88); border: 1.5px solid #cfa54e;
  box-shadow: 0 0 12px rgba(212, 169, 79, .35); pointer-events: none; }
/* the stick's own ring says AUTO-WALK in both modes now; the pill retired in v2.91.9 */
/* jump: a touch smaller, the thumb still lands on it */
#jump-btn { width: 52px; height: 52px; right: 19px; bottom: 176px; }

/* phones never drag panels (main.js PHONE_LAYOUT), so the drag handle is
   noise - Alan: "the moving button on mobile should only appear after..." -
   there is nothing for it to do; gone on touch layouts */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb) .pan-grip, body:not(.kb):has(#chat:not([hidden])) .pan-grip { display: none; }
}
/* the keyboard layout keeps its full banner and key hint on the bar */
body.kb #limit-label::after { content: ' BREAK \00b7 R'; }

/* --- READABLE CHAT, FOOD IN THE CLUSTER, NO KEY HINTS ON TOUCH (v2.91.8) ---
   Alan: "the text is quite hard to read in the chat dialogue, especially
   'you are in the world'" - lines sat bare on bright ground. Each line gets
   a dark pill; the collapsed whisper is no longer half-transparent; system
   lines lose the dim purple italic. */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* NO BOXES (Alan: "they look very blocky and in the way"). Legibility
     comes from the letterforms: a dark stroke painted UNDER the fill
     (paint-order) plus a soft shadow reads over lit cobbles and dark
     night alike, and costs no rectangle. */
  /* CLEAN, not outlined: the heavy stroke read as smeared (Alan: "the
     shadowing looks kind of ugly... more precise and clean"). White,
     semi-bold, one crisp 1px drop plus a tight 2px halo - the same recipe
     as subtitles. The battle log keeps its original look. */
  body:not(.kb) #chat-log { gap: 2px; }
  body:not(.kb) .chat-line { font-size: 12.5px; line-height: 1.35; font-weight: 600; color: #fff;
    text-shadow: none; paint-order: stroke fill; -webkit-text-stroke: 1.4px #000;   /* a thin border, no glow (Alan) */
    overflow-wrap: anywhere; }
  body:not(.kb) .chat-line.sys { color: #e6dfff; font-style: normal; }
  body:not(.kb) #chat.collapsed #chat-log { opacity: .95; max-height: 8vh; }
  /* "the food button is also just randomly floating on one side" - it joins
     the right-hand cluster, beside the attack button, and moves WITH it */
  body:not(.kb) #quick-eat { left: auto; right: 104px; bottom: 90px; transform: none;
    width: 52px; height: 52px; }
  body:not(.kb) #quick-eat.resting { transform: translateY(26px); }
  /* keyboard hints mean nothing to a thumb */
  body:not(.kb) #atk-key, body:not(.kb) #qe-key, body:not(.kb) .hb-key { display: none; }
}

/* --- the you-dot points where you are going (v2.91.8) --------------------
   A small triangle riding the dot, turned to the travel heading hud.js sets
   in --heading. The N compass badge is gone (Alan: "some random N"); the
   map is north-up anyway. The pulse animation owns the dot's transform, so
   the pointer lives on ::after with its own. */
#mini-north { display: none !important; }
.map-dot.you.heading::after { content: ''; position: absolute; left: 50%; top: 50%;
  width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 9px solid #fff; filter: drop-shadow(0 0 3px #8a6cff);
  transform: translate(-50%, -50%) rotate(var(--heading, 0deg)) translateY(-11px); }
.map-dot.small.you.heading::after { border-left-width: 4px; border-right-width: 4px; border-bottom-width: 7px;
  transform: translate(-50%, -50%) rotate(var(--heading, 0deg)) translateY(-9px); }
#mini-fpv.held { border-color: #ffd166; }

/* --- ONE COLUMN, ONE AXIS (v2.91.8) ---------------------------------------
   Alan: "really annoying having to track all these buttons and them not
   aligning with each other." Every right-hand control now shares the same
   centre line, 44px in from the right edge, stacked with 14px gaps:
     attack 74-146 | jump 160-212 | food 226-278 | skills 292-584 | limit 598-650 */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  /* one 8px gap everywhere, the same as between skill slots (Alan: "should
     it not be the same so spacing etc is the same?") */
  body:not(.kb) #attack-btn { width: 72px; height: 72px; right: 8px; bottom: 74px; }
  body:not(.kb) #jump-btn { width: 52px; height: 52px; right: 18px; bottom: 154px; }
  body:not(.kb) #quick-eat { width: 52px; height: 52px; right: 18px; bottom: 214px; left: auto; transform: none; }
  body:not(.kb) #quick-eat.resting { transform: translateY(26px); }
  body:not(.kb) #hotbar { width: 52px; right: 18px; bottom: 274px; gap: 8px; }
  body:not(.kb) #hotbar .hb-slot, body:not(.kb) #hotbar .hb-slot:nth-child(1) { width: 52px; height: 52px; }
  body:not(.kb) #hotbar .hb-slot:nth-child(1) .hb-icon { font-size: 24px; }
  body:not(.kb) #limit-btn { width: 52px; height: 52px; right: 18px; bottom: 574px; }
}

/* food at full health: still there, shaded and inert (Alan) */
#quick-eat.full { opacity: .4; filter: grayscale(.7); pointer-events: none; }

/* out of combat the column is attack + jump only: food and the limit orb
   follow the attack button's own resting state (Alan: "hide the food and
   limit break icon when not in combat as well") */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb):has(#attack-btn.resting) #quick-eat,
  body:not(.kb):has(#attack-btn.resting) #limit-btn { opacity: 0; pointer-events: none; transform: translateY(26px); }
}

/* ONE COMBAT FLAG FOR THE WHOLE COLUMN (Alan: "the skill buttons should all
   appear when the attack button appears"). Each control used to rest on its
   own clock - skills on a hot timer, food on hunger, the orb on charge - so
   the column assembled itself piece by piece. On touch the attack button's
   resting state is the only switch: attack out = everything out. */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:not(.kb):has(#attack-btn:not(.resting)) #hotbar.resting,
  body:not(.kb):has(#attack-btn:not(.resting)) #quick-eat.resting:not(.nofood),
  body:not(.kb):has(#attack-btn:not(.resting)) #limit-btn.resting { opacity: 1; pointer-events: auto; transform: none; }
  body:not(.kb):has(#attack-btn:not(.resting)) #quick-eat.full { opacity: .4; pointer-events: none; }
  body:not(.kb):has(#attack-btn.resting) #hotbar { opacity: 0; pointer-events: none; }
  body:not(.kb) #quick-eat.nofood { opacity: 0 !important; pointer-events: none !important; }
}

/* an open panel owns the phone: the limit orb and jump step aside with the
   rest of the column (Alan: "limit break button is still sitting on top of
   z index when bag and settings are open") */
@media (max-width: 700px), ((hover: none) and (pointer: coarse)) {
  body:has(#inv-panel:not(.closed)) #limit-btn, body:has(#inv-panel:not(.closed)) #jump-btn,
  body:has(#stats-panel:not(.closed)) #limit-btn, body:has(#stats-panel:not(.closed)) #jump-btn,
  body:has(#quest-log:not(.closed)) #limit-btn, body:has(#quest-log:not(.closed)) #jump-btn,
  body:has(#settings-panel:not(.closed)) #limit-btn, body:has(#settings-panel:not(.closed)) #jump-btn,
  body:has(#settings-panel:not(.closed)) #hotbar, body:has(#settings-panel:not(.closed)) #attack-btn,
  body:has(#settings-panel:not(.closed)) #quick-eat,
  body:has(#help-panel:not(.closed)) #limit-btn, body:has(#help-panel:not(.closed)) #jump-btn { display: none !important; }
}

/* CHAT READ MODE WINS OVER THE COLLAPSED WHISPER, and it has to be said down
   here: the phone block caps #chat.collapsed #chat-log at 12vh and the
   no-keyboard block at 8vh, both at least as specific as .tall, so a History
   tap opened a panel that was still eight lines high. Tapping History IS the
   request to read back, whether the box is collapsed or not. */
#chat.tall #chat-log, body:not(.kb) #chat.tall #chat-log {
  max-height: 46vh; opacity: 1; pointer-events: auto;
  background: rgba(8, 5, 20, .92); border: 1px solid #4b3d7a;
  border-radius: 10px; padding: 8px 10px; }

/* ...and the other half: three lines when nobody is reading. The phone block
   caps the log at 16vh (12vh collapsed, 8vh with no keyboard), which on a
   tall screen is six or seven lines lying across the joystick. Three is
   enough to see that someone spoke; History is how you read the rest. */
@media (max-width: 700px) {
  #chat:not(.tall) #chat-log,
  body:not(.kb) #chat:not(.tall) #chat-log { max-height: 56px; }
}

/* THE STICK WINS UNDER THE CHAT. The log sits over the joystick's lane and
   there is nowhere else on a phone to put it (Alan: "if it overlaps it should
   still let ppl run"). Chat lines are something you READ, not something you
   press, so while the log is not in read mode it takes no touches at all and
   the thumb falls straight through to the stick underneath. The input row and
   the History bubble stay pressable; read mode takes its touches back so the
   log can be scrolled. */
@media (max-width: 700px) {
  #chat:not(.tall) #chat-log { pointer-events: none; }
  #chat.tall #chat-log { pointer-events: auto; }
}
