/* In-match HUD. Every corner has one owner; the battlefield centre stays clear. Tokens only. Its frames are forged
   moonsteel: dark steel with a gold edge (Tyler, 26 October 2026: "do the forged moonsteel"). */
#hud { position: absolute; inset: 0; pointer-events: none; }
#hud [data-hud], #hud button, #hud input, #hud canvas { pointer-events: auto; }

/* Top centre: all six champions, score, clock, towers. Sizes from the 2026-10-26 UI council: every cluster keeps its
   2026-10-07 place and the 2026-10-08 enlargement over that study, at about 14% of a 1080p frame (it was 21%). */
.topbar {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  zoom: var(--ui-scale);
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  padding: var(--s1) var(--s3) var(--s2);
  background: var(--steel);
  border-bottom: 2px solid var(--trim);
  box-shadow: var(--steel-edge), 0 6px 16px var(--shadow);
}
.tb-team { gap: var(--s2); align-items: flex-start; }
.tm { display: flex; flex-direction: column; gap: 3px; width: 48px; }
.tm .portrait { --size: 48px; }
.inspect-portrait { border: none; padding: 0; background: transparent; }
.inspect-portrait:focus-visible { outline: 2px solid var(--you); }
.tm-note { text-align: center; }
.score { display: flex; flex-direction: column; align-items: center; min-width: 120px; }
.score .big { font-family: var(--font-head); font-size: var(--t-xl-sm); display: flex; align-items: center; gap: var(--s2); }
.score .clock { font-family: var(--font-head); font-size: var(--t-l); color: var(--you-light); }
.tb-towers { gap: var(--s1); font-family: var(--font-head); font-size: var(--t-s); } /* readable at a glance (Opus, round 9) */
.tb-towers .ico { width: 16px; height: 16px; }

/* Top right: your score and quick toggles */
.mystats {
  position: absolute;
  top: var(--s3);
  right: var(--s4);
  zoom: var(--ui-scale);
  display: flex;
  gap: var(--s2);
  align-items: center;
  padding: var(--s1) var(--s2);
  /* Quieter than the combat console: a navy backing and a thin line, no forged frame (GPT-6.1 Sol, round 8). */
  background: color-mix(in srgb, var(--bg-0) 82%, transparent);
  border: 1px solid var(--steel-line);
}

/* One left dock: inspection and death recap stack, never cover each other. */
.inspection-dock {
  position: absolute;
  /* Below the performance readout at the top left, which the target card used to cover (UI council round 2 capture). */
  top: calc(32px / var(--ui-scale));
  left: var(--s4);
  bottom: 210px;
  zoom: var(--ui-scale);
  width: 380px;
  max-width: calc(100vw / var(--ui-scale) - 32px);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  overflow-y: visible;
  pointer-events: none;
}
.inspection-dock > .panel { flex-shrink: 0; }
#hud .inspection-dock, #hud .inspection-dock .target { pointer-events: none; }
#hud.is-dead .inspection-dock { overflow-y: auto; }
.mystats .stat-pair { gap: var(--s1); margin-right: var(--s2); }
/* Following: the word turns gold, a small state mark rather than a gold frame (GPT-6.1 Sol, round 8). */
#camera.following > .sm { color: var(--you); }
/* Dead, the abilities rest; gold and the shop stay bright because you can still buy (Opus, round 5). */
#hud.is-dead .combat .slots { filter: saturate(0.4) brightness(0.75); }
.target { width: 280px; max-width: 100%; padding: var(--s2); font-size: var(--t-xs); }
.target > .row { gap: var(--s2); }
.target .portrait { --size: 36px; border-width: 1px; }
.target .portrait .lvl { min-width: 16px; line-height: 14px; right: -2px; bottom: -2px; }
.target-kind { display: grid; place-items: center; width: 36px; height: 36px; border: 1px solid var(--line); flex: none; }
.target .grow { min-width: 0; }
.target .sm { font-size: var(--t-xs); }
.target .bar { height: 16px; }
.target .bar .val { font-size: var(--t-xs); line-height: 1; }
.target .bar.thin { height: 4px; }
.target .btn.small { padding: 2px var(--s2); font-size: var(--t-xs); align-self: flex-start; }
.target-stats { gap: var(--s2); margin-top: var(--s1); justify-content: space-between; }
.target-stats .stat { gap: 3px; font-size: var(--t-xs); }
.target-stats .stat svg { width: 14px; height: 14px; }
.target-items { gap: 3px; margin-top: var(--s1); }
.target-buffs { gap: 3px; margin-top: var(--s1); flex-wrap: wrap; min-height: 24px; }
.target-buffs .buff { width: 24px; height: 24px; background-size: auto 24px; background-position: calc(var(--i) * -24px) 0; }

/* Banner: major events only, below the top bar */
.notifications {
  position: absolute;
  top: 128px;
  left: 50%;
  transform: translateX(-50%);
  zoom: var(--ui-scale);
  width: 500px;
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.banner {
  text-align: center;
  padding: var(--s3) var(--s7);
  background: linear-gradient(90deg, transparent, var(--scrim-strong) 15%, var(--scrim-strong) 85%, transparent);
  pointer-events: none;
  z-index: var(--z-banner);
}
.banner.good h3 { color: var(--ally); }
.banner.bad h3 { color: var(--enemy); }
/* The banner takes priority: a toast waits out of sight while one is up, never drawn over it (Tyler, 2026-10-26:
   "our new ones need to take priority and they can't be overlapped"). An open panel hides the notifications
   (`[hidden]`), and then neither rule applies, as when they were unmounted. */
body:has(.notifications:not([hidden]) .banner) #toast.show { opacity: 0; }
/* While dead the respawn plate holds the toast's spot, so the toast drops below it (round-11 capture: "Wait until you
   respawn." covered the countdown). */
body:has(.notifications:not([hidden]) .respawn) #toast { top: calc(284px * var(--ui-scale)); }
/* A kill slams in with both portraits (2026-10-25 evening playtest: "even more obvious when we get kills"). */
.banner { animation: banner-slam 260ms cubic-bezier(.2, 1.4, .4, 1) both; }
.banner .duel { display: flex; align-items: center; justify-content: center; gap: var(--s3); margin-bottom: var(--s1); }
.banner .duel img { width: 52px; height: 52px; image-rendering: pixelated; border: 2px solid var(--line, #2c4078); background: var(--scrim-strong); }
.banner.good .duel .killer { border-color: var(--ally); }
.banner.bad .duel .killer { border-color: var(--enemy); }
.banner .duel .victim { filter: grayscale(1) brightness(0.7); }
.banner .duel .vs { font-size: 26px; color: var(--you); text-shadow: 0 2px 0 #000; }
.banner.personal h3 { font-size: calc(var(--t-l) * 1.6); color: var(--you); text-shadow: 0 3px 0 #000, 0 0 18px rgb(244 196 64 / 55%); letter-spacing: 0.04em; }
.banner.personal .duel img { width: 68px; height: 68px; }
@keyframes banner-slam { 0% { opacity: 0; transform: scale(1.7); } 60% { opacity: 1; transform: scale(0.96); } 100% { opacity: 1; transform: scale(1); } }
/* The raid boss (docs/RAID_BOSS.md "The arrival"): its banners carry its body, in the Moonforge frost ramp. */
.banner.frost h3 { color: #c8f6ff; text-shadow: 0 3px 0 #000, 0 0 18px rgb(106 224 255 / 60%); letter-spacing: 0.04em; font-size: calc(var(--t-l) * 1.4); }
.banner .creature { filter: drop-shadow(0 0 6px rgb(106 224 255 / 70%)); }
.raid-countdown {
  position: absolute;
  top: 96px;
  left: 50%;
  transform: translateX(-50%);
  zoom: var(--ui-scale);
  z-index: var(--z-banner);
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s6);
  background: linear-gradient(90deg, transparent, var(--scrim-strong) 15%, var(--scrim-strong) 85%, transparent);
  pointer-events: none;
}
.raid-countdown .silhouette { filter: brightness(0) drop-shadow(0 0 3px #6ae0ff) drop-shadow(0 0 1px #c8f6ff); }
.raid-countdown .eyebrow { color: #a4c4f0; }
.raid-countdown b { font-size: calc(var(--t-l) * 1.8); color: #c8f6ff; text-shadow: 0 3px 0 #000; text-align: center; }
/* Frost sweeps down every screen as it arrives (0.6 s), then thaws away. */
.frost-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-banner);
  background: linear-gradient(180deg, rgb(200 246 255 / 50%), rgb(106 224 255 / 22%) 18%, rgb(14 90 154 / 10%) 38%, transparent 55%);
  animation: frost-sweep 3s ease-out both;
}
@keyframes frost-sweep { 0% { clip-path: inset(0 0 100% 0); opacity: 1; } 20% { clip-path: inset(0 0 0 0); opacity: 1; } 100% { clip-path: inset(0 0 0 0); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .banner, .frost-vignette { animation: none; } .frost-vignette { opacity: 0.6; } }

/* Above the combat cluster: status, recall, hint */
.status, .hint, .recall { position: absolute; left: 50%; transform: translateX(-50%); zoom: var(--ui-scale); }
.status {
  /* Just above the console it belongs to (GPT-6.1 Sol, rounds 3 and 4: the casting line floated mid-screen). */
  bottom: 196px;
  padding: var(--s2) var(--s4);
  background: var(--panel);
  border: 2px solid var(--you);
  font-weight: 700;
}
.recall { bottom: 290px; width: 360px; padding: var(--s3); display: flex; flex-direction: column; gap: var(--s1); }
/* The casting hint sits just above the console it belongs to (GPT-6.1 Sol, round 3). */
.hint { bottom: 186px; color: var(--text-dim); background: var(--panel-soft); padding: var(--s1) var(--s3); white-space: nowrap; }
/* An opaque moonsteel plate, so no world name shows through the count, and the count in the pixel face (Grok, round 6:
   "Ni" peeked out of "RESPAWN IN" and a smooth 112 px digit covered the body). */
.respawn { text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--s2); width: fit-content; margin-inline: auto; padding: var(--s2) var(--s5); background: var(--steel); box-shadow: var(--steel-edge); }
.respawn .t { font-family: var(--font-head); font-size: var(--t-display); line-height: 1; }

/* Bottom left: compact stats */
.statspanel {
  position: absolute;
  bottom: 0;
  left: 0;
  zoom: var(--ui-scale);
  width: 256px;
  padding: var(--s1) var(--s2);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s1) var(--s3);
  background: var(--steel);
  border-top: 2px solid var(--trim); /* the console's and minimap's gold edge (Opus, round 8) */
  box-shadow: var(--steel-edge), 0 6px 16px var(--shadow);
}
.statspanel .all-stats { grid-column: 1 / -1; }
.statspanel .stat .lbl { font-size: 15px; color: var(--text-dim); } /* readable labels (GPT-6.1 Sol, round 9) */
.allstats {
  position: absolute;
  top: 128px;
  left: var(--s4);
  zoom: var(--ui-scale);
  width: 720px;
  max-width: calc(100vw / var(--ui-scale) - 32px);
  max-height: calc(100vh / var(--ui-scale) - 148px);
  overflow-y: auto;
  z-index: var(--z-overlay);
}
.allstats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3) var(--s5); padding: var(--s4); }
.allstat { display: flex; flex-direction: column; gap: 2px; }

/* Bottom centre: the combat cluster, one moonsteel console with cut top corners and a gold edge. The shape is drawn
   by its ::before (steel) and ::after (gold edge), not clipped, so the buff strip and level-up buttons above stay whole. */
.combat {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  zoom: var(--ui-scale);
  display: flex;
  align-items: flex-end;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  isolation: isolate;
}
.combat::before, .combat::after { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.combat::before {
  background: var(--steel);
  box-shadow: inset 0 0 0 1px var(--steel-dark), inset 0 0 0 3px var(--steel-line);
  clip-path: polygon(0 18px, 18px 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
}
.combat::after {
  background: var(--trim);
  clip-path: polygon(0 18px, 18px 0, calc(100% - 18px) 0, 100% 18px, 100% 21px, calc(100% - 18px) 3px, 18px 3px, 0 21px);
}
.combat .portrait { border-color: var(--trim); box-shadow: 0 0 0 2px var(--steel-dark), inset 0 0 12px var(--shadow); }
.combat .portrait .lvl {
  right: -10px;
  bottom: -10px;
  min-width: 34px;
  height: 34px;
  line-height: 30px;
  font-size: var(--t-m);
  border: 2px solid var(--trim);
  border-radius: 50%;
  background: radial-gradient(var(--steel-line), var(--bg-1));
  font-family: var(--font-head);
  font-weight: 400;
  color: var(--you-light);
}
.combat .bar.xp { outline-color: var(--trim-dark); height: 6px; background: var(--steel-line); } /* a thin track, not an empty field (Opus, round 10) */
.combat :where(.slot) { border-color: var(--trim-socket); box-shadow: 0 0 0 2px var(--steel-dark); }
.combat .slot.ready { border-color: var(--you-light); }
.combat .slot.passive, .combat .slot.passive > img { border-radius: 50%; }
.combat .slot.passive { border-color: var(--steel-line); }
.combat .slot .k { background: var(--steel-flat); border-color: var(--trim); color: var(--you-light); font-family: var(--font-head); font-weight: 400; }
/* Ranks read at a glance (Opus, round 6: 10x4 was too small). */
.combat .pips i { width: 14px; height: 6px; background: var(--steel-dark); border-color: var(--steel-line); }
.combat .pips i.on { background: var(--you-light); border-color: var(--trim); box-shadow: 0 0 4px var(--you-glow); }
.combat .plus { background: linear-gradient(var(--you-light), var(--you-deep)); font-family: var(--font-head); font-weight: 400; box-shadow: 0 0 0 1px var(--trim-dark), 0 0 10px var(--levelup-glow); }
/* The portrait stands as tall as the ability sockets (Tyler, 2026-10-27: "Why is the character portrait so small?"). */
.combat .me { position: relative; align-items: center; gap: var(--s1); width: 116px; }
.combat .me .bar { width: 116px; }
/* The skill point note floats above the portrait, so the portrait never moves when you level up or spend a point
   (Tyler, 2026-10-27: "when I level up, it goes up, because there's a skill point thing under it"). */
.combat .points {
  position: absolute;
  bottom: calc(100% + var(--s2));
  left: 50%;
  transform: translateX(-50%);
  padding: 2px var(--s2);
  background: var(--steel-dark);
  border: 1px solid var(--trim);
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
}
.abil { display: flex; flex-direction: column; gap: var(--s2); position: relative; }
.buffs { position: absolute; bottom: 100%; left: 0; margin-bottom: var(--s2); display: flex; gap: var(--s1); flex-wrap: wrap; max-width: 520px; }
.buff { position: relative; width: 32px; height: 32px; background: url(/c/3af6603e81fa736042efcbcf89da16ccb94c131c68eeaac23a6203724cc46f0b.png) calc(var(--i) * -32px) 0 / auto 32px no-repeat; image-rendering: pixelated; cursor: help; }
.buff b { position: absolute; right: 1px; bottom: 0; z-index: 1; font-size: var(--t-xs); line-height: 1; color: var(--text); text-shadow: 0 0 2px var(--bg-0), 1px 1px 0 var(--bg-0); }
.buff.bad { filter: drop-shadow(0 0 3px var(--enemy)); }
.buff.starter { background: none; border: 1px solid var(--line); }
.buff.starter img { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
.buff.starter.mastered { border-color: var(--you); }
.slots { display: flex; gap: var(--s2); align-items: flex-end; padding-top: var(--s5); }
.slotcol { align-items: center; gap: 0; }
.slot.passive { margin-bottom: 13px; }
.bars { display: flex; flex-direction: column; gap: 3px; width: 400px; }
/* Health is the urgent number: a taller bar and larger digits, with mana 2 px thinner so the block keeps its height
   (GPT-6.1 Sol, round 5: the health digits were quieter than the gold). */
.bars .bar { height: 22px; background: var(--steel-dark); border-color: var(--steel-dark); outline-color: var(--steel-line); }
.bars .bar:not(.mana) .val { font-size: 18px; text-shadow: 0 0 2px var(--bg-0), 1px 1px 0 var(--bg-0), -1px -1px 0 var(--bg-0), 1px -1px 0 var(--bg-0), -1px 1px 0 var(--bg-0); }
.combat .bar > i { background-image: var(--sheen); }
.bars .bar.mana { height: 16px; }
.bars .bar.mana .val { font-size: var(--t-xs); }
.bars .bar .val { line-height: 1; }
/* Under 30% health the bar's frame pulses amber; the fill stays green, because red means an enemy (UI council round 2). */
.bars .bar.low { outline: 2px solid var(--physical); animation: low-health 1s steps(2, jump-none) infinite; }
@keyframes low-health { 50% { outline-color: var(--trim-dark); } }
@media (prefers-reduced-motion: reduce) { .bars .bar.low { animation: none; } }
/* A fixed width, so the panel never grows when gold gains a digit (2026-10-26 HUD audit: every icon slid at 1,000
   and 10,000 gold). 216 px holds 99,999 gold beside the Shop key. */
.econ { gap: var(--s2); width: 216px; }
.inv { display: grid; grid-template-columns: repeat(3, 48px); gap: var(--s2); justify-content: center; }
.inv :where(.slot) { border-color: var(--steel-line); }
.goldbox {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  background: var(--steel-dark);
  border: 2px solid var(--trim-dark);
  padding: var(--s1) var(--s3);
  color: var(--text);
  white-space: nowrap;
}
.goldbox:is(:hover, .virtual-hover) { border-color: var(--trim); }
/* Gold is quieter than health (GPT-6.1 Sol, round 7). */
.goldbox .lg { font-size: 20px; }
.goldbox.spend { border-color: var(--you); box-shadow: 0 0 10px var(--you-glow); }

/* Bottom right: minimap */
.minimap-frame {
  position: absolute;
  right: 0;
  bottom: 0;
  zoom: var(--ui-scale);
  --mm: 0.8;
  width: calc(452px * var(--mm));
  height: calc(222px * var(--mm));
  background: var(--bg-0);
  border: 2px solid var(--trim);
  border-right: none;
  border-bottom: none;
  box-shadow: 0 0 0 4px var(--steel-flat), 0 0 0 5px var(--steel-dark);
}
/* The raid pit's crown: a moonsteel arch over the bowl, so the frame holds the pit rather than cutting it off (Tyler:
   "make sure the minimap shows the rimecoil pit and doesn't cut it off"). The bowl's centre and radius come from the
   map (--pit-x, --pit-r: shares of the canvas's width, set by ui/play/minimap_raid.rs); the bump is drawn over it. */
.minimap-frame.pit-hidden::before { display: none; } /* no empty crown before the raid stirs (GPT-6.1 Sol, round 7) */
.minimap-frame::before {
  content: '';
  position: absolute;
  left: calc(448px * var(--mm) * var(--pit-x));
  top: calc(-42px * var(--mm));
  width: calc((448px * var(--pit-r) * 2 + 24px) * var(--mm));
  height: calc(42px * var(--mm));
  translate: -50% 0;
  background: var(--steel);
  border: 2px solid var(--trim);
  border-bottom: none;
  border-radius: 999px 999px 0 0;
  box-shadow: 0 0 0 1px var(--steel-dark);
}
/* The canvas reaches 32 of its 212 rows (38.4px) above the frame: the raid bowl's bump pokes out of the rectangle
   (docs/RAID_BOSS.md; ui/play/minimap_raid.rs BAND). Those rows are transparent until 14:00. The canvas takes no
   clicks; #minimap over it is the hit area, its clip-path cut to the rectangle and, once the arena is open, the bump. */
#hud #minimap-art { position: relative; z-index: 1; width: calc(448px * var(--mm)); height: calc(254.4px * var(--mm)); margin-top: calc(-38.4px * var(--mm)); display: block; image-rendering: pixelated; pointer-events: none; }
#minimap { position: absolute; z-index: 2; left: 0; top: calc(-38.4px * var(--mm)); width: calc(448px * var(--mm)); height: calc(254.4px * var(--mm)); }
.minimap-error { position: absolute; z-index: 3; inset: 0; display: grid; place-items: center; background: var(--bg-0); color: var(--text); pointer-events: none; }

/* Camp leash warning (camp-feedback.ts) */
.camp-warning {
  position: fixed;
  top: 200px;
  left: 50%;
  transform: translateX(-50%);
  zoom: var(--ui-scale);
  z-index: var(--z-banner);
  padding: var(--s2) var(--s4);
  background: var(--panel);
  border: 2px solid var(--line-strong);
  font-weight: 700;
  font-size: var(--t-s);
  max-width: 640px;
  text-align: center;
  pointer-events: none;
}
.camp-warning[data-state='warning'] { border-color: var(--physical); color: var(--physical); }
.camp-warning[data-state='returning'] { border-color: var(--text-dim); color: var(--text-dim); }

/* Out of mana: a soft blue glow at the screen edge while no learned spell is affordable; a hard flash when a cast is refused. */
#mana-edge { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 0.25s;
  box-shadow: inset 0 0 90px 24px var(--mana-tint); }
#mana-edge.on { opacity: 0.75; animation: mana-pulse 1.6s ease-in-out infinite; }
#low-health-edge { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 0.25s; box-shadow: inset 0 0 110px 30px rgb(255 154 46 / 55%); }
#low-health-edge.on { opacity: 0.9; animation: mana-pulse 1s ease-in-out infinite; }
/* Lighter than round 6's, so the edges of the fight stay readable (GPT-6.1 Sol, round 7); the fill now carries it. */
#low-health-edge.critical { box-shadow: inset 0 0 110px 40px rgb(255 154 46 / 75%); }
@media (prefers-reduced-motion: reduce) { #low-health-edge.on { animation: none; } }
/* Under 30% the fill itself turns amber, so the bar reads at a glance (Grok and Opus, round 6); red stays the enemy's. */
.bars .bar.low > i { background: var(--physical); }
.bars .bar.low .val { color: var(--text); }
#mana-edge.flash { animation: mana-flash 0.6s ease-out; }
@keyframes mana-pulse { 50% { opacity: 0.4; } }
@keyframes mana-flash { 0% { opacity: 1; box-shadow: inset 0 0 140px 44px var(--mana); } 100% { opacity: 0.75; } }

.core-alert {
  z-index: var(--z-banner);
  position: absolute;
  top: calc(124px * var(--ui-scale));
  left: 50%;
  transform: translateX(-50%);
  color: var(--enemy);
  border-color: var(--enemy);
  padding: var(--s2) var(--s3);
  font-weight: 700;
  pointer-events: none;
}

.match-performance { position: fixed; top: 8px; left: 12px; z-index: 20; pointer-events: none; color: var(--text-dim); font-size: var(--t-xs); background: var(--scrim); padding: 1px var(--s1); font-variant-numeric: tabular-nums; text-shadow: 0 1px 2px #000; }

.slot.buff-active { border-color: #ffbd62; box-shadow: 0 0 8px #ffbd6280, inset 0 0 5px #ffbd6240; }

/* Companion feedback overlays the original portrait footprint. */
.tm { position: relative; }
.squad-badge { position: absolute; inset: 0; pointer-events: none; border: 1px solid transparent; }
.squad-badge.armed { border-color: var(--you); box-shadow: 0 0 0 1px var(--you); }
.squad-status { pointer-events: auto; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); max-width: 52px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 9px; line-height: 11px; color: var(--text-dim); text-shadow: 0 1px 2px #000; }
/* Four circular sectors match the gesture's cardinal directions. */
.ping-wheel { position: fixed; z-index: var(--z-banner); width: 200px; height: 200px; transform: translate(-50%, -50%); pointer-events: none; font-size: 11px; color: var(--text); border: 2px solid var(--steel-line); border-radius: 50%; background: var(--steel); box-shadow: 0 3px 16px #000b, inset 0 0 0 4px #0b1723; }
.ping-wheel::before { content: ""; position: absolute; inset: 0; z-index: 2; border-radius: 50%; background: repeating-conic-gradient(from 45deg, var(--steel-line) 0deg 1deg, transparent 1deg 90deg); }
.ping-wheel::after { content: ""; position: absolute; left: 50%; top: 50%; width: 54px; height: 54px; transform: translate(-50%, -50%); z-index: 3; border: 1px solid var(--steel-line); border-radius: 50%; background: #0b1723; }
.ping-choice { position: absolute; inset: 0; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border-radius: 50%; white-space: nowrap; text-shadow: 0 1px 2px #000; }
.ping-choice.active { color: var(--you); background: #665025; box-shadow: inset 0 0 0 3px var(--you); }
.ping-choice.right { clip-path: polygon(50% 50%, 100% 0, 100% 100%); padding-left: 127px; }
.ping-choice.left { clip-path: polygon(50% 50%, 0 100%, 0 0); padding-right: 127px; }
.ping-choice.up { clip-path: polygon(50% 50%, 0 0, 100% 0); padding-bottom: 127px; }
.ping-choice.down { clip-path: polygon(50% 50%, 100% 100%, 0 100%); padding-top: 127px; }
.ping-center, .ping-scope { position: absolute; left: 50%; transform: translate(-50%, -50%); z-index: 4; white-space: nowrap; font-size: 9px; }
.ping-scope { top: calc(50% - 9px); color: var(--ally); }
.ping-center { top: calc(50% + 8px); color: var(--text-dim); }
