:root {
  --cursor-default: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAAAAAAAAPlDu38AAAAHdElNRQfqBx4TFSBrVrUNAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI2LTA3LTMwVDE5OjIwOjExKzAwOjAwT+K1yAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNi0wNy0zMFQxOToyMDoxMSswMDowMD6/DXQAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjYtMDctMzBUMTk6MjE6MzIrMDA6MDD1pVp1AAAJC0lEQVRYw8XUSYwcVx0G8O/Ve7V29d7TPT3TMx6PZzw2djyO8CITJZAQJTiRSWyCUBASQko4cAAkTkEcyDVikTggIRTEAQmFSCgiAiEggZDFOGA7jrPYY8+4Z/N0z/RW1dXd1VVv4WAFlNVmCXynd3jS/6e/3veA/3PoW4c77vrks/P75ycrM5W173/5B+1BsofMeBo7d84CuxU6C96HC8hkc3dmk5mv5jP5u8+snRmLedx8dfmVxu7xPcocGDiw/yCi/BCtlfaHA3AzGRIP42O2aZRNpt9KNfP4tuLUnlhEgd/za4X0SGxpFhYXFrH/E/tQq9b/u4DsaKEjOY4SGY1+bJcNW4cbDNR+qlkn0m76yDAOMYzDjaN33NtXQgFZgpm5GdBHYnhP9/5zwBfdzweXjSu7oejhckrg1vkcSimJXhAYvZDM6LrzaddJ3un1O8lBNKj94bHfdp5+/tcgZymmZ6YhjsTong/+fYBfCSEhGQH9DJRgk6MZHL7tKLaNaEhpbQRBoAUDjDlO6u58NnvvUy/+quIHfuNi4/zWjtycMtsm9s7thZoUaF/p/OuA9GgOEsqn0I5p1BgpJzl2zx/C3IG7MFoexUQeSDEPvaCLMKY5QL8FkhwvJyd2cxH7vWG3nraznHEdj3/t5zjdeQlba40bB9iVNJafXAiKvxi9iWjGgbQlMJqzMDI+jeLkLhTK21EqlVBOc1hiC81mC8FAuYyZNxvMOGGxxKFhPBTDONyohWsDKRTCRA8zO2dRW6ldH5ArFVB5YhICwtBAT1gGo+NZgnyhBMvNwE6k4aayKIxNY2RkBCV3iATpoBt0EQxgWpazM+Ek7k+5yduDQWCHcbhx/NCDftNrIFVOYtuOKYhbBwheHbw3wLrLgV7XISG7FPQ+jZr5kcQQk9smYRgGTDsBBQVGGVK5IooTsygVi5jIcOTsPrxugE7AqaHbFdu2j+pUv2d580ox5tHmyT+ebMzftA9Gy8Ls7CzUHgHvov92QO+cD2csjSuDi0HJGNtPqXFz0hSYGsshky9BN0wwpgMgICBghoFcsYLixByKI3lsK0gkWYC1jS10gpgkHLeQdN2PW4Z5//Yd0ztiEbVr3ka9kpuQDrdRvbT8dgAA5PNFTNlT4BCOqVvHDZ1pEzmF8amdoFSHbiWuATQCpRSU4DBMC9mRCrKj0yhkkpjMRkhoPazXW2j7ESzLTjm2fdBgxomMnd0nwQPbNTcvvrYQvQtg3ZmEtqlBSB5o0O4zTCebswbYPj0Dy3bATBuUalBKAUohDvvgwxCCR9B1A7nRKRTKMyjmEtheiGHTAVauttALBdyEaxtM3ytifiLwgo9cWao++S5A8FoH7ngaDbS7SeEcUJLu00mEmYkccqUKlJRgugkoCYAABJAihpICUdhHt9NE2A+giI5+P4TL+iimgMXlOurNPmq1Otav1gYdr/PjVrP1NwBg76yFKXRsJ5OCk/h3EY8f3ApMbaW6iKndH4WUErplgzEdSkloGgWhDPGgh67fwVr1ErrNVawsr2KhWke1FqIvLCjmwu96aLc7Umniu1dp9UdvzdPeCWgUfERaBKHJl4Tg60HEcGWlBq9ZhxQcUTgAyLU3IHgEEUeguoFsoYRMroD6ahXnL6zi1GWOtsgj4AY2NlvodgMQiicEi79XVpPyXTV8K4MrPhLjafgk8G1hHdZ1a49OOLaNuiiUJwGloFEKEYXgUQjJYxAQUKYjkytCQQMGGwiGCpdX2thqNOQgHKyDqF8Spr5FBduCUvA63nsDAKCcqyCHlIwVT1NCjxmGQUaTMSam58CYDsoM+F4Lw7APw7QgOL+2TqojXxpHNIyQpg10Q4LukDQ1Qz20tHD5O5lszmMw0J1roX85fH8AmacwQxNKUz2qtBO64aQc0kVlfBROIg3BYyy9cQpLb56BmyrATriQgkMBYLqB/GgFfruFSjpEu8+cWJrJ8vbSM5lsppfOJ3H55UXIUL4/oL/URWosg57q+zaxjzBq7DaZQKVgIZMfBY+HOH/6BTz34iuIgk2kM3m4qSyk4IBSsJwEcqUJeFurGMtwrDXkXBSzgtDDZzRCo0plAiuLK+8PAIBKfhJ5MycFRIZSdkwjFAV7iMrUDIJOHSdPnsKpS0PU6w1o4QYSrgs7kQIfXvvrnWQKbrYMr7aEco5ivanmCUytS9rPaZLJtaXVDwaQXQYsYUIR1adSOwFNTzokwOTkGDavLuIv59bRjpPY9ATa7Q7QW0fCsZBIFUAIIOIIiWQaup0F95YxkrPJ6qY4KEPWfOFzf/7ro08/+sGA3rKHVDmHSOM+jbRbiMbmTCZRdDmq1VVcqGuoN3z4QQ+xlkTb60H215FOmkhlS6BMBx8O4bhJeP4ANpoII8k2O/LwT9/4ydXqUvX8BwIAYCY9Cx1MxOB5ptF7GDOQYR0s1WKsexSbW1tyMBy8IqRMCeoaLS+E6K4jaROYTho8juB3mlhbfB3Pv/Qqzi1sot4KnSgaBrWN2lPXBWCvDiPSAaIGGrQHKDMTiHuo92ys1T0Mo7CqdPFZHos3oyg+wonjNP0YYXsVNg1h2Cl4W1WcO3saL19oY2lTIOLRn/ye9/Vup9u5LqBbbSMzPgKuCV9X7Daq0dmh0LHeCOB3u9AofljgU08w3zwdWt03OecHB5zlG74A79aRNgfw2nU8f2YVCzUFoXCJMPGwTowL7VYL198AgD1iHmaK8ZiIEUbop/zeEO2OB0rJbwTjjwyo3+86LVjcWRBafFJwcdMgIhOtHjDwW1jd6OBsNcZQaE2J+Cs67OeWrYsQm7gxgDhEwGIGRdQQEg/4XtcBkWfB5MNUslUiNcQ6R8OqI8UzVyM5fFYJNdUfyl1bvsDVVoxgSIaEym/e7X7hZ+ejF2FJB0Gzd2MAv9pGdqIAMOVrMbld8NhUmniISv3seGcOa7kL2Hy9jmwhB3eQhtR4R1LxeygkIk72x0rTpOLf90T7saX4dQkA65c2/ln36wEAQEFhfmY/eEJ9g0dRxxbu4017HUoQrL929R/3cgcKSDUzUEyCU2Fb0vw21bRdsRZ+SZNaS0iJ6kL1Rkb+7/J3ttJzezQt/+QAAAAASUVORK5CYII=") 2 2, default;
  --cursor-action: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAAAAAAAAPlDu38AAAAHdElNRQfqBx4TFSEcUYWbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI2LTA3LTMwVDE5OjIwOjEzKzAwOjAw2H2k4QAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNi0wNy0zMFQxOToyMDoxMyswMDowMKkgHF0AAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjYtMDctMzBUMTk6MjE6MzIrMDA6MDD1pVp1AAAI70lEQVRYw8XWSYwcVxkH8P97r/buru7qnp59sWfGThxnGYwdB2IlBBxlgSxeIjhwTC4ckLhEiuCSM9spkEMEJ4SUA0pQBBEQBYWEhGyjxAm2x54Z22PP9Ez3dFdXdXV11ds4hICy2QEC/E/v8vR++t73SR/wfw57/3D83luf/8KBa2dGdk5cfOLrP+6EQwkqkz527Z5D8RYHW4vb/11AUKkcnqhWvl31q3f+aXNxIpd8+51L7zR3j1ytSZdg4eobwK4iaC59tpB/ABy/TLJU3BN45hghxiHAPjJdm9nLFe9FWbdR94e5JS2cXV7B/jsXsH6m8dkCamNDHZ7jbsHz0dv3lVB0ZLETqQVCnWMlp3xTLjKdy3zj8I2395VQMIYNzO+ahf2wRueZ+D8HfNM40lv2Lu5Rih2cqXDce6iKsWKCqNuzeimbN0zvPs8pHo4G3VKWZ41nH3k2/PWrz4D8lWLn7A7Yxy20X+r8+4DejIAmmkHTY5ILYzqgWLjpIGYqXZRpiCTJaJyS8UKhcke1XPnq068/NRX3e63V9HRzxp3TrEGxZ3YPvP0Wtt5t/euA8mgARVRMNb2HUKs+VkgwNzuO+QO3oe51MOUPENgZ+v0UA2FWoa2btcSRujW6R2gR91XcKLFA0AHDT25+EidHX0FjeevTA7zpMs69eaY38vjodRrG/ooL1FkDtbF5jF5zAFW3jaqbY7TAUSA9hGGEuI+CwZwFi1lHbbgHucpEpvKNhrOaylxBBRy7ds9j/dzGlQHV4TomHpuEhLQJ2FHXsdmEz1EQGyhM7ENhZAYlM0SlkCPwJOpejpKZIUlSxAPYjuXtLriF+33Xvy3Je24mso2vzN8Vhf0OypMlzMzOAIcl4jd7Hw9gtxpw2jY0dMTA7qPMrg0XBOolDhshiuPToFTDIAIlR6LqU9RKwERFo17SiHopwp5ipulOOrZzN4Nx13p4aZgrvvXyc39pXb/3Otihhfm5eRhfYmgvdj4ISN9NUJj0sWIt9UbU6AKh1ufKLjBVIyiaKVzfheVQEMMENRxYJkEtcFALDNRKBDvqBsquwqWNNrqJgOcWhvxi8VbbtO/bObtzVkB0munm5nhxXHmZg9WT5z8IAIBafRgzagckpOeYzhHLsuhURaBepjAYgVcdAtEC1LQAakBxCdt1UanY8IsUFVtgusZQNDnWm12EMYfreL7nejea1DzqG+UbFJU9r+RsnnprKf8IgBxyYHdMCC17DPR+y/aCITfH5JAJExm82ggYA7TIAK2QNDfQD0PkaQbTMhEELio+Q61EMTdiomBxnN8IkQwUioWia5nWtSITR+N2b+/qyrknPwJIT8bwpwJ0jCj2cueAVPQ6x9DYUSfwXQ1CCZxiAZqn712gBkTSheA54rCH1maEOM4hpUI/FaiUPIyWCc6eb2Kzk6Kx2cT6eiMNo/CJ9nb7NQAwPjwWjrDgq1HJifgdF+IbW4lNG9sc43ULnfV1eEEFlkGgBYflFSDKAbJWG1E8wFojA9cWtjo5VhoZVpsKfeVCmyXEcYROJ1Sayh9ulNYe/8gUvB/5eQNuYkMTnTBNjxqWV67YOSZqFBAZTNdDIahACYksjtALe7BsA+WyA0UYLqynOLHax+sXTXCzioGg2O50kSR9aCJ/qaz8uyVezrrt6OMB6ZkYhfEyIvQiRzoHTdPZaxsa475EyaUg0HA9E2nrEjobW+g0IxDGUPQLqJQt8JyDEYqEU5xZ66K53ZL9QbpOqP4VMfT3qGRNKKAbdj8eAAAT1SkEtKwERJlRdo9lOWSsmGOkasF2GIq+i26nj3YrBdEaaT8HCINbcBH4JrKMo1Y0EGcMcc7azMWDK6eXf1AJgq6pHHQXOkhPpZ8MwD4GO7UB+t43mJbn+8YAdZ/Acw0QlWF5NcKFjRyOSWEYBGl/AEIMeAUb1YqFMBxgZthDe2B5uXaK4/Ojf6gE5b4/VMDKm6uQPfnJgP5yjNJ4BYlOIo+4X2TM3uOYwJgvUfQIGCV4dznBS2+3ARAUHQOOzZANMoBS+L6NamAjDPuYHHKx1tRX5TmrCnvwHAPjk2OTuHD2wmUqAGC6NI2aW1WSyopBja8RamDIGWB82IZQBC+/3cWfVzRa4QCuSeBaBgg04iiFVkC5WoJf8RC2IkyOFLDWJgtU20jd6AUimFpbvnh5ADlgwU4tKOiUKHIMMIpFI8PMqI12l+OlUxwd4WOzKxElHI6hYZsMjBIoLpDEKTzXhu1a0HmOkSGfXGiqG3lCtl8cffG1R99+9PKA3tku/MkAwpBdxukhSo2rXBMYrxCcW89wqulhsx0h6vXBmY9uIuAaCoHvwDIpoDXibh9EKYRxjqDkYMBhbIX64M+Tn62fWzl/4rIAAJgvzsNUhhSQNUrY3YZpoWrnOLNFcTE2sdVsqUE+WORC+oIWrU5PwSIC5aINnivESY5WO0XYk3j1nQbeOruNrc7Ay3nWa2xsPnVFANtvw8xMaKpTqskxZtgF8BSNvotLW11kebaqmXxASnmK5+ImQVxvO1bQPEPJM6ClBDFMrLU4Xl2KsdJUyEX+fNTvficOe+EVAd3lDqpTdSgTkSHpLZSwXQNl4lIzRhwnoAyPDQ+mnkTTfiMr9U5yLg+kwqi1YgWmOSZGfUhi4sUTbZzeAqTWSzDUQxasU+3Oh/aBT8oefQ1MlwkOMcwouyNKMnTCCIzhN9LIH0nNuJ8EXdi5s6SYeFkIcX2fk8lOQpAPclzc6mPxgkSu6LYi4lsW8V5YcpeAv6+LVwRgnwlDMIAgg8TxKIo9ELUIQz3EFFujkoFbEtulJnxeWc+RPw+pd/QG6upmrHCpIxFnJAOTj9y+ffwXJwqvwOMOknb/0wG659sYmq6DmIhIji8LwS3N5INMmouTG3NYmzuLzcVNlKsBKmkVgvKOMuTvIVHIBRa4ZlRq/qMQ7e+f804rUGB9afOffXbFCgDYeqCBn77xGOdM1KUSv3Wz0tNR0EIraGDz9SYAoN9MIOZyeKkHUKTcEn80NHMJ0S3JsocduIkGcOHUxU/z5P8ufwOKnVyMWe/wEwAAAABJRU5ErkJggg==") 2 2, pointer;
}

html,
body {
  cursor: var(--cursor-default);
}

/* Above every HUD window, floating number and lifted action bar: the badge is
   part of the pointer, so nothing the pointer travels over may cover it. */

.world-cursor-badge {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 140;
  width: 28px;
  height: 28px;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 1px 1px rgb(0 0 0 / 68%));
  transform-origin: 0 0;
}

.world-cursor-badge[data-state='pressed'] {
  filter: drop-shadow(0 1px 0 rgb(0 0 0 / 72%));
}

/* Same badge, less weight: the armed skill stays named while the spot under the
   pointer says it cannot take the cast. */

.world-cursor-badge[data-legality='blocked'] {
  opacity: 0.42;
  filter: saturate(0.25) drop-shadow(0 1px 1px rgb(0 0 0 / 68%));
}

/* ===== The HUD gauge ===================================================
   Every proportional readout the HUD draws is this one box: the character's
   own health, spirit and experience, an ally's health in the party rail, the
   focused target's health, and the health and threat rows on an overhead
   nameplate. A monster at 40% looks the same over its head, in the target
   frame, and — were it an ally — in the party rail, because all three are the
   same gauge with a different size and fill.

   A surface picks its size and its fill; the sheared box, the sunk track, the
   lit top edge and the label ride along. Nothing else in the HUD may invent a
   track-and-fill of its own.

   Knobs a surface may set on `.hud-gauge`:
     --gauge-h        row height
     --gauge-shear    how far the box leans; 0 for a square box
     --gauge-ring     the edge colour — this is how a gauge shows state
     --gauge-ring-w   the edge width
     data-band        a banded state — health or carried load — which paints the
                      edge from the same table the fill reads
   and on `.hud-gauge-fill`:
     background       the fill's paint
     data-band        the band's fill paint

   Structure:
     <div class="hud-gauge [--sm|--xs] [hud-gauge--split]" role="progressbar">
       <div class="hud-gauge-fill hud-gauge-fill--ghost"></div>   (optional)
       <div class="hud-gauge-fill"></div>
       <span class="hud-gauge-label">4,210 / 10,000</span>
       <span class="hud-gauge-pct">42%</span>                     (--split only)
     </div>
*/

.hud-gauge {
  --gauge-h: max(13px, calc(14px * var(--ui-scale)));
  --gauge-shear: 3px;
  --gauge-ring: rgba(220, 232, 226, 0.16);
  --gauge-ring-w: 1px;
  position: relative;
  width: 100%;
  height: var(--gauge-h);
  box-sizing: border-box;
  /* The track is translucent: the world stays the largest surface on screen,
     and an empty gauge should darken it rather than punch a hole in it. */
  background: var(--hud-vitals-track);
  /* No real border — a clip-path cuts one off at the sheared corners. The edge
     is an inset ring, which follows the clip. */
  border: 0;
  border-radius: 0;
  overflow: hidden;
  clip-path: polygon(
    var(--gauge-shear) 0,
    100% 0,
    calc(100% - var(--gauge-shear)) 100%,
    0 100%
  );
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.72);
}

/* The edge and the lit top line ride a pseudo-element ABOVE the fills, not the
   box's own shadow: an inset shadow paints on the background layer, so a full
   gauge would bury its own edge — and the edge is how a gauge shows state (out
   of range, rank, high contrast). The lit line is enough to seat the fill under
   a light source without the glassy top-half gloss of a mobile-game meter. */

.hud-gauge::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  box-shadow: inset 0 0 0 var(--gauge-ring-w) var(--gauge-ring);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0 1px, transparent 1px);
}

/* Overhead plates: read across a whole pull at distance, so the row is thinner
   and leans less — a 3px shear on a 9px bar is a third of the box. */

.hud-gauge--sm {
  --gauge-h: max(8px, calc(9px * var(--ui-scale)));
  --gauge-shear: 2px;
}

/* Progression strips carry no reading inside them; they are a line, not a row. */

.hud-gauge--xs {
  --gauge-h: max(3px, calc(4px * var(--ui-scale)));
  --gauge-shear: 2px;
}

/* The one edge state health gauges have: the focused target is out of reach. It
   snaps — the instant the player crosses the boundary cell is the whole point of
   the cue — and it replaces the resting ring rather than stacking on it. */

.hud-gauge--oor {
  --gauge-ring: var(--danger, #e0533d);
  --gauge-ring-w: 2px;
}

.hud-gauge-fill {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 1;
  width: 0;
  height: 100%;
  background: var(--hud-hp-healthy-fill);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
  transition: width 0.2s ease-out;
}

/* Health is banded rather than a fixed red: the colour IS the triage read, and it
   is the same read wherever the player meets it — their own bar, an ally's, the
   focused target's, and a field of plates. The percentage inside carries the
   number, so the band never has to be told apart by hue alone. */

.hud-gauge-fill[data-band='healthy'] { background: var(--hud-hp-healthy-fill); }

.hud-gauge-fill[data-band='hurt'] { background: var(--hud-hp-hurt-fill); }

.hud-gauge-fill[data-band='execute'] { background: var(--hud-hp-execute-fill); }

/* Carried load is banded by what it COSTS, not by how full the bag looks: an
   unencumbered bag is pewter rather than green, so the amber and the danger
   band mean "this is costing you movement" and nothing on the panel reads as a
   second health bar. One table, so the always-on panel and the inventory
   window's footer cannot disagree about the same bag. */

.hud-gauge-fill[data-band='light'] { background: var(--hud-load-light-fill); }

.hud-gauge-fill[data-band='heavy'] { background: var(--hud-load-heavy-fill); }

.hud-gauge-fill[data-band='overloaded'] { background: var(--hud-load-overloaded-fill); }

/* The worst band is not hue-only: a hard diagonal hatch rides the fill so an
   overloaded bag reads as overloaded in greyscale and to a red-blind player. */

.hud-gauge-fill[data-band='overloaded']::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    -45deg,
    rgba(0, 0, 0, 0.28) 0 3px,
    transparent 3px 7px
  );
}

/* The edge is the gauge's state channel; a costly load takes it. */

.hud-gauge[data-band='heavy'] { --gauge-ring: var(--hud-load-heavy-ring); }

.hud-gauge[data-band='overloaded'] {
  --gauge-ring: var(--hud-load-overloaded-ring);
  --gauge-ring-w: 2px;
}

/* High contrast swaps the resting ring for a hard white edge, but an edge that
   already carries meaning keeps its colour and only thickens — the same rule the
   out-of-range cue follows. Stated at this specificity or the blanket white edge
   below would erase the band. */

body.high-contrast .hud-gauge[data-band='heavy'] {
  --gauge-ring: var(--hud-load-heavy-ring);
  --gauge-ring-w: 2px;
}

body.high-contrast .hud-gauge[data-band='overloaded'] {
  --gauge-ring: var(--hud-load-overloaded-ring);
  --gauge-ring-w: 2px;
}

/* Crossing into a band that costs more fires once, on the frame the load
   changed — the pickup that caused it is the only moment the cue can connect to.
   The band colour holds the state afterwards, so the flash never repeats while
   the player stays loaded. Both halves stay INSIDE the box: the gauge is clipped
   to its sheared shape, so an outer glow would be cut away and never seen. */

@keyframes hud-load-alarm-edge {
  0% { box-shadow: inset 0 0 0 var(--gauge-ring-w) var(--gauge-ring); }
  30% { box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.88); }
  100% { box-shadow: inset 0 0 0 var(--gauge-ring-w) var(--gauge-ring); }
}

@keyframes hud-load-alarm-fill {
  0%, 100% { filter: none; }
  30% { filter: brightness(1.5) saturate(1.15); }
}

.hud-gauge.is-load-alarm::after { animation: hud-load-alarm-edge 620ms ease-out 1; }

.hud-gauge.is-load-alarm .hud-gauge-fill { animation: hud-load-alarm-fill 620ms ease-out 1; }

body.reduce-motion .hud-gauge.is-load-alarm::after,
body.reduce-motion .hud-gauge.is-load-alarm .hud-gauge-fill,
body.reduce-flashing .hud-gauge.is-load-alarm::after,
body.reduce-flashing .hud-gauge.is-load-alarm .hud-gauge-fill { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .hud-gauge.is-load-alarm::after,
  .hud-gauge.is-load-alarm .hud-gauge-fill { animation: none; }
}

/* Plates move up to two dozen gauges at once during a pull, so their fills are
   scaled rather than width-animated: width is a layout property, transform is
   not. Any gauge may opt in; the plates always do. */

.hud-gauge-fill--scaled {
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.18s ease-out;
}

/* Recent-damage ghost, behind the fill. JS snaps it up on a heal and lets its
   width lag on a hit, so the exposed strip between ghost and fill reads the size
   of the last landed hit at a glance. */

.hud-gauge-fill--ghost {
  z-index: 0;
  background: var(--hud-hp-ghost-fill);
  transition: width 0.4s ease-out;
}

body.reduce-flashing .hud-gauge-fill--ghost { transition: none; }

.hud-gauge-label,
.hud-gauge-pct {
  position: absolute;
  inset-block: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  font: 700 max(10px, calc(10px * var(--ui-scale))) / 1 'Segoe UI', sans-serif;
  color: #fff;
  text-shadow: var(--hud-vitals-text-shadow);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.2px;
  white-space: nowrap;
  pointer-events: none;
}

/* One reading sits in the middle of the row. */

.hud-gauge-label {
  inset-inline: 0;
  justify-content: center;
  padding-inline: calc(6px * var(--ui-scale));
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Two readings split the row: the exact value leads and the percentage trails,
   so neither sits on the centre line the fill edge sweeps past. */

.hud-gauge--split .hud-gauge-label {
  justify-content: flex-start;
  padding-inline-end: calc(38px * var(--ui-scale));
}

.hud-gauge-pct {
  inset-inline: auto calc(6px * var(--ui-scale));
  justify-content: flex-end;
}

.hud-gauge--sm .hud-gauge-label,
.hud-gauge--sm .hud-gauge-pct {
  font-size: max(8px, calc(6px * var(--ui-scale)));
  font-weight: 800;
  letter-spacing: 0.03em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
}

/* No reading available for this unit. It must not read as "empty health bar", so
   three things say so at once and none of them is a hue: a lighter slate track, a
   paler edge, and a dash where the number would be. */

.hud-gauge--unknown {
  --gauge-ring: rgba(138, 147, 163, 0.45);
  background: rgba(24, 29, 38, 0.72);
}

.hud-gauge--unknown .hud-gauge-label { color: rgba(174, 182, 196, 0.75); text-shadow: none; }

@media (prefers-reduced-motion: reduce) {
  .hud-gauge,
  .hud-gauge-fill { transition: none !important; }
}

body.reduce-motion .hud-gauge,
body.reduce-motion .hud-gauge-fill { transition: none !important; }

/* High contrast trades the quiet ring for a hard white edge on every gauge at
   once, so no surface keeps a softer outline than its neighbours. A gauge whose
   edge already carries meaning — out of range, rank, cast tier — keeps its own
   colour and only thickens. */

body.high-contrast .hud-gauge {
  --gauge-ring: rgba(255, 255, 255, 0.82);
  --gauge-ring-w: 2px;
}

body.high-contrast .hud-gauge--oor { --gauge-ring: var(--danger, #e0533d); }

:root {
  /* The UI shares the billboard world's matte warm/cool material hierarchy:
     slate carries structure, umber adds warmth, and brass marks action. */
  --bg: #091013;
  --panel: #11191d;
  --panel-2: #192327;
  --panel-raised: #202c30;
  --accent: #c9a45e;
  --accent-2: #9e7138;
  /* The gold family. --accent is the frame's quiet brass; these are the louder
     golds the HUD speaks in — the anchor accent, the coin-value ink, the ink a
     control answers the pointer with, and the emphasized-label ink. */
  --gold: #d9a441;
  --gold-value: #ffe066;
  --gold-ink: #ffe0a0;
  --gold-label: #f0d999;
  --text: #eee9de;
  --muted: #98a4a3;
  --danger: #d85b48;
  --ui-line: #344247;
  --ui-line-soft: rgba(133, 151, 153, 0.2);
  --ui-line-bright: rgba(188, 201, 196, 0.34);
  --ui-surface: rgba(13, 20, 23, 0.94);
  --ui-surface-raised: rgba(22, 31, 34, 0.96);
  --ui-shadow: 0 14px 34px rgba(0, 0, 0, 0.46);
  --ui-frame-fill: linear-gradient(145deg, rgba(29, 40, 42, 0.985), rgba(14, 22, 25, 0.985));
  --ui-frame-shadow:
    0 18px 44px rgba(0, 0, 0, 0.52),
    0 2px 10px rgba(0, 0, 0, 0.36),
    inset 0 1px 0 rgba(236, 228, 205, 0.055);
  /* The light on a frame's top corner, and the tick and rule its header wears.
     A surface with a tone of its own — the death card is the only one — restates
     these three and inherits every other part of the frame unchanged. */
  --ui-frame-glow: rgba(201, 164, 94, 0.08);
  --ui-tick-fill: linear-gradient(180deg, #ead8a5, var(--accent-2));
  --ui-tick-glow: rgba(201, 164, 94, 0.2);
  --ui-head-rule-lead: rgba(201, 164, 94, 0.42);
  --ui-control-fill: rgba(8, 14, 16, 0.54);
  --ui-control-fill-hover: rgba(37, 48, 49, 0.82);
  --ui-control-line: rgba(133, 151, 153, 0.28);
  --ui-control-line-hover: rgba(201, 164, 94, 0.58);
  --ui-focus-ring: 0 0 0 1px var(--bg), 0 0 0 3px var(--accent);
  /* How far a control sinks while it is held. A panel that scrolls its controls
     reserves this much room, because a sunk box is scrollable overflow and would
     otherwise raise a scrollbar for as long as the button is held down. */
  --ui-press-travel: 1px;
  /* The device's own insets — notch, island, home indicator — as one token set.
     Every surface reads these rather than env() itself, so a review glass can
     hand the page a phone's insets the way the phone would. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  /* The game's two typefaces. Both ship with the client; `loadGameFonts` puts
     them in the document's registry and in the render worker's, which keeps its
     own. The tail of each stack is what draws before they arrive and what draws
     for a script the latin subsets do not cover. Canvas text takes the same two
     stacks from FONT_DISPLAY / FONT_UI rather than restating them. */
  --font-display: 'Cinzel', Georgia, 'Times New Roman', serif;
  --font-ui: 'Inter', 'Segoe UI', system-ui, sans-serif;
  /* HUD-primitives design tokens (spec §4 addendum). Consumed by
     hud-primitives.css (AlertStack / ResultFlash / ResourcePips); kept here so
     :root owns the palette and a future theme swap touches one file. */
  --good: #6fd06a;            /* heal / success green */
  --info: #3498db;           /* mana / informational blue */
  --skillmsg: #b48ce0;       /* skill-channel system line (purple) */
  --warn: var(--accent-2);   /* warning tone band */
  --grid-line: var(--ui-line); /* table/grid hairline */
  --row-hover: rgba(255, 255, 255, 0.04);
  /* Action-bar dock height — AlertStack docks just above it. */
  --hotbar-h: calc(96px * var(--ui-scale));
  /* Class-resource pip hues (paired with per-kind glyphs, never hue-only). */
  --pip-sphere: #ffb84d;
  --pip-coin:   #e8c34a;
  --pip-soul:   #9bd0ff;
  --pip-charm:  #cdd6e0;
  --pip-shield: #9aa7b5;
  --pip-scar:   #e0533d;
  /* Accessibility UI scale: setUiScale() (palette.ts) overrides this on :root
     from the persisted value; the HUD sizes below scale off it via calc(). */
  --ui-scale: 1;
  /* HUD docking geometry — the single source for the screen margin and the gap
     between stacked widgets. The corner docks own both; no docked widget
     carries its own offset. */
  --hud-edge: calc(12px * var(--ui-scale));
  --hud-safe-top: max(var(--hud-edge), var(--safe-top));
  --hud-safe-right: max(var(--hud-edge), var(--safe-right));
  --hud-safe-bottom: max(var(--hud-edge), var(--safe-bottom));
  --hud-safe-left: max(var(--hud-edge), var(--safe-left));
  --hud-gap: calc(8px * var(--ui-scale));
  font-family: var(--font-ui);
  /* Mobile Safari inflates text on its own when this is left at `auto`, by a
     factor it picks from the block's width. Every size in the game is derived —
     from the accessibility scale, from the viewport, from a token — so a browser
     multiplying them behind our back makes every one of those derivations wrong
     at once: the type comes out oversized, the boxes around it grow with it, and
     a layout that fits on the desk does not fit on the phone it was measured for.
     The accessibility scale is how a player asks for larger text. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

* { box-sizing: border-box; }

/* ===== Scrollbars =========================================================
   Every scrolling surface in the game wears the same slim brass bar on a dark
   track. This is not decoration: a platform that draws classic scrollbars —
   Windows — otherwise puts a wide grey system bar inside HUD panels, so the same
   window looks like part of the game on one machine and like a web page on
   another. Both properties inherit, so the document root states them once and
   every scroll container in the game answers, including the ones nobody thought
   to style. A shadow root inherits them too, but a surface inside one that wants
   a different bar has to say so there, out of this sheet's reach. */

:root {
  --ui-scrollbar-thumb: rgba(201, 164, 94, 0.45);
  --ui-scrollbar-track: rgba(7, 12, 14, 0.5);
  scrollbar-width: thin;
  scrollbar-color: var(--ui-scrollbar-thumb) var(--ui-scrollbar-track);
}

/* Visually hidden, still available to screen readers (aria-live / labels). */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html, body, #app {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
}

#app { display: flex; align-items: center; justify-content: center; }

.screen {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel {
  background: var(--ui-frame-fill);
  border: 1px solid var(--ui-line-bright);
  border-radius: calc(9px * var(--ui-scale));
  padding: calc(12px * var(--ui-scale)) calc(14px * var(--ui-scale));
  width: 360px;
  box-shadow: var(--ui-frame-shadow);
}

.panel h1 {
  margin: 0 0 calc(8px * var(--ui-scale));
  padding-left: calc(9px * var(--ui-scale));
  border-left: calc(2px * var(--ui-scale)) solid var(--accent);
  font-size: calc(18px * var(--ui-scale));
  line-height: 1.15;
  letter-spacing: 0.15px;
}

.panel .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 20px; }

.field { margin-bottom: 14px; }

.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }

.field input {
  width: 100%;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--ui-control-line);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
}

.field input:focus { outline: none; border-color: var(--accent); }

.status { margin-top: 14px; font-size: 13px; min-height: 18px; }

.status.error { color: var(--danger); }

.status.info { color: var(--muted); }

/* char select */

.char-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }

.char-card {
  background: var(--panel-2);
  border: 1px solid var(--ui-control-line);
  border-radius: 10px;
  padding: 14px;
  cursor: var(--cursor-action);
  text-align: center;
  transition: border-color 0.15s, transform 0.1s;
}

.char-card:hover { border-color: var(--accent); transform: translateY(-2px); }

.char-card .name { font-weight: 600; margin-bottom: 4px; }

.char-card .meta { font-size: 12px; color: var(--muted); }

.char-card.empty { color: var(--muted); cursor: var(--cursor-default); opacity: 0.5; }

/* in-game */

#game-canvas { width: 100vw; height: 100vh; display: block; }

.hud {
  position: fixed; top: 12px; left: 12px;
  background: rgba(10, 14, 20, 0.7);
  border: 1px solid var(--ui-line-soft); border-radius: 8px;
  padding: 10px 14px; font-size: 13px; pointer-events: none;
}

.hud .row { margin: 2px 0; }

.hud .label { color: var(--muted); }

/* ===== HUD overlays ===== */

/* Containers don't capture clicks; only panels/buttons do, so the canvas under
   them stays usable for movement. */

.overlay { position: fixed; pointer-events: none; z-index: 10; }

/* --- HUD corner docks -------------------------------------------------------
   A dock pins itself to one screen corner and lays its rails out; a rail stacks
   its widgets top-to-bottom with a single shared gap. A docked widget's size is
   its own business and its position is the rail's, so a buff tray that wraps to
   three rows or a party frame that fills with six members pushes the widget
   below it down instead of covering it. Widgets the player drags gain
   .hud-layout-detached and leave the flow at their saved viewport coordinate. */

.hud-dock-row {
  position: fixed;
  display: grid;
  align-items: start;
  gap: var(--hud-gap);
  pointer-events: none;
  z-index: 10;
}

/* Equal side lanes keep combat on the actual viewport centre while left and
   right clusters stay inside their own budgets. */

.hud-dock-row--top {
  top: var(--hud-safe-top);
  left: var(--hud-safe-left);
  right: var(--hud-safe-right);
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}

/* Only the centre column drops under the toolbar: the toolbar is centred, and the
   side clusters carry the tall rails that would then reach the bottom docks. */

.hud-layout-editing .hud-dock--topCenter {
  margin-top: max(0px, calc(var(--hud-layout-bar-reserve, 0px) + var(--hud-gap) - var(--hud-safe-top)));
}

.hud-dock-row--bottom {
  right: var(--hud-safe-right);
  bottom: var(--hud-safe-bottom);
  left: var(--hud-safe-left);
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: end;
  z-index: 30;
}

.hud-dock {
  display: flex;
  align-items: flex-start;
  gap: var(--hud-gap);
  min-width: 0;
  /* A dock parked at its lane's edge is sized by its content, so without this it
     grows past the lane and out over its neighbour instead of narrowing. The lane
     is the budget the row promises each cluster; nothing may spend more than it. */
  max-width: 100%;
  pointer-events: none;
}

/* Each top dock is floored by the measured height of the dock below it in the same
   column. The two rows hang off opposite edges and neither can see the other, so
   without this a side rail grows with what the player is following until it reaches
   down through the chat launcher and the thumb stick.

   Only a rail member that can give ground does. Chips and medallions are fixed
   shells and keep their size; the widget that grows with its own content is the one
   that shrinks, and it scrolls what no longer fits. */

.hud-dock-row--top > .hud-dock {
  --hud-dock-room: 100dvh;
  max-block-size: calc(var(--hud-dock-room) - var(--hud-gap));
  min-block-size: 0;
}

.hud-dock-row--top > .hud-dock--topLeft { --hud-dock-room: var(--hud-dock-left-room, 100dvh); }

.hud-dock-row--top > .hud-dock--topCenter { --hud-dock-room: var(--hud-dock-center-room, 100dvh); }

.hud-dock-row--top > .hud-dock--topRight { --hud-dock-room: var(--hud-dock-right-room, 100dvh); }

/* A rail holds its size unless it carries a member that can give ground; a rail of
   fixed shells that shrank would simply spill them out of itself. */

.hud-dock-row--top .hud-rail {
  max-block-size: 100%;
  min-block-size: 0;
  flex-shrink: 0;
}

.hud-dock-row--top .hud-rail:has(> [data-hud-widget="questTracker"]) { flex-shrink: 1; }

.hud-dock--topCenter { align-items: center; justify-self: center; }

.hud-dock--topRight { align-items: flex-end; justify-self: end; }

.hud-dock--bottomLeft {
  width: 100%;
  min-width: 0;
  align-items: flex-start;
  align-self: end;
  justify-self: start;
}

.hud-dock--bottomCenter { align-items: center; align-self: end; justify-self: center; }

.hud-dock--bottomRight { align-items: flex-end; align-self: end; justify-self: end; }

.hud-rail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--hud-gap);
  pointer-events: none;
  /* Size to the widest widget rather than to a share of the dock, so a widget
     sizing itself against 100% still measures its own intended width. */
  width: max-content;
  max-width: 100%;
}

/* A rail a profile does not use draws nothing, and adds no gap between the rails
   that stand either side of it. */

.hud-rail:empty { display: none; }

.hud-dock--topRight .hud-rail,
.hud-dock--bottomRight .hud-rail { align-items: flex-end; }

/* A centre rail is as wide as its widest widget, so its narrower widgets centre
   on that rail rather than sitting flush against its left edge — otherwise an
   NPC conversation opening would drag the action bars off the screen centre. */

.hud-dock--topCenter .hud-rail,
.hud-dock--bottomCenter .hud-rail { align-items: center; }

.hud-dock--bottomLeft .hud-rail {
  width: 100%;
  min-width: 0;
}

.hud-dock--bottomLeft .chat-wrap.hud-railed:not(.hud-layout-detached):not(.is-collapsed) {
  width: min(var(--chat-w, calc(340px * var(--ui-scale))), 100%);
  min-width: 0;
  max-width: 100%;
}

/* A railed widget contributes height and nothing else — the rail owns where it
   lands, so any anchor the widget declared for itself is dropped here. */

.hud-railed:not(.hud-layout-detached) {
  position: relative;
  top: auto;
  left: auto;
  right: auto;
  bottom: auto;
  transform: none;
  flex: 0 0 auto;
}

/* One member of a top rail may give ground when the rail meets its floor: the
   quest tracker is the only one that grows with what the player is following —
   every other member is a fixed shell — so it is the one that yields, and it
   scrolls what no longer fits. */

.hud-dock-row--top .hud-rail
  > [data-hud-widget="questTracker"].hud-railed:not(.hud-layout-detached) {
  flex-shrink: 1;
  min-block-size: 0;
}

.hud-layout-detached { position: fixed; }

.hud-layout-seat {
  flex: 0 0 auto;
  visibility: hidden;
  pointer-events: none;
}

.hud-safe-area-probe {
  position: fixed;
  inset: 0;
  z-index: -1;
  padding:
    var(--safe-top)
    var(--safe-right)
    var(--safe-bottom)
    var(--safe-left);
  visibility: hidden;
  pointer-events: none;
}

.overlay button,
.overlay input,
.inv-cell,
.npc-menu-item,
.hot-slot { pointer-events: auto; cursor: var(--cursor-action); }

.panel-box {
  background: rgba(10, 14, 20, 0.82);
  border: 1px solid var(--ui-line-soft);
  border-radius: 8px;
}

/* Status panel (top-left) */

/* ===== Shared HUD materials ============================================
   HUD pieces share quiet planes and hairlines. Identity comes from layout,
   type, icon art, and state accents; the world remains the largest surface. */

:root {
  /* Cast-danger tier accents. One table, read by every surface that draws a
     windup — the player's own cast ribbon, the focused target's cast row, and
     the nameplate cast row — so a tier carries the same colour wherever the
     player meets it. Each surface derives its own bright variant from these;
     there is no second table to keep in step. */
  --cast-routine: #9ba7b8;
  --cast-strong: #d7aa38;
  --cast-signature: #db8cff;
  --cast-critical: #f05c50;
  /* The one track, fill and label ink every gauge in the HUD draws with. The box
     they sit in is the shared `.hud-gauge`. */
  --hud-vitals-track: rgba(5, 9, 12, 0.82);
  --hud-sp-fill: linear-gradient(90deg, #276e8c, #4ba8c2);
  --hud-exp-fill: linear-gradient(180deg, #ffd98a, var(--accent) 60%, #b9822c);
  --hud-hp-ghost-fill: linear-gradient(90deg, #7a1f17, #a3392f);
  /* Health bands. There is no single health colour — health IS the band, and one
     table serves every gauge that shows it, so "this one dies next" is the same
     colour on the player's own bar, an ally's, the focused frame's and a plate's. */
  --hud-hp-healthy-fill: linear-gradient(180deg, #6ee06e, #3fae3f 55%, #2b8a2b);
  --hud-hp-hurt-fill: linear-gradient(180deg, #f2d268, #e0a020 55%, #b87a12);
  /* Hotter than hurt on purpose: on a plate the two sit one row apart, and
     "nearly dead" has to separate from "wounded" at a glance. */
  --hud-hp-execute-fill: linear-gradient(180deg, #ffa24a, #f0561f 55%, #b83a10);
  /* Carried-load bands. A bag the player is not paying for is pewter, not green:
     health owns green in this HUD, and a second green bar beside it would say
     "you're fine" in the vocabulary of a stat that means something else. Amber
     and danger then mean exactly one thing — this load is costing you movement. */
  --hud-load-light-fill: linear-gradient(180deg, #a3b0bb, #6c7c88 55%, #4d5b66);
  --hud-load-heavy-fill: linear-gradient(180deg, #f2d268, #e0a020 55%, #b87a12);
  --hud-load-overloaded-fill: linear-gradient(180deg, #ec8a72, var(--danger) 55%, #9c3a2c);
  /* The band's edge, brighter than its fill so the ring reads as an outline
     rather than a thicker part of the paint. `light` keeps the resting ring. */
  --hud-load-heavy-ring: #e8bb46;
  --hud-load-overloaded-ring: #ef7359;
  /* The same three bands as flat colour, for the hairline meters a collapsed rail
     draws instead of a gauge. A folded frame reads the band its open form would. */
  --hud-hp-healthy-line: #3fae3f;
  --hud-hp-hurt-line: #e0a020;
  --hud-hp-execute-line: #f0561f;
  --hud-hp-line: var(--hud-hp-healthy-line);
  --hud-vitals-text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85), 0 0 2px rgba(0, 0, 0, 0.6);
}

/* Any health hairline states its band the way a gauge fill does. All three are
   stated, including the default one: the property inherits, so a healthy hairline
   drawn inside a hurt one would otherwise take its neighbour's colour. */

[data-hp-band='healthy'] { --hud-hp-line: var(--hud-hp-healthy-line); }

[data-hp-band='hurt'] { --hud-hp-line: var(--hud-hp-hurt-line); }

[data-hp-band='execute'] { --hud-hp-line: var(--hud-hp-execute-line); }

.ront-frame {
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(7px * var(--ui-scale));
}

.ront-ring {
  border: 1px solid color-mix(in srgb, var(--ui-line) 76%, transparent);
  border-radius: 50%;
}

.status-panel {
/* The rail sizes to its widest widget, so the panel states its own width and
   only yields to a viewport too narrow to hold it. */
width: min(calc(252px * var(--ui-scale)), calc(100vw - 24px));
background: transparent;
border: 0;
border-radius: 0;
padding: calc(2px * var(--ui-scale)) 0;
font-size: calc(12px * var(--ui-scale));
box-shadow: none;
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.72));
}

.status-panel > * {
  position: relative;
  z-index: 1;
}

/* Character identity leads; faction outranks the smaller guild affiliation. */

.hud-self-top {
  margin-bottom: calc(4px * var(--ui-scale));
}

.hud-self-main { min-width: 0; display: flex; flex-direction: column; gap: calc(3px * var(--ui-scale)); }

.hud-self-header {
  display: grid; gap: calc(2px * var(--ui-scale)); min-width: 0;
}

.hud-character-line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: calc(6px * var(--ui-scale)); min-width: 0;
}

.hud-character-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); font-size: calc(15px * var(--ui-scale)); font-weight: 800; line-height: 1.15;
  text-shadow: 0 1px 2px #000, 0 0 6px rgba(0, 0, 0, 0.9);
}

.hud-character-level {
  display: inline-flex; align-items: baseline; gap: calc(3px * var(--ui-scale)); flex: 0 0 auto;
  color: var(--muted); font-size: calc(9px * var(--ui-scale)); font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px #000, 0 0 5px rgba(0, 0, 0, 0.9);
}

.hud-character-level strong { color: #ffe1a0; font-size: calc(11px * var(--ui-scale)); }

.hud-affiliations {
  display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
  align-items: center; gap: calc(5px * var(--ui-scale)); min-width: 0;
}

.hud-faction { min-width: 0; overflow: hidden; }

.hud-faction-chip { width: 100%; gap: calc(4px * var(--ui-scale)); }

.status-panel .hud-faction-chip .identity-icon--sm {
  width: calc(22px * var(--ui-scale)); height: calc(22px * var(--ui-scale));
  padding: calc(2px * var(--ui-scale));
}

.status-panel .hud-faction-chip .identity-primary {
  color: color-mix(in srgb, var(--text) 92%, var(--accent));
  font-size: calc(12px * var(--ui-scale)); font-weight: 800;
  text-shadow: 0 1px 2px #000, 0 0 5px rgba(0, 0, 0, 0.9);
}

.hud-guild {
  display: flex; align-items: center; gap: calc(4px * var(--ui-scale));
  min-width: 0; padding-left: calc(6px * var(--ui-scale));
  border-left: 1px solid color-mix(in srgb, var(--ui-line) 70%, transparent);
}

.hud-guild-emblem {
  width: calc(19px * var(--ui-scale)); height: calc(19px * var(--ui-scale)); flex: 0 0 auto;
  overflow: hidden; border: 1px solid rgba(197, 211, 232, 0.34); border-radius: calc(3px * var(--ui-scale));
  background: rgba(4, 7, 12, 0.78); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}

.hud-guild-emblem img { display: block; width: 100%; height: 100%; object-fit: cover; image-rendering: pixelated; }

.hud-guild-copy { display: grid; gap: calc(1px * var(--ui-scale)); min-width: 0; }

.hud-guild-label {
  color: color-mix(in srgb, var(--muted) 72%, transparent); font-size: calc(7px * var(--ui-scale));
  font-weight: 800; line-height: 1; letter-spacing: .08em; text-transform: uppercase;
}

.hud-guild-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--muted); font-size: calc(9px * var(--ui-scale)); font-weight: 650; line-height: 1.15;
  text-shadow: 0 1px 2px #000, 0 0 5px rgba(0, 0, 0, 0.9);
}

.hud-resource-bars { display: flex; flex-direction: column; gap: calc(2px * var(--ui-scale)); }

.hud-row { color: var(--muted); margin-top: 0; }

.hud-progression { margin-top: calc(3px * var(--ui-scale)); }

.hud-progress-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(5px * var(--ui-scale));
}

.hud-progress-label {
  color: var(--muted); font-size: calc(8px * var(--ui-scale)); font-weight: 800;
  text-transform: uppercase; letter-spacing: 0;
}

.hud-progress-value {
  color: color-mix(in srgb, var(--text) 78%, var(--muted));
  font-size: calc(9px * var(--ui-scale)); font-weight: 700; font-variant-numeric: tabular-nums;
}

/* The character's own vitals and progression are the shared `.hud-gauge`; only
   the fill colour is theirs. */

.hud-gauge-fill.sp { background: var(--hud-sp-fill); }

.hud-gauge-fill.exp { background: var(--hud-exp-fill); box-shadow: inset 0 1px 0 rgba(255, 240, 200, 0.5); }

.hud-economy {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: center; gap: calc(5px * var(--ui-scale));
  margin-top: calc(4px * var(--ui-scale)); padding-top: 0;
  border-top: 0;
}

.hud-economy > .hud-row {
  min-height: calc(20px * var(--ui-scale));
  padding: calc(2px * var(--ui-scale)) calc(5px * var(--ui-scale));
  background: linear-gradient(90deg, rgba(5, 9, 10, 0.52), rgba(5, 9, 10, 0.22));
  clip-path: polygon(0 0, 100% 0, calc(100% - 5px) 100%, 0 100%);
}

.hud-economy .hud-row:first-child {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--gold-value); font-weight: 800; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 1px #000;
}

/* Gold-coin money readout (matches the inventory footer). */

.hud-gold { display: flex; align-items: center; gap: calc(4px * var(--ui-scale)); }

.hud-coin-ico { display: inline-flex; flex: 0 0 auto; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5)); }

.hud-coin { width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale)); object-fit: contain; display: block; }

.hud-gold-val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Carry-weight readout: scale glyph + gauge (mirrors the coin + value layout). */

.hud-weight { display: flex; align-items: center; gap: calc(4px * var(--ui-scale)); min-width: 0; }

.hud-weight-ico { display: inline-flex; flex: 0 0 auto; color: #cdb98a; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5)); }

.hud-weight-ico img { width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale)); object-fit: contain; display: block; }

/* The load gauge is the shared `.hud-gauge`, sized and split exactly like the
   HP and SP rows above it, so the panel reads as one instrument. */

.hud-weight .hud-weight-gauge { flex: 1 1 0; width: auto; min-width: 0; }

/* Always-on party unit frames use a local readability scrim and the shared
   vitals tokens. Click targets; right-click is social/management only. */

/* The widget box is bigger than the frames in it — it reserves the trigger's
   corner and, docked, the whole rail slot. Only the frames themselves take the
   pointer, so a tap on the empty space around them reaches the world and walks
   the character. */

.party-frames {
  width: clamp(200px, calc(236px * var(--ui-scale)), 250px);
  pointer-events: none;
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  display: flex; flex-direction: column;
}

.pf-list { display: flex; flex-direction: column; gap: calc(4px * var(--ui-scale)); }

.pf-frame {
  position: relative; cursor: var(--cursor-action); text-align: left;
  pointer-events: auto;
  background: linear-gradient(90deg, rgba(7, 12, 17, 0.62), rgba(7, 12, 17, 0.28) 76%, transparent);
  border: 0; border-left: 1px solid color-mix(in srgb, var(--gold) 42%, transparent);
  padding: calc(5px * var(--ui-scale)) calc(7px * var(--ui-scale));
  display: flex; flex-direction: column; gap: calc(4px * var(--ui-scale));
  clip-path: polygon(0 0, calc(100% - 7px) 0, 100% 7px, 100% 100%, 7px 100%, 0 calc(100% - 7px));
  transition: border-color 0.12s, background-color 0.12s;
}

.pf-frame:hover { background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 12%, rgba(7, 12, 17, 0.72)), rgba(7, 12, 17, 0.3) 76%, transparent); }

/* The row is one press target, edge to edge: the gauge and the buff tiles inside
   it are read, never clicked past. Their own cursor would say the row stops at
   them. The tiles keep their hover card. */

.pf-frame .pf-chip { cursor: inherit; }

/* A helping aim is armed and this row can take it: the whole frame says so, so the
   press lands where the player is already looking. An out-of-reach or offline mate
   is no recipient, and the player's own row only lights when the ability can name
   its caster. */

.party-frames[data-aim='support'] .pf-frame:not(.pf-self):not(.pf-oor):not(.pf-offline),
.party-frames[data-aim='support'][data-aim-self='yes'] .pf-frame.pf-self {
  outline: 1px solid color-mix(in srgb, var(--accent) 62%, transparent);
  outline-offset: -1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 18%, rgba(7, 12, 17, 0.76)), rgba(7, 12, 17, 0.32) 76%, transparent);
}

/* The name inside is what takes focus, but the whole row is what the player is
   choosing, so the ring is drawn around all of it. */

.pf-frame:has(.pf-target:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }

.pf-frame.pf-self { box-shadow: inset calc(3px * var(--ui-scale)) 0 var(--accent); }

.pf-frame.pf-offline { opacity: 0.45; }

.pf-frame.pf-oor { opacity: 0.6; cursor: var(--cursor-default); }

.pf-frame.pf-dead { box-shadow: inset calc(3px * var(--ui-scale)) 0 var(--danger); filter: grayscale(0.6); }

.pf-frame.pf-lowhp { box-shadow: inset calc(3px * var(--ui-scale)) 0 var(--danger); animation: pf-pulse 1s ease-in-out infinite; }

@keyframes pf-pulse { 0%,100% { background: rgba(10,14,20,0.82); } 50% { background: rgba(74,24,24,0.7); } }

@media (prefers-reduced-motion: reduce) {
  .pf-frame.pf-lowhp { animation: none; }
}

body.reduce-flashing .pf-frame.pf-lowhp { animation: none; }

.pf-top { display: flex; align-items: center; gap: calc(8px * var(--ui-scale)); min-width: 0; }

.pf-meta {
  display: flex; align-items: center; justify-content: flex-end;
  gap: calc(5px * var(--ui-scale)); flex: 0 0 auto;
}

.pf-level,
.pf-state {
  color: var(--muted);
  font-size: max(9px, calc(9px * var(--ui-scale)));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.pf-state { color: color-mix(in srgb, var(--danger) 76%, var(--text)); }

.pf-leader {
  flex: none;
  padding: 0 calc(3px * var(--ui-scale));
  border: 0; border-left: 1px solid color-mix(in srgb, var(--gold) 72%, transparent);
  color: var(--accent);
  font-size: max(8px, calc(8px * var(--ui-scale)));
  font-weight: 800;
  text-transform: uppercase;
}

.pf-name {
  flex: 1; min-width: 0; font-size: max(11px, calc(12px * var(--ui-scale))); font-weight: 750;
  color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* An ally's health is the shared `.hud-gauge`; the rail only says which fill. */

.pf-frame.pf-lowhp .pf-hp .hud-gauge-fill { filter: saturate(1.2) brightness(1.08); }

.pf-dead .hud-gauge-label { color: #ffd0cc; text-transform: uppercase; }

/* An ally's buff/debuff strip. The tiles themselves are the shared status chip
   (see the status-chip rules beside the tray's own tiles); a party row only
   picks how much of one there is room for. */

.pf-buffs { display: flex; flex-wrap: wrap; gap: 2px; }

/* Each ally's name is the control that targets them. Keeping the button around
   the name instead of the whole row leaves the HP gauge a gauge and the status
   tiles tiles — a button flattens everything inside it into one label. */

.pf-target {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  color: inherit; font: inherit; text-align: left; cursor: inherit;
}

.pf-target:focus-visible { outline: none; }

/* Self frame's SP bar — a blue strip stacked under the HP bar. No ally SP is
   streamed, so only the local player's own frame renders one. */

.pf-sp { margin-top: calc(1px * var(--ui-scale)); }

.pf-sp .hud-gauge-fill { background: var(--hud-sp-fill); }

.party-focus {
  width: max(200px, calc(210px * var(--ui-scale)));
  pointer-events: auto;
  background: rgba(7, 12, 14, 0.76);
  border: 1px solid var(--ui-line-soft);
  border-radius: 5px;
  padding: calc(9px * var(--ui-scale));
  display: flex;
  flex-direction: column;
  gap: calc(5px * var(--ui-scale));
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.24);
}

.party-focus.pf-oor,
.party-focus.pf-offline { opacity: 0.72; }

.party-focus.pf-dead,
.party-focus.pf-lowhp { box-shadow: inset calc(3px * var(--ui-scale)) 0 var(--danger), 0 8px 20px rgba(0, 0, 0, 0.35); }

.party-focus:hover {
  filter: brightness(1.08);
}

.pf-focus-head {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  min-width: 0;
}

.pf-focus-copy { min-width: 0; flex: 1; }

.pf-focus-name {
  color: var(--text);
  font-size: max(11px, calc(12px * var(--ui-scale)));
  font-weight: 800;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pf-focus-meta,
.pf-focus-state {
  color: var(--muted);
  font-size: max(9px, calc(10px * var(--ui-scale)));
  line-height: 1.15;
}

.pf-focus-close,
.pf-focus-action {
  border: 1px solid #354050;
  background: rgba(19, 26, 36, 0.92);
  color: var(--text);
  border-radius: 5px;
  min-height: calc(22px * var(--ui-scale));
}

.pf-focus-close {
  width: calc(22px * var(--ui-scale));
  padding: 0;
  line-height: 1;
}

.pf-focus-close img {
  display: block;
  width: calc(16px * var(--ui-scale));
  height: calc(16px * var(--ui-scale));
  margin: auto;
  object-fit: contain;
}

.pf-focus-action {
  width: 100%;
  padding: calc(4px * var(--ui-scale)) calc(7px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 800;
}

.pf-focus-action:hover:not(:disabled),
.pf-focus-close:hover {
  border-color: var(--accent);
  background: rgba(27, 36, 48, 0.95);
}

.pf-focus-action:disabled {
  color: var(--muted);
  cursor: var(--cursor-default);
  opacity: 0.75;
}

/* Window-toggle dock — the single consolidated command bar that opens the
   standard windows. Its default bottom-right anchor shares the action bar's
   baseline and the minimap's right edge; player layout customization can detach
   it to any other saved position. */

.menu-buttons {
  position: fixed;
  right: var(--hud-safe-right);
  bottom: var(--hud-safe-bottom);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 6px;
  --menu-pad-block: calc(6px * var(--ui-scale));
  --menu-pad-inline: calc(6px * var(--ui-scale));
  padding: var(--menu-pad-block) var(--menu-pad-inline);
  pointer-events: auto;
  background: rgba(7, 12, 14, 0.72);
  border: 1px solid var(--ui-line-soft);
  border-radius: 7px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28);
}

/* Placement stays on `.menu-buttons` alone. Restating it here would carry two
   classes, tying with the rail's own two-class claim and winning on source order
   — the dock would keep `position: fixed` inside a rail, drop out of flow, and
   land at its static position below the screen edge. */

.menu-buttons.is-collapsible {
  gap: 0;
  overflow: visible;
  isolation: isolate;
  background: transparent;
  border: 0;
  box-shadow: none;
}

.menu-buttons.is-collapsible:not(.is-collapsed) {
  z-index: 48;
  background: transparent;
  border: 0;
  box-shadow: none;
}

/* The command panel is placed in dock-local pixels: JavaScript picks the corner it
   grows from and clamps it inside the visible viewport, so the panel never depends
   on a transition landing to sit where it belongs. */

.menu-buttons.is-collapsible .menu-items {
  position: absolute;
  top: var(--menu-panel-y, 0px);
  left: var(--menu-panel-x, 0px);
  z-index: 42;
  display: grid;
  grid-template-columns: repeat(var(--menu-panel-columns, 3), auto);
  align-items: center;
  justify-items: center;
  gap: 6px;
  /* Bounds only for a viewport no arrangement of the grid fits; unset otherwise,
     so nothing can clip a panel that had the room. */
  max-width: var(--menu-panel-max-width, none);
  max-height: var(--menu-panel-max-height, none);
  padding: var(--menu-pad-block) var(--menu-pad-inline);
  overflow: auto;
  overscroll-behavior: contain;
  background: rgba(7, 12, 14, 0.86);
  border: 1px solid var(--ui-line-soft);
  border-radius: 8px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42);
}

.menu-buttons[data-menu-open-x="left"][data-menu-open-y="up"] .menu-items {
  transform-origin: bottom right;
}

.menu-buttons[data-menu-open-x="left"][data-menu-open-y="down"] .menu-items {
  transform-origin: top right;
}

.menu-buttons[data-menu-open-x="right"][data-menu-open-y="up"] .menu-items {
  transform-origin: bottom left;
}

.menu-buttons[data-menu-open-x="right"][data-menu-open-y="down"] .menu-items {
  transform-origin: top left;
}

.menu-collapse-btn {
  z-index: 43;
}

.menu-items .menu-btn.has-attention {
  border-color: #f3c768;
  box-shadow:
    inset 0 0 0 1px rgba(255, 229, 157, 0.5),
    0 0 12px color-mix(in srgb, var(--gold) 48%, transparent),
    0 3px 7px rgba(0, 0, 0, 0.48);
  animation: menu-attention-breathe 1.35s ease-in-out infinite;
}

.menu-items .menu-btn.has-attention::after {
  content: '';
  position: absolute;
  inset: 3px;
  border: 1px solid rgba(255, 231, 166, 0.58);
  border-radius: 5px;
  pointer-events: none;
}

@keyframes menu-attention-breathe {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.18); }
}

.hud-layout-editing .hud-layout-widget {
  outline: 2px solid color-mix(in srgb, var(--gold) 85%, transparent);
  outline-offset: 3px;
  opacity: 0.72;
  cursor: move;
  pointer-events: auto !important;
  touch-action: none;
  user-select: none;
}

.hud-layout-editing .hud-layout-widget.hud-layout-dragging { cursor: grabbing; }

.hud-layout-editing .hud-layout-widget.hud-layout-custom {
  outline-color: var(--accent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--gold) 30%, transparent);
}

.hud-layout-editing .hud-layout-widget.hud-layout-keyboard-focus {
  outline: 3px solid rgba(74,163,255,0.9);
}

.hud-layout-editing.hud-layout-snap::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9;
  pointer-events: none;
  background-image:
    linear-gradient(color-mix(in srgb, var(--gold) 14%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--gold) 14%, transparent) 1px, transparent 1px);
  background-size: 8px 8px;
}

.hud-layout-handle {
  position: fixed;
  display: none;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  opacity: 0;
  pointer-events: none;
}

.hud-layout-bar {
  top: 12px;
  left: 50%;
  z-index: 63;
  transform: translateX(-50%);
  align-items: center;
  gap: 8px;
  padding: 5px 6px 5px 10px;
  pointer-events: auto;
  background: rgba(10,14,20,0.9);
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 8px;
  box-shadow: 0 10px 26px rgba(0,0,0,0.38);
}

.hud-layout-title {
  color: var(--text);
  font-size: 12px;
  font-weight: 800;
}

.hud-layout-action {
  border: 1px solid rgba(116,132,156,0.42);
  border-radius: 6px;
  background: rgba(255,255,255,0.04);
  color: var(--text);
  padding: 4px 9px;
  font-size: 12px;
  font-weight: 700;
}

.hud-layout-action:hover {
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  color: var(--accent);
}

.hud-layout-action:disabled,
.hud-layout-action[aria-disabled="true"] {
  cursor: var(--cursor-default);
  border-color: rgba(116,132,156,0.26);
  color: rgba(214,223,237,0.68);
  background: rgba(255,255,255,0.025);
}

.hud-layout-action:disabled:hover,
.hud-layout-action[aria-disabled="true"]:hover {
  border-color: rgba(116,132,156,0.26);
  color: rgba(214,223,237,0.68);
}

.hud-layout-action[aria-pressed="true"] {
  border-color: color-mix(in srgb, var(--gold) 75%, transparent);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  color: #ffe6a6;
}

.hud-layout-editing.hud-layout-precision .hud-layout-action[aria-pressed="true"] {
  border-color: rgba(74,163,255,0.78);
  background: rgba(74,163,255,0.13);
  color: #b8e4ff;
}

.hud-layout-action.primary {
  background: color-mix(in srgb, var(--gold) 18%, transparent);
  border-color: color-mix(in srgb, var(--gold) 75%, transparent);
  color: #ffe6a6;
}

.hud-layout-bar textarea[data-hud-layout-debug="fallback"] {
  width: min(52vw, 280px);
  height: 4.5rem;
  margin: 0;
  padding: 4px 6px;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 6px;
  background: rgba(10,14,20,0.92);
  color: var(--text);
  font-size: 10px;
  line-height: 1.3;
  pointer-events: auto;
}

.hud-layout-placeholder {
  min-width: 132px;
  min-height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5px 9px;
  border: 1px dashed color-mix(in srgb, var(--gold) 65%, transparent);
  border-radius: 7px;
  color: var(--accent);
  background: rgba(10,14,20,0.62);
  font-size: 11px;
  font-weight: 800;
}

/* Thin divider fencing the system/Options button off from the window toggles. It
   takes a row of its own so the commands above it keep the grid's columns. */

.menu-divider {
  grid-column: 1 / -1;
  justify-self: stretch;
  height: 1px;
  margin: 2px 4px;
  background: linear-gradient(90deg, transparent, rgba(109,124,146,0.62) 18%, rgba(109,124,146,0.62) 82%, transparent);
}

.menu-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  overflow: hidden;
  background: rgba(15, 23, 25, 0.72);
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--muted);
  cursor: var(--cursor-action);
  box-shadow: none;
  transition: color .12s ease, background-color .12s ease, border-color .12s ease, box-shadow .12s ease, transform .08s ease;
}

.menu-glyph {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 4px;
  background: transparent;
}

.menu-icon {
  display: block;
  width: 34px;
  height: 34px;
  object-fit: contain;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.75));
}

.menu-label {
  display: none;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  padding-right: calc(16px * var(--ui-scale));
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.1;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-btn:hover {
  color: var(--text);
  background: rgba(32, 44, 47, 0.9);
  border-color: color-mix(in srgb, var(--accent) 52%, transparent);
  box-shadow: 0 4px 10px rgba(0,0,0,0.24);
  transform: translateY(-1px);
}

.menu-btn:active {
  transform: translateY(0);
  background: linear-gradient(180deg, rgba(34,28,16,0.96), rgba(12,12,14,0.96));
}

.menu-btn[aria-pressed="true"] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, rgba(13, 20, 23, 0.92));
  border-color: color-mix(in srgb, var(--accent) 72%, transparent);
  box-shadow: inset 2px 0 var(--accent);
}

/* Hotkey badge: runtime layer, not baked into icon art. */

.menu-key {
  position: absolute;
  right: 2px;
  bottom: 2px;
  display: grid;
  place-items: center;
  min-width: 14px;
  height: 13px;
  padding: 0 3px;
  border: 1px solid rgba(0,0,0,0.7);
  border-radius: 4px;
  background: rgba(5,7,10,0.88);
  color: #e9edf4;
  font-size: calc(8px * var(--ui-scale));
  font-weight: 800;
  line-height: 1;
  text-shadow: 0 1px 1px #000;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 1px 2px rgba(0,0,0,0.5);
  pointer-events: none;
}

.menu-btn:hover .menu-key, .menu-btn[aria-pressed="true"] .menu-key {
  border-color: color-mix(in srgb, var(--gold) 72%, transparent);
  color: #ffe2a0;
}

.target-panel {
  background: rgba(10,14,20,0.82); border: 1px solid var(--ui-line-soft);
  border-radius: 6px; padding: 6px 16px;
}

.target-name { font-weight: 600; }

/* ===== Button kit ==========================================================
   The one button family for in-game surfaces — HUD widgets, service windows,
   dialogs, modal overlays. A button states a variant (what it does to the
   game), never its own paint: primary is brass and means "this is the way
   on", secondary is the matte control, ghost steps out of the way, danger
   spends or destroys. States (hover, press, focus, disabled, busy,
   armed-confirm) come with the class, so no surface restates them.
   Pre-game keeps its own .menu-button family by design; the hotbar slot is
   an action slot, not a button, and stays in its own grammar.
   Surface CSS may size or place a kit button, and may keep place behavior
   (the NPC talk's waiting breath); it may not re-paint one. */

.ron-btn {
  /* Kit-scoped values, set where they are consumed: the ink that sits on
     brass, and the pale edge a primary button carries. */
  --ron-ink: #1a1206;
  --ron-brass-edge: rgba(232, 207, 146, 0.72);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: calc(6px * var(--ui-scale));
  min-height: calc(30px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(14px * var(--ui-scale));
  background: var(--ui-control-fill);
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  color: var(--text);
  font-family: var(--font-ui);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  cursor: var(--cursor-action);
  position: relative;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}

.ron-btn:hover:not(:disabled):not([aria-disabled='true']):not(.is-loading):not([data-locked]) {
  background: var(--ui-control-fill-hover);
  border-color: var(--ui-control-line-hover);
}

.ron-btn:active:not(:disabled):not([aria-disabled='true']):not(.is-loading):not([data-locked]) {
  transform: translateY(var(--ui-press-travel, 1px));
}

.ron-btn:focus-visible {
  outline: none;
  box-shadow: var(--ui-focus-ring);
}

.ron-btn:disabled,
.ron-btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: var(--cursor-default);
}

/* Locked: gated by the game, not off. A dimmed button reads as broken; a
   dashed quiet one reads as "not yet" — the wait is the state, and it ends.
   Locked is an attribute, not a class, so the button's identity stays stable
   while the wait runs. */

.ron-btn[data-locked],
.ron-btn[data-locked]:disabled {
  opacity: 1;
  border-style: dashed;
  border-color: var(--ui-line);
  background: var(--ui-control-fill);
  color: var(--muted);
  box-shadow: none;
  cursor: var(--cursor-default);
}

/* --- variants ---------------------------------------------------------- */

.ron-btn--primary {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  border-color: var(--ron-brass-edge);
  color: var(--ron-ink);
  box-shadow: inset 0 1px 0 rgba(255, 247, 218, 0.22);
}

.ron-btn--primary:hover:not(:disabled):not([aria-disabled='true']):not(.is-loading):not([data-locked]) {
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  filter: brightness(1.07);
  border-color: var(--ron-brass-edge);
}

.ron-btn--ghost {
  background: transparent;
  border-color: var(--ui-control-line);
  color: var(--muted);
}

.ron-btn--ghost:hover:not(:disabled):not([aria-disabled='true']):not(.is-loading):not([data-locked]) {
  background: transparent;
  border-color: var(--accent);
  color: var(--text);
}

.ron-btn--danger {
  background: color-mix(in srgb, var(--danger) 16%, var(--ui-control-fill));
  border-color: color-mix(in srgb, var(--danger) 55%, transparent);
  color: #ffd9d0;
}

.ron-btn--danger:hover:not(:disabled):not([aria-disabled='true']):not(.is-loading):not([data-locked]) {
  background: color-mix(in srgb, var(--danger) 24%, var(--ui-control-fill));
  border-color: color-mix(in srgb, var(--danger) 72%, transparent);
}

/* --- sizes and shape ---------------------------------------------------- */

.ron-btn--sm {
  min-height: calc(22px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(9px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
}

.ron-btn--block { width: 100%; }

.ron-btn--pill { border-radius: 999px; }

/* --- states beyond rest --------------------------------------------------
   Busy: the press was taken and the game is working; the button stops
   answering without pretending the choice vanished. */

/* The label stays in place, turned invisible, so a button that starts or
   stops being busy does not shift under the cursor. */

.ron-btn.is-loading {
  pointer-events: none;
  color: transparent;
}

.ron-btn.is-loading::after {
  content: '';
  width: calc(12px * var(--ui-scale));
  height: calc(12px * var(--ui-scale));
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-top-color: var(--accent);
  animation: ui-spin 700ms linear infinite;
}

/* The one spin keyframe every spinner shares — buttons, grid loaders, pre-game
   menus, crest uploads. Identical private copies drift into divergent timing
   for the same motion. */

@keyframes ui-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .ron-btn.is-loading::after { animation-duration: 1600ms; }
}

body.reduce-motion .ron-btn.is-loading::after,
body.reduce-flashing .ron-btn.is-loading::after { animation-duration: 1600ms; }

/* Armed confirm: a destructive second press. The first press changed the
   button's meaning, not its shape — same button, now saying "sure?". */

.ron-btn.is-confirming {
  background: color-mix(in srgb, var(--danger) 14%, var(--ui-control-fill));
  border-color: color-mix(in srgb, var(--danger) 60%, transparent);
  color: #ffd0c8;
}

/* The key that presses this button, worn inside it. Place-specific keycaps
   (the hotbar badge, the menu-row key, the settings monospace label) are
   intentional presentations and stay where they live. */

.ron-btn__key {
  display: grid;
  place-items: center;
  min-width: calc(18px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  padding: 0 calc(4px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(4px * var(--ui-scale));
  background: rgba(5, 7, 10, 0.72);
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.ron-btn:hover .ron-btn__key,
.ron-btn:focus-visible .ron-btn__key {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--ui-control-line));
  color: var(--accent);
}

/* ===== Select kit ==========================================================
   The one dropdown family for in-game surfaces. Native <select> cannot wear
   Ront paint — the closed trigger can, the open list is OS chrome — so the
   kit owns both. A select states size and shape, never its own paint.
   The open list is a popover (fixed, above WINDOW_Z_CEIL) because a GameWindow
   clips overflow, isolates stacking, and is often a fixed containing block.
   Surface CSS may size or place a kit select; it may not re-paint one. */

.ron-select {
  display: inline-flex;
  min-width: 0;
  vertical-align: middle;
}

.ron-select--block { width: 100%; }

.ron-select__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  min-height: calc(30px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(28px * var(--ui-scale)) calc(6px * var(--ui-scale)) calc(10px * var(--ui-scale));
  background: var(--ui-control-fill);
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  color: var(--text);
  font-family: var(--font-ui);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
  cursor: var(--cursor-action);
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}

.ron-select__trigger::after {
  content: '';
  position: absolute;
  top: 50%;
  right: calc(10px * var(--ui-scale));
  width: 0;
  height: 0;
  border-left: calc(4px * var(--ui-scale)) solid transparent;
  border-right: calc(4px * var(--ui-scale)) solid transparent;
  border-top: calc(5px * var(--ui-scale)) solid var(--accent);
  transform: translateY(-35%);
  pointer-events: none;
}

.ron-select.is-open .ron-select__trigger::after {
  transform: translateY(-65%) rotate(180deg);
}

.ron-select__trigger:hover:not(:disabled):not([aria-disabled='true']),
.ron-select.is-open .ron-select__trigger:not(:disabled):not([aria-disabled='true']) {
  background: var(--ui-control-fill-hover);
  border-color: var(--ui-control-line-hover);
}

.ron-select__trigger:active:not(:disabled):not([aria-disabled='true']) {
  transform: translateY(1px);
}

.ron-select__trigger:focus-visible {
  outline: none;
  box-shadow: var(--ui-focus-ring);
}

.ron-select__trigger:disabled,
.ron-select__trigger[aria-disabled='true'] {
  opacity: 0.5;
  cursor: var(--cursor-default);
}

.ron-select.is-active .ron-select__trigger {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--ui-control-fill));
  color: var(--gold-label);
}

.ron-select--sm .ron-select__trigger {
  min-height: calc(22px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(22px * var(--ui-scale)) calc(3px * var(--ui-scale)) calc(9px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
}

.ron-select--sm .ron-select__trigger::after {
  right: calc(8px * var(--ui-scale));
}

.ron-select--pill .ron-select__trigger { border-radius: 999px; }

/* Z_POPOVER — above WINDOW_Z_CEIL and the system menu, with context menus. */

.ron-select__menu {
  position: fixed;
  z-index: 200;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: calc(2px * var(--ui-scale));
  max-height: calc(8 * 28px * var(--ui-scale));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(4px * var(--ui-scale));
  background: var(--ui-surface-raised);
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  box-shadow: var(--ui-shadow);
}

.ron-select__menu[hidden] { display: none; }

.ron-select__option {
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  background: transparent;
  border: 1px solid transparent;
  border-radius: calc(4px * var(--ui-scale));
  color: var(--text);
  font-family: var(--font-ui);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
  cursor: var(--cursor-action);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ron-select--sm .ron-select__option {
  padding: calc(4px * var(--ui-scale)) calc(6px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
}

.ron-select__option:hover:not(:disabled),
.ron-select__option.is-active:not(:disabled),
.ron-select__option.is-selected:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

.ron-select__option:focus-visible {
  outline: none;
  box-shadow: var(--ui-focus-ring);
}

.ron-select__option:disabled {
  opacity: 0.5;
  cursor: var(--cursor-default);
}

@media (prefers-reduced-motion: reduce) {
  .ron-select__trigger,
  .ron-select__trigger::after { transition: none; }
}

body.reduce-motion .ron-select__trigger,
body.reduce-motion .ron-select__trigger::after,
body.reduce-flashing .ron-select__trigger,
body.reduce-flashing .ron-select__trigger::after { transition: none; }

/* Generic windows */

/* The frame every functional surface on this plane wears. The death card is one
   of them and takes it here rather than restating it: a change to the fill, the
   hairline or the shadow must reach every window at once. */

.window,
.death-card,
.enhance-panel {
  overflow: hidden;
  isolation: isolate;
  background: var(--ui-frame-fill);
  border: 1px solid var(--ui-line-bright);
  border-radius: calc(9px * var(--ui-scale));
  box-shadow: var(--ui-frame-shadow);
}

.window {
  top: 80px; left: 50%; transform: translateX(-50%);
  width: 300px; flex-direction: column;
  pointer-events: auto;
}

/* The frame reads as a thin crafted object, not a flat black web card. */

.window::before,
.death-card::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background:
    radial-gradient(circle at 14% 0, var(--ui-frame-glow), transparent 28%),
    linear-gradient(90deg, rgba(255, 255, 255, 0.025), transparent 24%, transparent 76%, rgba(0, 0, 0, 0.12));
}

/* Distinct default anchors so Stats/Inventory/Skills don't pile on one spot when
   opened together (drag overrides + persists; makeDraggable only sets left/top
   when a saved position exists, otherwise these anchors hold). Right-anchored so
   they clear the top-left status panel and the top-right menu buttons. */

.window.win-stats  { top: 80px;  left: auto; right: 70px;  transform: none; }

.window.win-inv    { top: 130px; left: auto; right: 24px;  transform: none; width: min(calc(430px * var(--ui-scale)), calc(100vw - 32px)); }

.window.win-skills { top: 72px; left: 50%; right: auto; transform: translateX(-50%); width: min(1520px, calc(100vw - 24px)); max-width: calc(100vw - 24px); }

/* Both Adventurer tabs are single-focus master-detail layouts (a condensed list
   or a role rail + one tree), so the skills window stays narrow — far less width
   than the constellation graph it otherwise hosts. */

.window.win-skills:has(.skill-mode-adventurer) { width: min(520px, calc(100vw - 24px)); }

.window.win-bank { top: 185px; left: auto; right: 90px; transform: none; width: 320px; max-width: calc(100vw - 48px); }

.window.win-chat-rooms { top: 190px; left: 50%; transform: translateX(-50%); width: 460px; max-width: calc(100vw - 48px); }

.window.win-quests { top: 150px; left: auto; right: 150px; transform: none; width: 460px; max-width: calc(100vw - 48px); }

.window.win-achievements { top: 190px; left: auto; right: 180px; transform: none; width: 460px; max-width: calc(100vw - 48px); }

/* World map (M): centered, wide — the biggest "where am I" surface. */

.window.win-worldmap { top: 64px; left: 50%; right: auto; transform: translateX(-50%); width: min(600px, calc(100vw - 32px)); }

.win-worldmap .win-body { max-height: calc(100vh - 140px); }

/* Season ([): the war readout the HUD strip compresses, two Crowns side by side
   in map order. Narrow — it carries facts, not a map. */

.window.win-season { top: 88px; left: 50%; right: auto; transform: translateX(-50%); width: min(620px, calc(100vw - 32px)); }

.win-season .win-body { max-height: calc(100vh - 152px); overflow-y: auto; }

.season-body { display: grid; gap: calc(10px * var(--ui-scale)); }

.season-waiting { margin: 0; color: var(--muted, #9ba5b4); }

.season-head { display: grid; gap: 2px; }

.season-head h3 {
  margin: 0;
  color: #f7e3a5;
  font-family: var(--font-display);
  font-size: max(14px, calc(14px * var(--ui-scale)));
  font-weight: 850;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.season-began { margin: 0; color: var(--muted, #9ba5b4); font-size: max(11px, calc(11px * var(--ui-scale))); }

.season-crowns { display: grid; grid-template-columns: 1fr 1fr; gap: calc(10px * var(--ui-scale)); }

.season-crown {
  display: grid;
  align-content: start;
  gap: calc(5px * var(--ui-scale));
  border: 1px solid rgb(255 255 255 / 12%);
  border-top: 2px solid var(--crown, #caa14d);
  padding: calc(8px * var(--ui-scale));
  background: linear-gradient(160deg, color-mix(in srgb, var(--crown, #caa14d) 10%, rgb(8 12 18 / 62%)), rgb(8 12 18 / 34%));
}

.season-crown-head { display: flex; align-items: center; gap: calc(6px * var(--ui-scale)); min-width: 0; }

.season-crown-head .identity-chip,
.season-crown-head .identity-body { min-width: 0; }

/* Two Crown names have to share one row of cards, so the chip runs a size down
   here rather than clipping the name that says whose Core this is. The crest and
   the ring colour say which side a column is; no label repeats it. */

.season-crown-head .identity-primary { font-size: max(11px, calc(12px * var(--ui-scale))); }

/* One fact: label left, value right, in the same two columns on every row of
   both Crowns. That alignment is what lets an unknown value be a "?" sitting
   where the number would sit, instead of a missing row or a line of prose. */

.season-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(6px * var(--ui-scale));
  margin: 0;
  color: #cbd2dc;
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.season-row strong { color: var(--text, #fff); font-variant-numeric: tabular-nums; }

/* The row is uppercased for its label, and every value is a figure — until a
   reforming Core puts words in the value slot, where shouting them reads as a
   second label rather than as the reading it is. */

.season-row strong.season-reform { text-transform: none; letter-spacing: 0.01em; }

.season-row[title] { cursor: help; }

/* A value the Crown does not have. Purple is the informant's colour wherever a
   bought fact — or the absence of one — is drawn. */

.season-unknown { color: #c6a7de; }

.season-gauge { --gauge-ring: color-mix(in srgb, var(--crown, #caa14d) 45%, transparent); }

.season-gauge .hud-gauge-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--crown, #caa14d) 45%, #111923), var(--crown, #caa14d));
}

.season-reform { margin: 0; color: #8fb8d8; font-size: max(11px, calc(11px * var(--ui-scale))); font-variant-numeric: tabular-nums; font-weight: 800; }

.season-muster strong { color: var(--text, #fff); }

/* How long the paid reading beside it has left. Purple is the informant's colour
   wherever a bought fact is drawn. */

.season-intel-ends { color: #c6a7de; font-weight: 800; }

/* A reading nobody has paid for: the same bar as the Crown beside it, with its
   fill sealed over. The shape says which reading it is, the seal says the value
   is hidden, and the "?" in the row above says it in one glyph. Nothing here is
   spelled out in prose — what is hidden and who sells it lives in the hover. */

/* The seal is the way to Neris: pressing the thing that hides the number is how
   the number is bought, so nothing beside it has to say where readings come
   from. A button, therefore, not a label. */

.season-gauge.is-sealed {
  display: grid;
  place-items: center;
  /* The bar is a button because pressing it is how the reading is bought, but it
     has to measure as the gauge it replaces: a UA button brings its own box
     model, font and colour, and the gauge is sized in em, so left alone the
     sealed bar renders a different size from the bar beside it. */
  margin: 0;
  padding: 0;
  box-sizing: content-box;
  width: 100%;
  font: inherit;
  color: inherit;
  border-color: rgb(185 138 216 / 42%);
  background:
    repeating-linear-gradient(125deg, rgb(79 52 94 / 34%) 0 5px, transparent 5px 11px),
    var(--bg, #05080c);
  cursor: pointer;
}

.season-gauge.is-sealed:hover,
.season-gauge.is-sealed:focus-visible {
  border-color: rgb(203 164 228 / 78%);
  background:
    repeating-linear-gradient(125deg, rgb(107 71 127 / 46%) 0 5px, transparent 5px 11px),
    var(--bg, #05080c);
}

.season-gauge-seal {
  width: calc(15px * var(--ui-scale));
  height: calc(15px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 0 4px rgb(185 138 216 / 62%));
}

/* The paid-intelligence service is visible before a Crown owns a report. It is
   one compact route to the world NPC, not a second catalog or a dead purchase
   button: buying still happens by speaking to Neris Veyl on East Shardmarch. */

.season-intel-service {
  display: grid;
  grid-template-columns: calc(44px * var(--ui-scale)) minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  border: 1px solid rgb(185 138 216 / 38%);
  border-left: 3px solid #b98ad8;
  padding: calc(8px * var(--ui-scale)) calc(10px * var(--ui-scale));
  background:
    radial-gradient(circle at 6% 50%, rgb(185 138 216 / 16%), transparent 28%),
    linear-gradient(90deg, rgb(46 32 60 / 52%), rgb(8 12 18 / 34%));
}

.season-intel-service-mark {
  width: calc(40px * var(--ui-scale));
  height: calc(40px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / 72%));
}

.season-intel-service-copy { display: grid; gap: 1px; min-width: 0; }

.season-intel-service-copy p,
.season-intel-service-copy h4 { margin: 0; }

.season-intel-service-name {
  color: #c9a6e8;
  font-size: max(10px, calc(10px * var(--ui-scale)));
  font-weight: 900;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.season-intel-service-copy h4 {
  color: var(--gold-ink, #f7e3a5);
  font-family: var(--font-display);
  font-size: max(13px, calc(13px * var(--ui-scale)));
}

.season-intel-service-copy > p:last-child {
  color: #cbd2dc;
  font-size: max(11px, calc(11px * var(--ui-scale)));
  line-height: 1.3;
}

.season-intel-service-status {
  display: grid;
  justify-items: end;
  gap: 2px;
  border-inline-start: 1px solid rgb(185 138 216 / 28%);
  padding-inline-start: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.season-intel-service-status strong { color: #c9a6e8; font-size: max(11px, calc(11px * var(--ui-scale))); }

.season-intel-service-status span { color: var(--muted, #9ba5b4); font-size: max(10px, calc(10px * var(--ui-scale))); }

/* Bought intel wears the informant's own tint, so a figure the Crown paid for is
   never mistaken for the public read above it. */

.season-intel { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(6px * var(--ui-scale)); }

.season-intel-title {
  grid-column: 1 / -1;
  margin: 0;
  color: #c9a6e8;
  font-family: var(--font-display);
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-weight: 850;
  line-height: 1.2;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.season-intel-block {
  border: 1px solid rgb(255 255 255 / 12%);
  border-left: 2px solid #b98ad8;
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  background: linear-gradient(160deg, rgb(46 32 60 / 46%), rgb(8 12 18 / 34%));
}

.season-intel-block[data-product="armoryLedger"] { grid-column: 1 / -1; }

.season-intel-head { display: flex; align-items: baseline; gap: calc(6px * var(--ui-scale)); }

.season-intel-name {
  color: var(--gold-ink, #f7e3a5);
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-weight: 850;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.season-intel-ends {
  margin-inline-start: auto;
  color: #8fb8d8;
  font-size: max(10px, calc(10px * var(--ui-scale)));
  font-variant-numeric: tabular-nums;
  font-weight: 800;
}

.season-intel-rows { display: grid; gap: 1px; margin: calc(3px * var(--ui-scale)) 0 0; padding: 0; list-style: none; }

.season-intel-row {
  display: flex;
  align-items: baseline;
  gap: calc(6px * var(--ui-scale));
  color: #cbd2dc;
  font-size: max(11px, calc(11px * var(--ui-scale)));
}

.season-intel-row strong { margin-inline-start: auto; color: var(--text, #fff); font-variant-numeric: tabular-nums; }

.season-intel-row.is-muted { color: var(--muted, #9ba5b4); font-style: italic; }

.season-intel-armory {
  display: grid;
  width: max-content;
  max-width: 100%;
  gap: calc(3px * var(--ui-scale));
  margin-block-start: calc(5px * var(--ui-scale));
  margin-inline: auto;
}

.season-intel-armory-type {
  display: grid;
  grid-template-columns: calc(84px * var(--ui-scale)) minmax(0, 1fr);
  align-items: center;
  gap: calc(6px * var(--ui-scale));
}

.season-intel-type {
  overflow: hidden;
  color: #cbd2dc;
  font-size: max(10px, calc(10px * var(--ui-scale)));
  font-weight: 800;
  letter-spacing: 0.04em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.season-intel-cells {
  display: grid;
  grid-template-columns: repeat(5, calc(34px * var(--ui-scale)));
  gap: calc(3px * var(--ui-scale));
}

.season-intel-cell {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--rarity-color) 42%, var(--ui-line));
  background:
    linear-gradient(0deg, color-mix(in srgb, var(--rarity-color) 34%, transparent) 0 var(--intel-progress), transparent var(--intel-progress)),
    var(--ui-control-fill);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 7%);
}

.season-intel-cell img { width: 82%; height: 82%; object-fit: contain; opacity: 0.58; filter: saturate(0.7); }

.season-intel-cell.is-unlocked {
  border-color: color-mix(in srgb, var(--rarity-color) 82%, #fff);
  background: color-mix(in srgb, var(--rarity-color) 18%, var(--ui-control-fill));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 14%), 0 0 calc(5px * var(--ui-scale)) color-mix(in srgb, var(--rarity-color) 24%, transparent);
}

.season-intel-cell.is-unlocked img { opacity: 1; filter: none; }

/* The Crown's own forge, as a milestone read: how far each rarity has come. */

.season-armory { display: grid; gap: calc(4px * var(--ui-scale)); }

.season-armory-head { display: flex; align-items: baseline; gap: calc(6px * var(--ui-scale)); }

.season-armory-title {
  margin: 0;
  color: var(--gold-ink, #f7e3a5);
  font-family: var(--font-display);
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-weight: 850;
  line-height: 1.2;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.season-armory-total {
  margin-inline-start: auto;
  color: var(--text, #fff);
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-variant-numeric: tabular-nums;
  font-weight: 800;
}

.season-armory-rows { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; }

.season-armory-row {
  display: flex;
  align-items: baseline;
  gap: calc(6px * var(--ui-scale));
  color: #cbd2dc;
  font-size: max(11px, calc(11px * var(--ui-scale)));
}

.season-armory-rarity { color: var(--rarity-color, #cbd2dc); font-weight: 800; letter-spacing: 0.04em; }

.season-armory-count { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

/* The session's war feed, so the window answers "what happened" as well as
   "where do we stand". Newest first, and toned by the same intents the feed uses. */

.season-history { display: grid; gap: calc(4px * var(--ui-scale)); }

.season-history-title {
  margin: 0;
  color: var(--muted, #9ba5b4);
  font-family: var(--font-display);
  font-size: max(11px, calc(11px * var(--ui-scale)));
  font-weight: 850;
  line-height: 1.2;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.season-history-rows { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; }

.season-history-row {
  padding-inline-start: calc(8px * var(--ui-scale));
  border-inline-start: 2px solid var(--muted, #9ba5b4);
  color: #cbd2dc;
  font-size: max(11px, calc(11px * var(--ui-scale)));
  line-height: 1.35;
}

.season-history-row.is-positive { border-inline-start-color: var(--good, #63c08a); }

.season-history-row.is-negative { border-inline-start-color: var(--danger, #e0533d); }

.season-history-row.is-notable { border-inline-start-color: var(--gold-ink, #f7e3a5); }

body.high-contrast .season-crown { background: #0b1016; }

body.high-contrast .season-intel-block { background: #0b1016; }

body.high-contrast .season-intel-service { background: #0b1016; }

@media (max-width: 520px) {
  .season-intel { grid-template-columns: 1fr; }
  .season-intel-title,
  .season-intel-block[data-product="armoryLedger"] { grid-column: 1; }
  .season-intel-service { grid-template-columns: calc(36px * var(--ui-scale)) minmax(0, 1fr); }
  .season-intel-service-mark { width: calc(32px * var(--ui-scale)); height: calc(32px * var(--ui-scale)); }
  .season-intel-service-status { grid-column: 2; grid-row: 2; justify-items: start; border: 0; padding: 0; }
}

/* Combat meter fold. Its resting place is the gap between the bottom-centre
   action bars and the bottom-right window toggles, and that is all the fixed
   corner is for: the moment the meter is in a dock rail it stands in the rail
   like every other widget, and the player may park it on any edge. */

.combat-telemetry-fold.hud-fold-surface {
  --hud-fold-reserve-gap: 6px;
  position: fixed;
  right: calc(88px * var(--ui-scale));
  bottom: calc(12px * var(--ui-scale));
  left: auto;
  top: auto;
  z-index: 30;
  box-sizing: border-box;
  width: min(320px, calc(100vw - 220px * var(--ui-scale)));
  min-width: var(--hud-fold-shell-size, calc(44px * var(--ui-scale)));
  pointer-events: none;
  padding-top: var(--hud-fold-reserve-top);
  padding-bottom: var(--hud-fold-reserve-bottom);
  transition: none;
  --hud-fold-expanded-width: min(320px, calc(100vw - 220px * var(--ui-scale)));
  --hud-fold-expanded-min-width: var(--hud-fold-shell-size, calc(44px * var(--ui-scale)));
  --hud-fold-expanded-height: auto;
  --hud-fold-expanded-min-height: var(--hud-fold-shell-size, calc(44px * var(--ui-scale)));
  --ct-bar-0: #c9a45e;
  --ct-bar-1: #d85b48;
  --ct-bar-2: #6fd06a;
  --ct-bar-3: #3498db;
  --ct-bar-4: #b48ce0;
  --ct-bar-5: #ffb84d;
  --ct-bar-6: #9bd0ff;
  --ct-bar-7: #e0533d;
  --ct-bar-8: #d7aa38;
  --ct-bar-9: #db8cff;
  --ct-bar-10: #f05c50;
  --ct-bar-11: #9e7138;
  --ct-bar-12: #4aa86a;
  --ct-bar-13: #9ba7b8;
  --ct-bar-14: #e8c34a;
  --ct-bar-15: #c8821f;
}

/* A railed fold keeps its rail slot while folded, so its panel clips away with
   nothing moving underneath it. This one free-floats and has to hand the screen
   back, and a content-sized seat has no height a browser can interpolate:
   resizing the seat the moment the fold flips drops the bottom-anchored panel
   onto the trigger in a single frame, and only then plays the fade. So the seat
   waits. The panel clips into the trigger corner at full size, then the empty
   seat closes in one step, once there is nothing left to see move. Unfolding
   needs the room before the panel reveals, so it takes the seat back at once. */

.combat-telemetry-fold.hud-fold-surface.is-collapsed {
  padding-top: 0;
  padding-bottom: 0;
  transition-property: width, min-width, height, min-height, padding-top, padding-bottom;
  transition-duration: 0s;
  transition-delay: var(--hud-fold-duration);
  transition-behavior: allow-discrete;
}

.combat-telemetry-fold .hud-fold-surface-trigger {
  pointer-events: auto;
}

.hud-layout-editing .combat-telemetry-fold.hud-fold-surface {
  pointer-events: auto;
}

/* Host is display:contents so the trigger positions against the fold surface (stable corner). */

.combat-telemetry-fold-slot {
  display: contents;
}

.combat-telemetry-fold-icon { object-fit: contain; }

.combat-telemetry-panel {
  pointer-events: auto;
  position: relative;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: calc(8px * var(--ui-scale));
  padding: calc(10px * var(--ui-scale));
  height: auto;
  max-height: none;
  min-height: 0;
  overflow: visible;
  background: rgba(7, 12, 14, 0.72);
  border: 1px solid var(--grid-line);
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

.combat-telemetry-title {
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  color: var(--text);
  flex-shrink: 0;
}

.combat-telemetry-toolbar {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
}

.combat-telemetry-tabs {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: calc(2px * var(--ui-scale));
}

.combat-telemetry-tab {
  flex: 1 1 0;
  margin: 0;
  padding: calc(4px * var(--ui-scale)) calc(6px * var(--ui-scale));
  font: inherit;
  font-size: calc(11px * var(--ui-scale));
  color: var(--muted);
  background: var(--ui-control-fill);
  border: 1px solid var(--ui-control-line);
  border-radius: 4px;
  cursor: var(--cursor-action);
  white-space: nowrap;
}

.combat-telemetry-tab.is-selected,
.combat-telemetry-tab[aria-selected='true'] {
  color: var(--text);
  border-color: var(--accent);
  background: var(--ui-control-fill-hover);
}

.combat-telemetry-history {
  position: relative;
  flex: 0 1 9.5rem;
  max-width: 42%;
  min-width: 0;
  z-index: 2;
}

.combat-telemetry-list {
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  /* Reserve five bar rows (+ gaps); overflow scrolls beyond that. */
  height: calc(5 * 22px * var(--ui-scale) + 4 * 3px * var(--ui-scale));
  max-height: calc(5 * 22px * var(--ui-scale) + 4 * 3px * var(--ui-scale));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
  flex: 0 0 auto;
  touch-action: pan-y;
}

.combat-telemetry-empty {
  display: flex;
  align-items: center;
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  padding: 0 calc(6px * var(--ui-scale));
  background: transparent;
}

.combat-telemetry-bar-row {
  position: relative;
  flex: 0 0 auto;
  height: calc(22px * var(--ui-scale));
  background: var(--ui-control-fill);
  border-radius: 2px;
  overflow: hidden;
}

.combat-telemetry-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  opacity: 0.85;
  pointer-events: none;
}

.combat-telemetry-bar-label {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(8px * var(--ui-scale));
  height: 100%;
  padding: 0 calc(6px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  color: var(--text);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
  font-variant-numeric: tabular-nums;
}

.combat-telemetry-bar-name {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.combat-telemetry-bar-stats {
  flex-shrink: 0;
  white-space: nowrap;
}

.combat-telemetry-footer {
  border-top: 1px solid var(--grid-line);
  padding-top: calc(6px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  flex-shrink: 0;
}

.combat-telemetry-participant {
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  flex: 0 0 auto;
}

.combat-telemetry-bar-row.is-report-source { cursor: var(--cursor-action); }

.combat-telemetry-bar-row.is-pinned {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}

.combat-telemetry-breakdown {
  box-sizing: border-box;
  flex: 0 0 auto;
  margin-left: calc(8px * var(--ui-scale));
  max-height: calc(4 * 22px * var(--ui-scale) + 3 * 3px * var(--ui-scale));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  padding: calc(4px * var(--ui-scale));
  background: rgba(7, 12, 14, 0.55);
  border: 1px solid var(--accent);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  pointer-events: auto;
}

.combat-telemetry-breakdown[hidden] { display: none; }

.combat-telemetry-breakdown-list {
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  flex: 0 0 auto;
  min-height: 0;
}

.combat-telemetry-breakdown-empty {
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale)) 0;
}

.combat-telemetry-breakdown-leading {
  display: flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  min-width: 0;
}

.combat-telemetry-breakdown-icon {
  width: calc(14px * var(--ui-scale));
  height: calc(14px * var(--ui-scale));
  flex-shrink: 0;
  object-fit: contain;
}

.worldmap-body { display: flex; flex-direction: column; gap: calc(10px * var(--ui-scale)); }

/* A height-capped flex column: never crush the overview/legend rows — the
   detail canvas (object-fit: contain) gives up height first instead. */

.worldmap-body > * { flex-shrink: 0; }

/* One row above the frame: the way back out, then the name of whatever the
   frame is showing. Above rather than over, because the hub's own plates now
   reach the top of the frame and a floating header lands on them. */

.worldmap-bar { display: flex; align-items: center; gap: calc(8px * var(--ui-scale)); min-height: calc(22px * var(--ui-scale)); }

/* The map stage: one canvas showing either the season's travel hub or one place
   filling the frame, with a transparent %-positioned button per plate overlaid
   for click/keyboard/SR at hub level. Those buttons are positioned in percent of
   the stage, so the stage and the bitmap must stay the same shape: the height
   budget is spent as a max-width (the frame's own ratio, set on the element),
   never as a max-height on the canvas — capping height alone leaves an off-ratio
   box with the drawing letterboxed inside it and every plate button off its
   plate. */

.worldmap-stage {
  position: relative;
  max-width: calc((100vh - 250px) * var(--worldmap-aspect, 1.49));
  margin-inline: auto;
}

.worldmap-canvas {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--grid-line);
  border-radius: 4px;
  background: var(--bg);
}

.worldmap-plates { position: absolute; inset: 0; }

.worldmap-body[data-level='place'] .worldmap-plates { display: none; }

.worldmap-plate-hit {
  position: absolute;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: var(--cursor-action);
  border-radius: 4px;
  transition: background 90ms ease-out;
}

.worldmap-plate-hit:hover { background: rgba(255, 255, 255, 0.08); }

.worldmap-plate-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Back to the hub. Only a zoomed-in map has somewhere to go back to, and Escape
   does the same thing, so the control wears that key. */

.worldmap-back {
  display: none;
  flex: none;
  align-items: center;
  gap: 5px;
  padding: calc(2px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border: 1px solid var(--grid-line);
  border-radius: 4px;
  background: rgba(9, 12, 18, 0.86);
  color: var(--text);
  font-size: calc(11px * var(--ui-scale));
  cursor: var(--cursor-action);
}

.worldmap-body[data-level='place'] .worldmap-back { display: inline-flex; }

.worldmap-back::before {
  content: '';
  border-right: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

.worldmap-back:hover { border-color: var(--accent); color: var(--accent); }

.worldmap-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

[data-ui-profile="handheld"] .worldmap-back kbd { display: none; }

.worldmap-back kbd {
  padding: 0 4px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.1);
  color: var(--muted);
  font: inherit;
  font-size: calc(9px * var(--ui-scale));
}

/* The name of what the frame is showing, beside the back control. */

.worldmap-zone-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.worldmap-zone-name { font-weight: 700; color: var(--text); text-shadow: 0 1px 2px rgba(0,0,0,0.8); }

.worldmap-zone-here {
  padding: 0 5px;
  border-radius: 3px;
  background: var(--accent);
  color: #181008;
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700;
  line-height: calc(14px * var(--ui-scale));
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* What this place has waiting, worn beside its name at the size the plaza wears
   it over a head. */

.worldmap-zone-mark {
  width: calc(18px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  object-fit: contain;
}

.worldmap-zone-pvp {
  padding: 0 5px;
  border-radius: 3px;
  background: var(--danger);
  color: #fff;
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700;
  line-height: calc(14px * var(--ui-scale));
}

.worldmap-zone-level {
  padding: 0 5px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.55);
  color: var(--info);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700;
  line-height: calc(14px * var(--ui-scale));
  white-space: nowrap;
}

.worldmap-remote { color: var(--info); font-size: calc(11px * var(--ui-scale)); }

/* Legend: the marker language, spelled out once (teaches the radar too). */

.worldmap-legend { display: flex; flex-wrap: wrap; gap: calc(10px * var(--ui-scale)); color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.worldmap-legend-item { display: inline-flex; align-items: center; gap: 4px; }

.worldmap-marker-art { width: 13px; height: 13px; display: inline-block; object-fit: contain; }

/* The two road strokes at legend size: a headed gold line for a trip this
   character may take, a dashed grey one for the Warper that refuses them. */

.worldmap-legend-road,
.worldmap-legend-road-invade,
.worldmap-legend-road-blocked {
  position: relative;
  width: 20px;
  height: 2px;
  display: inline-block;
  background: rgba(228, 178, 70, 0.92);
}

.worldmap-legend-road-invade { background: rgba(224, 83, 61, 0.95); }

.worldmap-legend-road::after,
.worldmap-legend-road-invade::after {
  content: '';
  position: absolute;
  right: -1px;
  top: -3px;
  border-left: 6px solid rgba(228, 178, 70, 0.92);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

.worldmap-legend-road-invade::after { border-left-color: rgba(224, 83, 61, 0.95); }

.worldmap-legend-road-blocked {
  height: 0;
  border-top: 1px dashed rgba(138, 147, 163, 0.7);
  background: none;
}

.worldmap-foot { display: flex; align-items: flex-start; justify-content: space-between; gap: calc(14px * var(--ui-scale)); color: var(--muted); font-size: calc(11px * var(--ui-scale)); border-top: 1px solid var(--grid-line); padding-top: calc(6px * var(--ui-scale)); }

/* Ruled off the authored line beside it: without the rule the coordinates read
   as the next words of the sentence they sit against. */

.worldmap-foot-pos { color: var(--text); white-space: nowrap; border-left: 1px solid var(--grid-line); padding-left: calc(10px * var(--ui-scale)); }

.worldmap-foot-pos:empty { display: none; }

.window.win-cash-shop { top: 155px; left: auto; right: 190px; transform: none; width: 420px; max-width: calc(100vw - 48px); }

.window.win-mail { top: 210px; left: auto; right: 210px; transform: none; width: 440px; max-width: calc(100vw - 48px); }

.window.win-item-services { top: 250px; left: auto; right: 170px; transform: none; width: 440px; max-width: calc(100vw - 48px); }

.window.win-party,
.window.win-friends { width: 340px; max-width: calc(100vw - 48px); }

.window.win-party .win-body,
.window.win-friends .win-body,
.window.win-guild .win-body { max-height: min(72vh, 560px); }

.window.win-system-utility { top: clamp(84px, 14vh, 120px); left: auto; right: clamp(24px, 8vw, 130px); transform: none; width: 460px; max-width: calc(100vw - 48px); }

.window.win-system-utility .win-body { max-height: min(72vh, 560px); }

/* Window titles sit on the same working plane as their content. */

.win-head {
  position: relative;
  display: flex; justify-content: space-between; align-items: center; gap: calc(10px * var(--ui-scale));
  padding: 0 calc(9px * var(--ui-scale)) 0 calc(16px * var(--ui-scale));
  height: calc(40px * var(--ui-scale)); flex: 0 0 auto;
  font-family: var(--font-ui);
  font-weight: 760; letter-spacing: 0.18px;
  font-size: calc(13px * var(--ui-scale)); color: var(--text);
  text-shadow: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.008));
  box-shadow: none;
}

/* The tick that marks a title, and the divider under it. The death card's header
   is one of these headers and paints from the same two rules; only the tone
   variables and the tick's own height differ there. */

.win-head::before,
.death-card__head::before {
  content: '';
  position: absolute;
  left: calc(8px * var(--ui-scale));
  width: calc(2px * var(--ui-scale));
  height: calc(14px * var(--ui-scale));
  border-radius: 999px;
  background: var(--ui-tick-fill);
  box-shadow: 0 0 calc(8px * var(--ui-scale)) var(--ui-tick-glow);
}

.win-head::before {
  top: 50%;
  transform: translateY(-50%);
}

/* One neutral divider separates controls from the working plane. */

.win-head::after,
.death-card__head::after {
  content: ''; position: absolute; left: calc(10px * var(--ui-scale)); right: calc(10px * var(--ui-scale));
  bottom: 0; height: 1px;
  background: linear-gradient(90deg, var(--ui-head-rule-lead), var(--ui-line-soft) 18%, var(--ui-line-soft) 82%, transparent);
}

/* Title text. */

.win-head > span:first-child {
  display: inline-flex; align-items: center; gap: calc(9px * var(--ui-scale)); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Close art sits on a quiet matte control clear of the frame corner. */

.win-close {
  position: relative; flex: 0 0 auto; padding: 0;
  width: calc(26px * var(--ui-scale)); height: calc(26px * var(--ui-scale));
  border-radius: 5px; cursor: var(--cursor-action); color: transparent; font-size: 0;
  background-color: var(--ui-control-fill);
  background-image: url('/play/assets/hud-close-DhU3PbeL.webp');
  background-position: center;
  background-repeat: no-repeat;
  background-size: clamp(20px, calc(20px * var(--ui-scale)), 30px) clamp(20px, calc(20px * var(--ui-scale)), 30px);
  border: 1px solid var(--ui-control-line);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
  transition: border-color .12s, background .12s, box-shadow .12s, transform .06s;
}

.win-close:hover {
  border-color: rgba(224,83,61,0.85);
  background-color: #351d1a;
  background-image: url('/play/assets/hud-close-DhU3PbeL.webp');
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
}

.win-close:active { transform: scale(0.9); }

.win-close:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.win-body {
  padding: calc(14px * var(--ui-scale)) calc(16px * var(--ui-scale)) calc(16px * var(--ui-scale));
  max-height: min(68vh, calc(720px * var(--ui-scale)));
  overflow-y: auto;
}

.bank-summary { display: grid; gap: 6px; margin-bottom: 10px; }

.bank-summary-row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 7px 8px; border: 1px solid var(--ui-line-soft); border-radius: 6px; background: rgba(255,255,255,0.035);
}

.bank-summary-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }

.bank-summary-value { color: var(--text); font-size: 13px; font-variant-numeric: tabular-nums; }

.bank-amount { display: grid; gap: 5px; margin-bottom: 8px; color: var(--muted); font-size: 11px; }

.bank-amount input { width: 100%; font-variant-numeric: tabular-nums; }

.bank-quick {
  display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px;
}

/* The quick chips count money; their digits stay tabular on the kit button. */

.bank-quick .ron-btn { font-variant-numeric: tabular-nums; }

.bank-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }

.storage-sec-card,
.storage-sec-status {
  padding: 8px 9px; border: 1px solid var(--ui-line-soft); border-radius: 6px;
  background: rgba(255,255,255,0.035); color: var(--text); font-size: 12px;
}

.storage-sec-status { margin-top: 7px; color: var(--muted); }

.storage-log-list { display: grid; gap: 5px; max-height: 240px; overflow-y: auto; }

.storage-log-row {
  display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 3px 8px;
  align-items: baseline; padding: 7px 8px; border: 1px solid var(--ui-line-soft); border-radius: 6px;
  background: rgba(255,255,255,0.03);
}

.storage-log-put { color: #7fdc8c; font-weight: 700; }

.storage-log-take { color: #e0b86b; font-weight: 700; }

.storage-log-item { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.storage-log-meta { grid-column: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 11px; }

.storage-log-empty { padding: 8px 9px; color: var(--muted); border: 1px dashed var(--ui-control-line); border-radius: 6px; }

.sw-head { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; }

.sw-tabs { display: flex; flex: 0 0 auto; gap: 0; margin-bottom: 10px; border-bottom: 1px solid var(--ui-line-soft); }

.sw-tab {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 30px; padding: 5px 10px; border: 0; border-bottom: 2px solid transparent;
  border-radius: 0; background: transparent; color: var(--muted); cursor: var(--cursor-action);
}

.sw-tab:hover { color: var(--text); background: rgba(255,255,255,0.025); }

.sw-tab[aria-selected="true"] { border-bottom-color: var(--accent); background: rgba(201,164,94,0.055); color: var(--text); }

.sw-tab-count {
  min-width: 1.4em; padding: 1px 5px; border-radius: 999px;
  background: rgba(255,255,255,0.06); color: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums;
}

.sw-tab[aria-selected="true"] .sw-tab-count { background: rgba(224,184,107,0.16); color: var(--gold-ink); }

.sw-tab:focus-visible { outline: none; box-shadow: inset var(--ui-focus-ring); }

/* A face may carry reviewed art before its word. The art is the tab's identity,
   so a narrow frame drops the word and keeps the picture; a tab with no art
   keeps its word at every width, because an unlabelled blank is not a tab. */

.sw-tab-ico { display: block; width: 18px; height: 18px; object-fit: contain; opacity: 0.72; }

.sw-tab[aria-selected="true"] .sw-tab-ico { opacity: 1; }

@media (max-width: 520px) {
  .sw-tab:has(.sw-tab-ico) { padding-inline: 8px; }
  .sw-tab:has(.sw-tab-ico) .sw-tab-label { display: none; }
}

.sw-pane[hidden] { display: none; }

.sw-summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  margin-bottom: 8px; padding: 0 1px 8px; border-bottom: 1px solid var(--ui-line-soft);
  background: transparent; color: var(--muted); font-size: 12px;
}

.sw-summary-strong { color: var(--text); font-weight: 700; }

.sw-summary-sep { opacity: 0.55; }

.sw-toolbar { display: flex; gap: 5px; margin-bottom: 8px; }

/* The filter field's layout seat — its paint is the shared sw-input field. */

.sw-filter { min-width: 0; flex: 1 1 auto; height: calc(28px * var(--ui-scale)); }

.sw-section { margin-top: 10px; }

.sw-section-title {
  margin-bottom: 6px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px;
}

.sw-list {
  display: grid;
  gap: 0;
  max-height: calc(260px * var(--ui-scale));
  overflow-y: auto;
  border: calc(1px * var(--ui-scale)) solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.34);
}

.sw-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: calc(7px * var(--ui-scale)); align-items: center;
  min-height: calc(44px * var(--ui-scale)); padding: calc(7px * var(--ui-scale)) calc(8px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border: 0; border-bottom: calc(1px * var(--ui-scale)) solid var(--ui-line-soft); border-left: calc(2px * var(--ui-scale)) solid transparent;
  border-radius: 0; background: transparent; color: var(--text); font-size: calc(12px * var(--ui-scale)); cursor: var(--cursor-action);
}

.sw-row:last-child { border-bottom: 0; }

.sw-row.off { color: var(--muted); background: rgba(255,255,255,0.012); }

.sw-row.is-self { border-left-color: var(--accent); background: rgba(201,164,94,0.065); }

.sw-row.is-dead { border-left-color: var(--danger); }

.sw-row:hover { background: rgba(255,255,255,0.045); }

.sw-row:focus-visible, .sw-tab:focus-visible, .sw-more:focus-visible, .sw-filter:focus-visible {
  outline: none; box-shadow: var(--ui-focus-ring);
}

.sw-dot { width: calc(8px * var(--ui-scale)); height: calc(8px * var(--ui-scale)); border-radius: 999px; background: #8a93a3; flex: 0 0 auto; }

.sw-dot.on { background: var(--good); box-shadow: 0 0 0 calc(2px * var(--ui-scale)) rgba(111, 208, 106, 0.11); }

.sw-dot.off { background: #8a93a3; }

.sw-id { min-width: 0; display: grid; gap: 2px; }

.sw-name, .sw-meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sw-meta { color: var(--muted); font-size: calc(11px * var(--ui-scale)); }

.sw-you {
  margin-left: 4px; padding: 0 4px; border: 1px solid rgba(224,184,107,0.35); border-radius: 4px;
  color: var(--gold-ink); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px;
}

.sw-presence {
  justify-self: end; color: var(--muted); font-size: 10px; text-transform: lowercase;
  white-space: nowrap;
}

.sw-presence.is-online { color: #8fdb8f; }

.sw-more {
  width: auto; min-width: 26px; height: 26px; padding: 0 7px; border: 1px solid var(--ui-control-line); border-radius: 5px;
  background: var(--ui-control-fill); color: var(--accent); font-size: 10px; font-weight: 700; line-height: 1; cursor: var(--cursor-action);
}

.sw-more:hover { border-color: var(--ui-control-line-hover); color: var(--gold-ink); background: var(--ui-control-fill-hover); }

.sw-edge { width: auto; min-width: 26px; height: 26px; padding: 0 7px; border: 1px solid #3b4657; border-radius: 6px; background: rgba(255,255,255,0.04); color: #e0b86b; font-size: 10px; font-weight: 700; cursor: var(--cursor-action); }

.sw-edge:hover { border-color: var(--accent); color: var(--gold-ink); }

.sw-actions { display: flex; gap: 5px; margin-top: 0; }

.sw-actions input { min-width: 0; flex: 1 1 auto; height: 30px; box-sizing: border-box; padding: 5px 8px; border: 1px solid var(--ui-control-line); border-radius: 6px; background: var(--ui-control-fill); color: var(--text); font: inherit; font-size: 12px; }

.sw-actions input:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.sw-empty { padding: 14px 12px; color: var(--muted); border: 0; border-radius: 0; }

.sw-empty-title { color: var(--text); font-size: 12px; font-weight: 600; }

.sw-empty-hint { margin-top: 4px; font-size: 11px; line-height: 1.35; }

.sw-sub { min-width: 0; display: flex; justify-content: space-between; gap: 8px; color: var(--muted); font-size: 11px; }

.sw-badge { justify-self: end; padding: calc(1px * var(--ui-scale)) calc(5px * var(--ui-scale)); border: calc(1px * var(--ui-scale)) solid var(--ui-control-line); border-radius: calc(4px * var(--ui-scale)); color: var(--muted); font-size: calc(9px * var(--ui-scale)); text-transform: uppercase; }

.sw-badge.lead { color: var(--gold-ink); border-color: color-mix(in srgb, var(--gold-ink) 35%, transparent); }

/* A downed unit reads the one danger red everywhere it is named — painted
   inline at the roster's row, badge and dot (DANGER_COLOR) so every palette
   mode reaches it; the dot's glow geometry rides the same inline paint. */

.party-roster, .friends-roster { max-height: calc(240px * var(--ui-scale)); }

.party-member-row { grid-template-columns: auto minmax(0, 1fr) auto auto; }

.friends-row { grid-template-columns: auto minmax(0, 1fr) auto; }

.party-share {
  display: grid; gap: 4px; margin: 10px 0 0; padding: 9px 1px 0;
  border-top: 1px solid var(--ui-line-soft); background: transparent;
}

.party-toggle-row { display: flex; align-items: center; gap: 6px; margin-top: 6px; color: var(--text); font-size: 12px; }

.party-toggle-row.is-disabled { opacity: 0.55; }

/* The party is under exactly one loot rule, so the two choices are radios in one
   group, each stating what it does under its own name. Doc "Party Loot". */

.party-loot-rule { display: grid; gap: 2px; margin: 8px 0 0; padding: 0; border: 0; }

.party-loot-rule legend { padding: 0; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }

.party-loot-rule-row { align-items: flex-start; }

.party-loot-rule-row input { margin-top: 2px; }

.party-loot-rule-row span { display: grid; gap: 1px; }

.party-loot-rule-hint { color: var(--muted); font-size: 11px; }

.party-prefs { margin-top: 10px; padding: 8px 1px 0; border-top: 1px solid var(--ui-line-soft); background: transparent; }

.party-invite-section { margin-top: 10px; }

.guild-roster { max-height: calc(260px * var(--ui-scale)); }

.window.win-guild { width: 360px; max-width: calc(100vw - 48px); }

.window.win-guild .sw-tabs { overflow-x: auto; padding-bottom: 2px; }

.window.win-guild .sw-tab { flex: 0 0 auto; }

.window.win-guild .sw-card { display: grid; gap: 5px; padding: 8px 10px; border: 1px solid var(--ui-line-soft); border-radius: 6px; background: rgba(255,255,255,0.025); }

.window.win-guild .sw-stat { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }

.window.win-guild .sw-stat-label { color: var(--muted); }

.window.win-guild .sw-stat-val { min-width: 0; color: var(--text); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.guild-sort { display: flex; gap: 4px; margin-left: auto; }

.guild-actions { display: flex; justify-content: flex-end; margin-top: 10px; }

.guild-notice-form,
.guild-disband-form { display: grid; gap: 6px; padding: 8px 10px; }

.guild-notice-form input,
.guild-notice-form textarea,
.guild-disband-form input,
.guild-create-form input { min-width: 0; padding: 5px 7px; border: 1px solid var(--ui-control-line); border-radius: 6px; background: var(--panel-2); color: var(--text); font-size: 12px; }

.guild-notice-form textarea { resize: vertical; }

.guild-member-row { grid-template-columns: auto minmax(0, 1fr) auto; }

.guild-member-row.is-managed { grid-template-columns: auto minmax(0, 1fr) auto; }

/* Placement only for the kit Leave button; its look is the button kit's. */

.party-prefs + .ron-btn { margin-top: calc(8px * var(--ui-scale)); }

.sysutil-status {
  padding: 8px 9px; border: 1px solid var(--ui-line-soft); border-radius: 6px;
  background: rgba(255,255,255,0.035); color: var(--text); font-size: 12px;
}

.sysutil-section { margin-top: 10px; }

.sysutil-list { display: grid; gap: 5px; max-height: 150px; overflow-y: auto; }

.sysutil-row {
  padding: 7px 8px; border: 1px solid var(--ui-line-soft); border-radius: 6px;
  background: rgba(255,255,255,0.03); color: var(--text); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.sysutil-empty { padding: 8px 9px; color: var(--muted); border: 1px dashed var(--ui-control-line); border-radius: 6px; }

/* Inventory window body: a plain vertical stack (equip paperdoll, bag header, bag
   grid, footer). NOTE: the body itself must NOT be display:grid — only the inner
   .inv-grid is the icon grid. (A stray inv-grid on the body forced every section
   into 46px columns.) */

.inv-window { display: flex; flex-direction: column; gap: 8px; }

/* Only the BAG GRID scrolls — the equipment paperdoll, search and footer stay
   pinned. The body becomes a fixed-height flex column (no body scroll); the grid
   takes the remaining space and scrolls inside it. */

.win-inv .win-body {
  max-height: 74vh;
  overflow: hidden;
  padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale));
}

.inv-window .inv-grid {
  flex: 1 1 auto; min-height: calc(148px * var(--ui-scale)); overflow-y: auto; align-content: start;
  padding: 4px 6px 4px 0;
}

/* Inventory bag — MMO icon-cell grid */

.inv-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(52px * var(--ui-scale)), 1fr));
  gap: 6px; justify-content: start; box-sizing: border-box;
}

.inv-cell {
  position: relative; width: 100%; min-width: 0; aspect-ratio: 1; min-height: 46px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, #1b222d, #141a23);
  border: 1px solid var(--ui-control-line); border-radius: 6px; cursor: var(--cursor-action);
  transition: border-color 0.12s, transform 0.08s, box-shadow 0.12s;
}

.inv-cell:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,0.38); }

.inv-cell:active { transform: translateY(0); }

.inv-cell.equipped { border-color: var(--accent); background: #23303f; }

.inv-cell .inv-icon { width: 36px; height: 36px; pointer-events: none; }

.inv-section-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.inv-count { color: var(--muted); font-size: calc(11px * var(--ui-scale)); font-weight: 600; font-variant-numeric: tabular-nums; }

.inv-name { font-size: 12px; }

.inv-amt {
  position: absolute; right: 2px; bottom: 1px; z-index: 1;
  font-size: 10px; font-weight: 700; color: #fff; line-height: 1;
  padding: 0 2px; border-radius: 3px; background: rgba(8,12,18,0.85); text-shadow: 0 1px 1px #000;
}

.inv-refine {
  position: absolute; left: 2px; top: 1px; z-index: 1;
  font-size: 10px; font-weight: 700; color: var(--gold-value); line-height: 1; text-shadow: 0 1px 1px #000;
}

/* Bag / Sell tab strip — own full-width row inside the panel padding so a tab
   never sits under the window border. */

.inv-tabs { display: flex; gap: 4px; width: 100%; box-sizing: border-box; margin: 6px 0; }

.inv-tab {
  flex: 1; min-width: 0; padding: 5px 8px; cursor: var(--cursor-action); font-size: 12px;
  background: var(--panel-2); border: 1px solid var(--ui-control-line); border-radius: 6px; color: var(--muted);
}

.inv-tab:hover { border-color: var(--accent); }

.inv-tab.active { background: rgba(20,28,38,0.95); color: var(--accent); border-color: var(--accent); }

.inv-bag-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; width: 100%; box-sizing: border-box; }

.inv-search {
  flex: 1; min-width: 60px; padding: 4px 7px; font-size: 12px;
  background: rgba(10,14,20,0.85); border: 1px solid var(--ui-line-soft); border-radius: 5px; color: #e6e6e6;
}

.inv-search:focus { outline: none; border-color: var(--accent); }

.inv-hint { font-size: 11px; color: var(--muted); margin-bottom: 6px; }

/* Inventory cell context menu reuses .context-menu chrome; size its buttons. */

.inv-ctx .context-item { font-size: 12px; padding: 6px 10px; }

/* Skills window body container. */

.skill-list { display: flex; flex-direction: column; gap: 4px; }

/* Per-prerequisite Requires block: one line per prereq + base/job gate lines,
   shown in the rich skill tooltip (skillReqsHtml). */

.skill-req-block {
  flex-basis: 100%; display: flex; flex-direction: column; gap: 1px;
  margin-top: 2px; font-size: calc(10px * var(--ui-scale)); line-height: 1.25;
}

.skill-req-block .skill-req, .skill-req-block .skill-gate { flex-basis: auto; }

.skill-req-block .skill-gate { color: var(--muted); }

.skill-req-have { opacity: 0.85; }

/* Satisfied vs unmet prerequisite/gate colouring. */

.skill-req-ok { color: #7fc08a; }

.skill-req-bad { color: #d98b8b; }

/* Skill tooltip: synthesized description lines + the inline icon in the title. */

.tt-desc { color: var(--muted); font-size: calc(11px * var(--ui-scale)); line-height: 1.3; margin: 1px 0; }

.skill-tt-icon { width: 20px; height: 20px; vertical-align: middle; margin-right: 6px; border-radius: 4px; }

/* Kafra storage: compact searchable item grid backed by DataGrid. */

.storage-overlay {
position: fixed; inset: 0; z-index: 36; display: flex; align-items: center; justify-content: center;
background: rgba(8,10,16,0.5);
}

.storage-panel {
width: min(720px, calc(100vw - 32px));
height: min(500px, calc(100vh - 72px));
display: flex; flex-direction: column; gap: 8px;
}

.storage-panel.storage-drop { outline: 2px dashed var(--accent); outline-offset: 4px; }

.storage-capacity { color: var(--muted); font-size: calc(12px * var(--ui-scale)); font-variant-numeric: tabular-nums; }

.storage-grid-host { min-height: 0; flex: 1; }

.storage-grid-host .dgrid { height: 100%; }

.storage-item-cell { min-width: 0; display: flex; align-items: center; gap: 8px; }

.storage-item-icon { width: 28px; height: 28px; border-radius: 5px; flex: none; }

.storage-item-copy { min-width: 0; display: flex; flex-direction: column; gap: 1px; }

.storage-item-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }

.storage-row-action { padding: 4px 8px; font-size: calc(11px * var(--ui-scale)); border-radius: 6px; border: 1px solid var(--ui-control-line); background: var(--panel); color: var(--text); cursor: var(--cursor-action); }

.storage-row-action:hover { border-color: var(--accent); color: var(--accent); }

.storage-actions { display: flex; justify-content: flex-end; gap: 6px; }

.storage-deposit {
  display: flex; align-items: end; gap: 8px; flex-wrap: wrap;
  padding: 8px; border: 1px solid var(--ui-control-line); border-radius: 6px;
  background: color-mix(in srgb, var(--panel) 82%, transparent);
}

.storage-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; font-size: calc(10px * var(--ui-scale)); color: var(--muted); }

.storage-field ron-select { min-width: calc(180px * var(--ui-scale)); }

.storage-field input[type='number'] { width: calc(72px * var(--ui-scale)); }

.storage-foot { color: var(--muted); font-size: calc(11px * var(--ui-scale)); }

.storage-actions button {
  width: auto;
  min-width: 120px;
  min-height: 34px;
  padding: 7px 14px;
  font: inherit;
  font-weight: 700;
}

.storage-foot { color: var(--muted); font-size: calc(11px * var(--ui-scale)); }

.item-fact { display: block; min-width: 0; color: var(--muted); font-size: calc(9px * var(--ui-scale)); line-height: 1.3; overflow-wrap: anywhere; white-space: normal; }

/* Modal commerce and service windows share the same crafted frame; each
   module below owns only its dimensions and internal working layout. */

.shop-panel,
.cashshop-panel,
.mail-panel,
.itemsvc-panel,
.refine-panel,
.armory-panel,
.informant-panel,
.trade-panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  color: var(--text);
  background: var(--ui-frame-fill);
  border: 1px solid var(--ui-line-bright);
  border-radius: calc(9px * var(--ui-scale));
  box-shadow: var(--ui-frame-shadow);
}

/* A frame holds the tallest it has been while its window is open, so each
   window's working area takes the surplus rather than leaving a void under the
   last row. Inert while the frame is sized by its content: there is no surplus
   to take. */

.win-body,
.shop-body,
.cashshop-content,
.mail-workspace,
.itemsvc-content,
.refine-body,
.trade-body {
  flex-grow: 1;
}

/* Shop and premium-catalog window layouts. */

/* Modal NPC shop composes the same chrome as draggable windows. */

.shop-panel {
  width: min(calc(430px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(72vh, calc(620px * var(--ui-scale)));
}

.shop-body {
  display: flex;
  flex-direction: column;
  gap: calc(8px * var(--ui-scale));
  min-height: 0;
  padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale)) calc(12px * var(--ui-scale));
}

.shop-list {
  display: flex;
  flex-direction: column;
  gap: calc(5px * var(--ui-scale));
  min-height: 0;
  overflow-y: auto;
}

.shop-row {
  display: grid;
  grid-template-columns:
    calc(32px * var(--ui-scale))
    minmax(calc(86px * var(--ui-scale)), 1fr)
    auto
    calc(58px * var(--ui-scale))
    calc(72px * var(--ui-scale));
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  min-height: calc(46px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.4);
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  transition: border-color 120ms, background 120ms;
}

.shop-row:hover {
  background: rgba(37, 48, 49, 0.58);
  border-color: var(--ui-control-line-hover);
}

.shop-row-icon {
  width: calc(30px * var(--ui-scale));
  height: calc(30px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
  object-fit: contain;
}

.shop-row-copy { min-width: 0; }

.shop-row-facts { min-width: 0; margin-top: calc(2px * var(--ui-scale)); }

.shop-row-name {
  overflow: hidden;
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.shop-row-meta {
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.shop-qty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(2px * var(--ui-scale));
}

.shop-step {
  min-width: calc(23px * var(--ui-scale));
  min-height: calc(23px * var(--ui-scale));
  padding: 0;
}

.shop-qty-value {
  min-width: calc(22px * var(--ui-scale));
  text-align: center;
  font-size: calc(12px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.shop-row-total { text-align: right; }

.shop-empty {
  padding: calc(18px * var(--ui-scale)) calc(8px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  text-align: center;
}

@media (max-width: 560px) {
  .shop-row {
    grid-template-columns: calc(32px * var(--ui-scale)) minmax(0, 1fr) auto;
  }
  .shop-qty { grid-column: 2; justify-content: flex-start; }
  .shop-row-total { grid-column: 3; grid-row: 1; }
  .shop-row-action { grid-column: 3; grid-row: 2; }
}

.cashshop-panel {
  width: min(calc(500px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(660px * var(--ui-scale)), calc(100vh - 48px));
}

.cashshop-balance {
  color: var(--accent);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 760;
  font-variant-numeric: tabular-nums;
}

.cashshop-toolbar {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  gap: calc(10px * var(--ui-scale));
  padding: calc(8px * var(--ui-scale)) calc(12px * var(--ui-scale));
  border-block-end: 1px solid var(--ui-line-soft);
  background: rgba(5, 10, 12, 0.18);
}

.cashshop-current-tab {
  color: color-mix(in srgb, var(--accent) 62%, var(--text));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.cashshop-amount { display: flex; align-items: center; gap: calc(6px * var(--ui-scale)); color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.cashshop-amount input { width: calc(64px * var(--ui-scale)); min-height: calc(27px * var(--ui-scale)); }

.cashshop-list + .cashshop-tab-heading { margin-top: calc(7px * var(--ui-scale)); }

.cashshop-content {
  min-height: 0;
  overflow-y: auto;
  padding: calc(3px * var(--ui-scale)) calc(12px * var(--ui-scale)) calc(12px * var(--ui-scale));
}

.cashshop-section { margin-top: calc(10px * var(--ui-scale)); }

.cashshop-section-title,
.cashshop-list { display: grid; max-height: calc(210px * var(--ui-scale)); overflow-y: auto; border-block-start: 1px solid var(--ui-line-soft); }

.cashshop-row {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: calc(4px * var(--ui-scale)) calc(8px * var(--ui-scale)); align-items: center;
  min-height: calc(46px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border-block-end: 1px solid var(--ui-line-soft);
  background: transparent;
}

.cashshop-row::before { position: absolute; inset: calc(7px * var(--ui-scale)) auto calc(7px * var(--ui-scale)) 0; width: 2px; content: ''; background: color-mix(in srgb, var(--accent) 34%, transparent); }

.cashshop-row:hover { background: var(--row-hover); }

.cashshop-row-info { grid-template-columns: minmax(0, 1fr); }

.cashshop-row-main { min-width: 0; }

.cashshop-row-title { min-width: 0; overflow: hidden; font-size: calc(11px * var(--ui-scale)); font-weight: 760; text-overflow: ellipsis; white-space: nowrap; }

.cashshop-row-meta { color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.cashshop-row button {
  min-height: calc(28px * var(--ui-scale)); border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--ui-control-line)); border-radius: calc(5px * var(--ui-scale));
  padding-inline: calc(10px * var(--ui-scale)); background: color-mix(in srgb, var(--accent) 7%, var(--ui-control-fill)); color: color-mix(in srgb, var(--accent) 64%, var(--text)); font: inherit; font-size: calc(10px * var(--ui-scale)); font-weight: 780; cursor: var(--cursor-action);
}

.cashshop-row button:hover { border-color: var(--accent); color: var(--gold-ink); background: color-mix(in srgb, var(--accent) 14%, var(--ui-control-fill)); }

.cashshop-row button:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

@media (max-width: 560px) {
  .cashshop-toolbar { grid-template-columns: auto 1fr; }
}

/* ===== Shared service-window chrome ========================================
   The parts every modal service window wears the same way, defined once: the
   purse in its header, the row of faces under it, the status line at its foot,
   and the sentence a face shows when it has nothing to list. A window's own
   sheet owns its dimensions and its working layout; it does not restate these.
   ============================================================================ */

/* What the player is carrying, in a window header. The reviewed coin, then the
   number — the same pair the bag's footer draws, so gold reads as gold
   wherever a window prices something. */

.sw-purse {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: calc(5px * var(--ui-scale));
  color: var(--accent);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 750;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* One text field for a service window — an amount, a name, a mail draft, a
   filter word. A field states its width; the paint is shared, and focus is
   one fact. */

.sw-input,
.sw-filter {
  min-width: 0;
  box-sizing: border-box;
  padding: calc(5px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(5px * var(--ui-scale));
  background: var(--ui-control-fill);
  color: var(--text);
  font: inherit;
  font-size: calc(12px * var(--ui-scale));
}

.sw-input:focus, .sw-filter:focus { outline: none; border-color: var(--accent); box-shadow: var(--ui-focus-ring); }

.sw-input:disabled, .sw-filter:disabled { opacity: 0.5; cursor: not-allowed; }

/* The gold a row asks for — coin and amount; short reads as short so the
   price and the row's dead button never disagree. */

.sw-price {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(4px * var(--ui-scale));
  color: var(--accent);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 750;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.sw-price.is-short { color: var(--danger); }

.sw-price-coin {
  width: calc(14px * var(--ui-scale));
  height: calc(14px * var(--ui-scale));
  object-fit: contain;
  display: block;
}

.sw-purse-coin {
  width: calc(17px * var(--ui-scale));
  height: calc(17px * var(--ui-scale));
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
}

/* The last thing the window said, at its foot: one line, announced, and it
   stands until the next answer replaces it. */

.sw-status {
  flex: 0 0 auto;
  min-height: calc(20px * var(--ui-scale));
  padding: 0 calc(14px * var(--ui-scale)) calc(8px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 600;
}

.sw-status[data-tone='info'] { color: var(--muted); }

.sw-status[data-tone='ok'] { color: var(--good); }

.sw-status[data-tone='warn'] { color: var(--warn); }

.sw-status[data-tone='error'] { color: var(--danger); }

/* Mail and item-service window layouts. */

.mail-panel {
  width: min(calc(660px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(640px * var(--ui-scale)), calc(100vh - 48px));
}

.mail-workspace {
  display: grid;
  grid-template-columns: minmax(calc(210px * var(--ui-scale)), 0.82fr) minmax(0, 1.35fr);
  min-height: 0;
  overflow: hidden;
}

.mail-inbox-pane,
.mail-reader-pane,
.mail-compose-pane {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

.mail-inbox-pane { border-inline-end: 1px solid var(--ui-line-soft); }

.mail-compose-pane { grid-column: 1 / -1; }

.mail-section { padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale)); }

.mail-list { display: grid; max-height: calc(390px * var(--ui-scale)); overflow-y: auto; }

.mail-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  width: 100%;
  min-height: calc(49px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 0;
  border-block-end: 1px solid color-mix(in srgb, var(--ui-line-soft) 76%, transparent);
  background: transparent;
  color: var(--text);
}

.mail-row::before {
  position: absolute;
  inset: calc(7px * var(--ui-scale)) auto calc(7px * var(--ui-scale)) 0;
  width: 2px;
  content: '';
  background: transparent;
}

.mail-row:hover { background: var(--row-hover); }

.mail-row.is-selected { background: color-mix(in srgb, var(--accent) 8%, transparent); }

.mail-row.is-selected::before { background: var(--accent); }

.mail-row-main {
  min-width: 0;
  display: grid;
  gap: calc(2px * var(--ui-scale));
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: var(--cursor-action);
}

.mail-row-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.mail-row-title { min-width: 0; overflow: hidden; font-size: calc(11px * var(--ui-scale)); font-weight: 760; text-overflow: ellipsis; white-space: nowrap; }

.mail-row-meta { color: var(--muted); font-size: calc(10px * var(--ui-scale)); line-height: 1.35; }

.mail-read { display: grid; gap: 5px; }

.mail-read-card {
  color: var(--text);
  font-size: calc(11px * var(--ui-scale));
}

.mail-body {
  min-height: calc(90px * var(--ui-scale));
  border-inline-start: 2px solid color-mix(in srgb, var(--accent) 38%, transparent);
  padding-inline-start: calc(9px * var(--ui-scale));
  color: color-mix(in srgb, var(--text) 82%, var(--muted));
  white-space: pre-wrap;
  line-height: 1.48;
}

.mail-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: calc(5px * var(--ui-scale));
  margin-top: calc(10px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  padding-block-start: calc(8px * var(--ui-scale));
}

.mail-attachment-item {
  min-width: min(100%, calc(180px * var(--ui-scale)));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(4px * var(--ui-scale));
  color: var(--text);
  background: var(--ui-control-fill);
}

/* An attachment reads like an item everywhere: art, rarity-coloured name, ×count. */

.mail-attachment-icon {
  width: calc(16px * var(--ui-scale));
  height: calc(16px * var(--ui-scale));
  object-fit: contain;
  flex: none;
}

.mail-attachment-name {
  min-width: 0;
  overflow: hidden;
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mail-gold { margin-top: calc(5px * var(--ui-scale)); }

.mail-attachment-preview { min-width: 0; margin-top: calc(7px * var(--ui-scale)); }

.mail-actions { display: flex; flex-wrap: wrap; gap: calc(6px * var(--ui-scale)); margin-top: calc(9px * var(--ui-scale)); }

/* The row's buttons are kit buttons; the mail surface only holds them to the
   compact height and type size it has always used. */

.mail-actions button,
.mail-action {
  min-height: calc(28px * var(--ui-scale));
  padding-inline: calc(8px * var(--ui-scale));
  font-size: calc(10px * var(--ui-scale));
}

.mail-compose { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(8px * var(--ui-scale)) calc(10px * var(--ui-scale)); }

.mail-compose > .mail-actions { grid-column: 1 / -1; }

.mail-field { display: grid; gap: calc(4px * var(--ui-scale)); color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.mail-field textarea { width: 100%; }

.mail-field ron-select { width: 100%; }

.mail-field textarea { resize: vertical; }

.mail-attach-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 6px; }

.mail-attach-form .mail-actions { grid-column: 1 / -1; margin-top: 0; }

.mail-field-wide { grid-column: 1 / -1; }

.mail-result {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: calc(8px * var(--ui-scale));
  min-height: calc(31px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  padding: calc(6px * var(--ui-scale)) calc(12px * var(--ui-scale));
  color: var(--muted);
  background: rgba(5, 10, 12, 0.2);
  font-size: calc(9px * var(--ui-scale));
}

.mail-result-history {
  min-width: 0;
  max-height: calc(58px * var(--ui-scale));
  display: grid;
  gap: calc(2px * var(--ui-scale));
  overflow-y: auto;
}

.mail-result-line {
  display: flex;
  min-width: 0;
  align-items: baseline;
  gap: calc(5px * var(--ui-scale));
}

.mail-result-line strong { flex: 0 0 auto; color: var(--text); font-weight: 760; }

.mail-result-line span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 620px) {
  .mail-panel { width: calc(100vw - 20px); max-height: calc(100vh - 20px); }
  .mail-head { grid-template-columns: minmax(0, 1fr) auto; }
  .mail-head .win-close { grid-column: 2; grid-row: 1 / span 2; }
  .mail-workspace { display: block; overflow-y: auto; }
  .mail-inbox-pane { border-inline-end: 0; border-block-end: 1px solid var(--ui-line-soft); }
  .mail-list { max-height: calc(180px * var(--ui-scale)); }
  .mail-compose { grid-template-columns: minmax(0, 1fr); }
  .mail-field-wide,
  .mail-compose > .mail-actions { grid-column: 1; }
  .mail-result { display: grid; }
  .mail-result { grid-template-columns: minmax(0, 1fr); }
}

.itemsvc-panel {
  width: min(calc(540px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(680px * var(--ui-scale)), calc(100vh - 48px));
}

.itemsvc-content {
  min-height: 0;
  overflow-y: auto;
  padding: calc(3px * var(--ui-scale)) calc(12px * var(--ui-scale)) calc(12px * var(--ui-scale));
}

.itemsvc-section { margin-top: calc(10px * var(--ui-scale)); }

.itemsvc-list { display: grid; max-height: calc(230px * var(--ui-scale)); overflow-y: auto; border-block-start: 1px solid var(--ui-line-soft); }

.itemsvc-row {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: calc(2px * var(--ui-scale)) calc(8px * var(--ui-scale));
  min-height: calc(48px * var(--ui-scale)); padding: calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border-block-end: 1px solid var(--ui-line-soft); background: transparent; color: var(--text);
}

.itemsvc-row::before { position: absolute; inset: calc(7px * var(--ui-scale)) auto calc(7px * var(--ui-scale)) 0; width: 2px; content: ''; background: color-mix(in srgb, var(--accent) 32%, transparent); }

.itemsvc-row:hover { background: var(--row-hover); }

.itemsvc-row-title { min-width: 0; overflow: hidden; font-size: calc(11px * var(--ui-scale)); font-weight: 760; text-overflow: ellipsis; white-space: nowrap; }

.itemsvc-row-meta { grid-column: 1; color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.itemsvc-actions { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-wrap: wrap; align-items: center; justify-content: end; gap: calc(6px * var(--ui-scale)); margin-top: 0; }

.itemsvc-actions input {
  width: calc(64px * var(--ui-scale));
  height: calc(28px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.itemsvc-actions input[type="text"] { width: 132px; }

.itemsvc-choice-list { display: flex; flex-wrap: wrap; justify-content: end; gap: calc(4px * var(--ui-scale)); }

.itemsvc-choice {
  display: inline-flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  min-height: calc(26px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(4px * var(--ui-scale));
  padding-inline: calc(6px * var(--ui-scale));
  color: var(--muted);
  background: var(--ui-control-fill);
  font-size: calc(9px * var(--ui-scale));
  cursor: var(--cursor-action);
}

.itemsvc-choice:has(input:checked) { border-color: color-mix(in srgb, var(--accent) 55%, var(--ui-control-line)); color: var(--gold-ink); background: color-mix(in srgb, var(--accent) 9%, var(--ui-control-fill)); }

.itemsvc-choice input { width: calc(13px * var(--ui-scale)); height: calc(13px * var(--ui-scale)); margin: 0; accent-color: var(--accent); }

.itemsvc-actions button {
  min-width: calc(58px * var(--ui-scale)); height: calc(28px * var(--ui-scale)); border: 1px solid var(--ui-control-line); border-radius: calc(5px * var(--ui-scale));
  background: var(--ui-control-fill); color: var(--text); font: inherit; font-size: calc(10px * var(--ui-scale)); cursor: var(--cursor-action);
}

.itemsvc-actions button:hover { border-color: var(--accent); color: var(--gold-ink); background: var(--ui-control-fill-hover); }

.itemsvc-actions button:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ui-focus-ring); }

.itemsvc-result { min-height: calc(16px * var(--ui-scale)); margin-top: calc(8px * var(--ui-scale)); color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

.itemsvc-result .itemsvc-row { min-height: 0; grid-template-columns: minmax(0, 1fr); }

@media (max-width: 560px) {
  .itemsvc-head { grid-template-columns: minmax(0, 1fr) auto; }
  .itemsvc-head .win-close { grid-column: 2; grid-row: 1 / span 2; }
  .itemsvc-row { grid-template-columns: minmax(0, 1fr); }
  .itemsvc-actions { grid-column: 1; grid-row: 3; justify-content: start; }
}

/* ===== The smith's counter =================================================
   Refining is the one place in the HUD where a player bets something they care
   about, so the window is allowed the ceremony the design system reserves for
   ceremonial moments — and nothing more. It composes the shared modal frame,
   the shared window header, the shared gauge, the shared item slot and the
   button kit; the only local paint is the ladder's pips, the step arrow and the
   protect switch, none of which exist anywhere else in the game.

   Every size scales with --ui-scale. Colour never carries a state alone: the
   ladder marks a rung's shape as well as its ink, the gauge prints its percent,
   and the miss line says the consequence in words. */

.refine-panel {
  width: min(calc(760px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(660px * var(--ui-scale)), calc(100vh - 48px));
}

.refine-body {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* The anvil: the rack on the left, the piece and its readings on the right. */

.refine-anvil {
  display: grid;
  grid-template-columns: calc(196px * var(--ui-scale)) minmax(0, 1fr);
  min-height: 0;
  overflow: hidden;
}

/* Nothing racked: the bench is the whole body, not a column beside a gap. */

.refine-anvil.is-bare { grid-template-columns: minmax(0, 1fr); }

.refine-rack {
  display: flex;
  flex-direction: column;
  gap: calc(7px * var(--ui-scale));
  min-height: 0;
  padding: calc(11px * var(--ui-scale));
  border-inline-end: 1px solid var(--ui-line-soft);
  background: rgba(5, 10, 12, 0.22);
}

.refine-rail-title,
.refine-block h3 {
  margin: 0;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.refine-block h3 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(8px * var(--ui-scale));
}

.refine-block h3 small {
  color: var(--accent);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}

.refine-rack-grid {
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  min-height: 0;
  overflow-y: auto;
}

.refine-gear {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale));
  border: 1px solid transparent;
  border-radius: calc(6px * var(--ui-scale));
  color: var(--text);
  background: transparent;
  font: inherit;
  font-size: calc(12px * var(--ui-scale));
  text-align: start;
  cursor: var(--cursor-action);
  transition: border-color 120ms, background 120ms;
}

.refine-gear:hover { border-color: var(--ui-control-line-hover); background: rgba(255, 255, 255, 0.03); }

.refine-gear:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.refine-gear.is-picked {
  border-color: var(--accent);
  background: rgba(201, 164, 94, 0.1);
}

.refine-gear[disabled] { opacity: 0.5; cursor: var(--cursor-default); }

.refine-gear-name {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.refine-bench {
  display: flex;
  flex-direction: column;
  gap: calc(11px * var(--ui-scale));
  min-height: 0;
  padding: calc(12px * var(--ui-scale)) calc(14px * var(--ui-scale));
  overflow-y: auto;
}

.refine-bench.is-empty { align-items: center; justify-content: center; }

.refine-empty {
  margin: 0;
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  font-style: italic;
  text-align: center;
}

.refine-empty--inline { text-align: start; }

/* The piece, its name, and the step it is about to take. */

.refine-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(12px * var(--ui-scale));
}

.refine-hero-art { position: relative; }

.refine-hero-name {
  overflow: hidden;
  font-size: calc(15px * var(--ui-scale));
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.refine-hero-sub {
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
}

.refine-hero-step {
  display: inline-flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(11px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(7px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.45);
  font-variant-numeric: tabular-nums;
}

.refine-step-from {
  color: var(--muted);
  font-size: calc(16px * var(--ui-scale));
  font-weight: 700;
}

.refine-step-to {
  color: var(--accent);
  font-size: calc(21px * var(--ui-scale));
  font-weight: 800;
}

.refine-step-done { color: var(--muted); font-size: calc(12px * var(--ui-scale)); }

/* The one arrow shape the window uses, drawn rather than typed so it keeps its
   weight at every UI scale and needs no glyph coverage. */

.refine-step-arrow,
.refine-gain-arrow {
  width: calc(14px * var(--ui-scale));
  height: calc(2px * var(--ui-scale));
  position: relative;
  background: var(--muted);
}

.refine-step-arrow::after,
.refine-gain-arrow::after {
  content: '';
  position: absolute;
  inset-inline-end: 0;
  top: 50%;
  width: calc(6px * var(--ui-scale));
  height: calc(6px * var(--ui-scale));
  border-top: calc(2px * var(--ui-scale)) solid var(--muted);
  border-inline-end: calc(2px * var(--ui-scale)) solid var(--muted);
  transform: translateY(-50%) rotate(45deg);
}

/* The verdict, said once, over the piece it happened to. */

.refine-verdict {
  position: absolute;
  inset-inline-start: 50%;
  top: 50%;
  z-index: 2;
  padding: calc(2px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
  color: #10161a;
  font-size: calc(13px * var(--ui-scale));
  font-weight: 800;
  white-space: nowrap;
  pointer-events: none;
  animation: refine-verdict-rise 900ms ease-out forwards;
}

.refine-verdict[data-kind='success'] { background: var(--good, #6fd06a); }

.refine-verdict[data-kind='held'] { background: var(--accent); }

.refine-verdict[data-kind='drop'] { background: var(--danger); color: #fff; }

.refine-verdict[data-kind='miss'] { background: var(--muted); }

@keyframes refine-verdict-rise {
  0% { opacity: 0; transform: translate(-50%, -30%); }
  18% { opacity: 1; transform: translate(-50%, -110%); }
  75% { opacity: 1; transform: translate(-50%, -125%); }
  100% { opacity: 0; transform: translate(-50%, -155%); }
}

body.reduce-flashing .refine-verdict,
body.reduce-motion .refine-verdict {
  animation: none;
  opacity: 1;
  transform: translate(-50%, -120%);
}

@media (prefers-reduced-motion: reduce) {
  .refine-verdict { animation: none; opacity: 1; transform: translate(-50%, -120%); }
}

.refine-block {
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--ui-scale));
}

/* The ladder. A rung the piece has climbed is filled; a rung that cannot miss
   is a square block and a rung that can is a narrower notch, so the guaranteed
   band survives greyscale. */

.refine-ladder {
  display: flex;
  align-items: flex-end;
  gap: calc(3px * var(--ui-scale));
  margin: 0;
  padding: 0 0 calc(14px * var(--ui-scale));
  list-style: none;
}

.refine-pip {
  position: relative;
  display: flex;
  flex: 1;
  align-items: flex-end;
  justify-content: center;
  min-width: calc(6px * var(--ui-scale));
}

.refine-pip-mark {
  width: 100%;
  height: calc(16px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(2px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.5);
}

.refine-pip.is-risky .refine-pip-mark {
  height: calc(11px * var(--ui-scale));
  border-style: dashed;
}

.refine-pip.is-reached .refine-pip-mark {
  border-color: var(--accent);
  background: linear-gradient(180deg, #e0c07a, var(--accent));
}

.refine-pip.is-cap::after {
  content: '';
  position: absolute;
  inset-block: calc(-3px * var(--ui-scale)) calc(-4px * var(--ui-scale));
  inset-inline-end: calc(-3px * var(--ui-scale));
  width: calc(2px * var(--ui-scale));
  border-radius: 999px;
  background: var(--accent);
}

.refine-pip.is-next .refine-pip-mark {
  border-color: var(--text);
  box-shadow: 0 0 0 1px var(--text);
}

.refine-pip-here {
  position: absolute;
  top: 100%;
  margin-block-start: calc(2px * var(--ui-scale));
  color: var(--text);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 750;
  font-variant-numeric: tabular-nums;
}

/* The odds, on the shared gauge; only the row height and the band fills are
   local. The percent is printed inside, so no band is told apart by hue. */

.refine-odds { --gauge-h: calc(18px * var(--ui-scale)); }

.refine-odds-fill[data-band='sure'] { background: linear-gradient(90deg, #4f9a58, var(--good, #6fd06a)); }

.refine-odds-fill[data-band='likely'] { background: linear-gradient(90deg, #97a24a, #c9c05e); }

.refine-odds-fill[data-band='even'] { background: linear-gradient(90deg, #c8821f, var(--accent)); }

.refine-odds-fill[data-band='long'] { background: linear-gradient(90deg, #8a3b2c, #c2543c); }

body.high-contrast .refine-odds { --gauge-ring-w: 2px; }

.refine-miss {
  display: flex;
  gap: calc(7px * var(--ui-scale));
  margin: 0;
  font-size: calc(12px * var(--ui-scale));
}

.refine-miss-label {
  flex: 0 0 auto;
  color: var(--muted);
  font-weight: 700;
}

.refine-miss[data-kind='drop'] .refine-miss-copy { color: var(--danger); font-weight: 650; }

.refine-miss[data-kind='protected'] .refine-miss-copy { color: var(--accent); font-weight: 650; }

.refine-miss[data-kind='guaranteed'] .refine-miss-copy { color: var(--good, #6fd06a); font-weight: 650; }

/* What the step adds — the only place the amount is stated rather than the rule. */

.refine-gain { margin: 0; padding: 0; list-style: none; }

.refine-gain-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto calc(54px * var(--ui-scale));
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) 0;
  font-size: calc(12px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.refine-gain-label { overflow: hidden; color: var(--muted); text-overflow: ellipsis; white-space: nowrap; }

.refine-gain-before { color: var(--muted); }

.refine-gain-after { color: var(--text); font-weight: 700; }

.refine-gain-delta { color: var(--good, #6fd06a); font-weight: 750; text-align: end; }

/* What it costs. A short row reds its amount and says the word. */

.refine-costs { margin: 0; padding: 0; list-style: none; }

.refine-cost {
  display: grid;
  grid-template-columns: calc(22px * var(--ui-scale)) minmax(0, 1fr) auto calc(78px * var(--ui-scale));
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale)) 0;
  font-size: calc(12px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.refine-cost-icon {
  width: calc(22px * var(--ui-scale));
  height: calc(22px * var(--ui-scale));
  object-fit: contain;
}

.refine-cost-coin {
  width: calc(16px * var(--ui-scale));
  height: calc(16px * var(--ui-scale));
  margin-inline: calc(3px * var(--ui-scale));
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: radial-gradient(circle at 35% 30%, #f0d79a, #b98c34);
}

.refine-cost-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.refine-cost-need { color: var(--text); font-weight: 700; }

.refine-cost-have { color: var(--muted); text-align: end; }

.refine-cost.is-short .refine-cost-need,
.refine-cost.is-short .refine-cost-have { color: var(--danger); font-weight: 750; }

/* Fail protection: a switch, what it does, and a way to get more. */

.refine-protect {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(10px * var(--ui-scale));
  margin-block-start: calc(6px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(7px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.4);
}

.refine-protect.is-on { border-color: var(--accent); background: rgba(201, 164, 94, 0.08); }

.refine-switch {
  display: inline-flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  padding: 0;
  border: 0;
  color: var(--text);
  background: transparent;
  font: inherit;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  cursor: var(--cursor-action);
}

.refine-switch[disabled] { opacity: 0.5; cursor: var(--cursor-default); }

.refine-switch:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.refine-switch-track {
  position: relative;
  width: calc(34px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: 999px;
  background: rgba(7, 13, 15, 0.7);
  transition: background 120ms, border-color 120ms;
}

.refine-switch-knob {
  position: absolute;
  inset-block-start: calc(2px * var(--ui-scale));
  inset-inline-start: calc(2px * var(--ui-scale));
  width: calc(12px * var(--ui-scale));
  height: calc(12px * var(--ui-scale));
  border-radius: 999px;
  background: var(--muted);
  transition: transform 120ms, background 120ms;
}

.refine-switch[aria-checked='true'] .refine-switch-track {
  border-color: var(--accent);
  background: rgba(201, 164, 94, 0.35);
}

.refine-switch[aria-checked='true'] .refine-switch-knob {
  background: var(--accent);
  transform: translateX(calc(16px * var(--ui-scale)));
}

.refine-protect-copy { margin: 0; color: var(--muted); font-size: calc(11px * var(--ui-scale)); }

.refine-visit {
  margin: 0;
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.refine-visit-label { margin-inline-end: calc(6px * var(--ui-scale)); font-weight: 750; }

.refine-foot {
  flex: 0 0 auto;
  padding: calc(10px * var(--ui-scale)) calc(14px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  background: rgba(5, 10, 12, 0.2);
}

/* The counter: crystals in, blessings out. */

.refine-counter {
  display: flex;
  flex-direction: column;
  gap: calc(9px * var(--ui-scale));
  min-height: 0;
  padding: calc(13px * var(--ui-scale)) calc(14px * var(--ui-scale));
  overflow-y: auto;
}

.refine-counter-intro { margin: 0; color: var(--muted); font-size: calc(12px * var(--ui-scale)); }

.refine-trades {
  display: flex;
  flex-direction: column;
  gap: calc(5px * var(--ui-scale));
  margin: 0;
  padding: 0;
  list-style: none;
}

.refine-trade {
  display: grid;
  grid-template-columns: calc(34px * var(--ui-scale)) minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.4);
}

.refine-trade.is-empty { opacity: 0.55; }

.refine-trade-icon {
  width: calc(32px * var(--ui-scale));
  height: calc(32px * var(--ui-scale));
  object-fit: contain;
}

.refine-trade-copy { display: flex; flex-direction: column; min-width: 0; }

.refine-trade-name {
  overflow: hidden;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.refine-trade-yield { color: var(--accent); font-size: calc(11px * var(--ui-scale)); font-weight: 700; }

.refine-trade-stock {
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.refine-holding {
  display: flex;
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  margin: 0;
  padding-block-start: calc(6px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 650;
}

/* The whole-stock trade, one press short of committing. It stands over the face
   that raised it rather than beside it, because the counter's rows go quiet
   while it is up and the question is the only live thing on the screen. */

.refine-confirm {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: safe center;
  justify-content: center;
  padding: calc(14px * var(--ui-scale));
  overflow-y: auto;
  background: rgba(5, 8, 11, 0.66);
  animation: refine-confirm-in 120ms ease-out;
}

.refine-confirm-card {
  display: flex;
  flex-direction: column;
  gap: calc(7px * var(--ui-scale));
  flex: 0 0 auto;
  width: min(calc(360px * var(--ui-scale)), 100%);
  padding: calc(14px * var(--ui-scale));
  border: 1px solid var(--danger);
  border-radius: calc(8px * var(--ui-scale));
  background: var(--ui-frame-fill);
  box-shadow: var(--ui-frame-shadow);
}

.refine-confirm-card h3 {
  margin: 0;
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 750;
}

.refine-confirm-deal {
  margin: 0;
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 650;
}

.refine-confirm-warn {
  margin: 0;
  color: var(--danger);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 600;
}

.refine-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: calc(7px * var(--ui-scale));
  padding-block-start: calc(3px * var(--ui-scale));
}

@keyframes refine-confirm-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .refine-confirm { animation: none; }
}

/* Narrow frames stack the rack above the bench rather than squeezing both. */

@media (max-width: 760px) {
  .refine-anvil { grid-template-columns: minmax(0, 1fr); }
  .refine-rack {
    border-inline-end: 0;
    border-block-end: 1px solid var(--ui-line-soft);
  }
  .refine-rack-grid { flex-direction: row; overflow-x: auto; overflow-y: hidden; }
  .refine-gear { flex: 0 0 auto; grid-template-columns: auto; }
  .refine-gear-name { display: none; }
}

/* ===== The Faction Armory ==================================================
   Four faces on one counter: what the faction has opened, what it can seal,
   what a spare piece is worth to it, and the bench where a seal goes into a
   slot. The window composes the shared modal frame, the shared header, the
   shared face row, the shared purse and status line, the shared gauge and the
   button kit; the only local paint is the row grammar those faces share and
   the step numbers on the bench.

   Every size scales with --ui-scale. Colour never carries a state alone: a
   locked row says "Locked" on a dashed control, a short price prints the
   number it cannot pay, and every track prints its own count. */

.armory-panel {
  width: min(calc(720px * var(--ui-scale)), calc(100vw - 32px));
  /* A fixed height, not a cap: the four faces run to very different lengths and
     a counter that resizes under the player as they change face is a worse
     reading than one that always scrolls its own list. */
  height: min(78vh, calc(680px * var(--ui-scale)));
}

.armory-body {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* One face fills the body and scrolls its own list; the head above it stays. */

.armory-face {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: calc(9px * var(--ui-scale));
  min-height: 0;
  padding: calc(11px * var(--ui-scale)) calc(13px * var(--ui-scale));
  overflow-y: auto;
}

/* ---- Filters: the type a row must be, and the words it must carry -------- */

.armory-filters {
  display: flex;
  flex-wrap: wrap;
  flex: 0 0 auto;
  align-items: center;
  gap: calc(7px * var(--ui-scale));
}

.armory-type-filter { flex: 0 0 auto; min-width: calc(150px * var(--ui-scale)); }

.armory-unlocked-filter { flex: 0 0 auto; }

.armory-search { flex: 1 1 calc(180px * var(--ui-scale)); min-width: 0; }

/* ---- The row every face lists ------------------------------------------- */

.armory-rows {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: calc(4px * var(--ui-scale));
  min-height: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.armory-row {
  display: grid;
  grid-template-columns:
    calc(34px * var(--ui-scale))
    minmax(calc(120px * var(--ui-scale)), 1fr)
    calc(132px * var(--ui-scale))
    calc(84px * var(--ui-scale))
    calc(88px * var(--ui-scale));
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(255, 255, 255, 0.022);
}

.armory-row:hover { background: rgba(255, 255, 255, 0.04); }

/* A locked row is quieter, not unreadable: its own track is the reason. */

.armory-row.is-locked { background: rgba(5, 10, 12, 0.26); }

/* A seal has no bitmap of its own, so its row drops the art column. */

.armory-row--seal { grid-template-columns: minmax(calc(120px * var(--ui-scale)), 1fr) calc(132px * var(--ui-scale)) calc(84px * var(--ui-scale)) calc(88px * var(--ui-scale)); }

/* A donation reads its whole bargain, so it gives the copy the width the
   track and price columns take on a catalog row. */

.armory-row--donate {
  grid-template-columns:
    calc(34px * var(--ui-scale))
    minmax(0, 1fr)
    calc(116px * var(--ui-scale));
  align-items: start;
}

.armory-row--donate.is-marked { border-color: var(--accent); background: rgba(201, 164, 94, 0.09); }

.armory-row-icon {
  width: calc(34px * var(--ui-scale));
  height: calc(34px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
  object-fit: contain;
  display: block;
}

.armory-row-copy { display: grid; gap: calc(2px * var(--ui-scale)); min-width: 0; }

.armory-row-name {
  overflow: hidden;
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.armory-row-sub {
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  line-height: 1.35;
}

/* ---- A shared unlock's track -------------------------------------------- */

.armory-track-cell { display: grid; gap: calc(3px * var(--ui-scale)); width: 100%; }

.armory-track { --gauge-h: max(9px, calc(10px * var(--ui-scale))); width: 100%; }

.armory-track-fill { background: linear-gradient(180deg, var(--accent), var(--accent-2)); }

.armory-track-count {
  color: var(--muted);
  font-size: calc(9.5px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  text-align: center;
}

.armory-track-open {
  color: var(--accent);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 750;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* ---- What a row costs ---------------------------------------------------- */

/* The price itself is the shared sw-price panel; only the buy press is local. */

.armory-buy { width: 100%; }

/* ---- Donating ------------------------------------------------------------ */

.armory-notice {
  flex: 0 0 auto;
  margin: 0;
  padding: calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-inline-start: calc(2px * var(--ui-scale)) solid var(--warn);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(5, 10, 12, 0.3);
  color: var(--muted);
  font-size: calc(10.5px * var(--ui-scale));
  line-height: 1.45;
}

.armory-notice-lead {
  margin-inline-end: calc(5px * var(--ui-scale));
  color: var(--warn);
  font-weight: 800;
}

.armory-facts {
  display: grid;
  gap: calc(1px * var(--ui-scale));
  margin: 0;
  padding: 0;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  line-height: 1.35;
  list-style: none;
}

.armory-credits {
  margin-inline-start: calc(4px * var(--ui-scale));
  color: var(--accent);
  font-weight: 700;
}

/* The reward rides inside the press it pays for, so the two are one reading. */

.armory-donate { flex-direction: column; gap: calc(1px * var(--ui-scale)); width: 100%; line-height: 1.2; }

.armory-donate-verb { font-size: calc(11px * var(--ui-scale)); }

.armory-donate-reward { font-size: calc(10px * var(--ui-scale)); font-weight: 750; opacity: 0.82; }

/* ---- The lesson's opening ------------------------------------------------ */

.armory-primer {
  flex: 0 0 auto;
  display: grid;
  gap: calc(4px * var(--ui-scale));
  padding: calc(10px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--accent) 62%, var(--ui-line));
  border-radius: calc(9px * var(--ui-scale));
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent 62%),
    rgba(5, 10, 12, 0.34);
}

.armory-primer-kicker {
  color: var(--gold-label);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 900;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.armory-primer h2 {
  margin: 0;
  color: var(--text);
  font-size: calc(15px * var(--ui-scale));
}

.armory-primer p {
  margin: 0;
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.4;
}

.armory-primer-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(6px * var(--ui-scale));
  margin-top: calc(4px * var(--ui-scale));
}

.armory-primer-facts span {
  display: grid;
  grid-template-columns: calc(20px * var(--ui-scale)) minmax(0, 1fr);
  align-items: start;
  gap: calc(5px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  line-height: 1.3;
}

.armory-primer-facts b {
  display: grid;
  place-items: center;
  width: calc(18px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--accent) 65%, var(--ui-line));
  border-radius: 50%;
  color: var(--gold-label);
}

/* ---- The enchant bench: three choices, then one press -------------------- */

.armory-bench { gap: calc(10px * var(--ui-scale)); }

.armory-steps {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: calc(10px * var(--ui-scale));
  min-height: 0;
}

.armory-step { display: flex; flex-direction: column; gap: calc(5px * var(--ui-scale)); min-height: 0; }

.armory-step h3 {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  margin: 0;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.armory-step-n {
  display: grid;
  place-items: center;
  width: calc(16px * var(--ui-scale));
  height: calc(16px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: 50%;
  color: var(--accent);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0;
}

.armory-picks {
  display: flex;
  flex-direction: column;
  gap: calc(3px * var(--ui-scale));
  max-height: calc(168px * var(--ui-scale));
  overflow-y: auto;
}

.armory-pick {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  width: 100%;
  padding: calc(4px * var(--ui-scale)) calc(6px * var(--ui-scale));
  border: 1px solid transparent;
  border-radius: calc(6px * var(--ui-scale));
  color: var(--text);
  background: transparent;
  font: inherit;
  font-size: calc(11px * var(--ui-scale));
  text-align: start;
  cursor: var(--cursor-action);
  transition: border-color 120ms, background 120ms;
}

.armory-pick:hover { border-color: var(--ui-control-line-hover); background: rgba(255, 255, 255, 0.03); }

.armory-pick:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.armory-pick.is-picked { border-color: var(--accent); background: rgba(201, 164, 94, 0.1); }

.armory-pick-icon {
  width: calc(24px * var(--ui-scale));
  height: calc(24px * var(--ui-scale));
  border-radius: calc(4px * var(--ui-scale));
  object-fit: contain;
  display: block;
}

.armory-pick-name { overflow: hidden; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }

.armory-slots { display: flex; flex-wrap: wrap; gap: calc(5px * var(--ui-scale)); }

.armory-slot {
  display: grid;
  gap: calc(1px * var(--ui-scale));
  min-width: calc(150px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(6px * var(--ui-scale));
  color: var(--text);
  background: var(--ui-control-fill);
  font: inherit;
  text-align: start;
  cursor: var(--cursor-action);
  transition: border-color 120ms, background 120ms;
}

.armory-slot:hover { border-color: var(--ui-control-line-hover); background: var(--ui-control-fill-hover); }

.armory-slot:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.armory-slot.is-picked { border-color: var(--accent); background: rgba(201, 164, 94, 0.1); }

.armory-slot-name {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.armory-slot-holds { font-size: calc(11px * var(--ui-scale)); font-weight: 650; }

/* The press that spends the seal sits at the foot of the bench rather than
   floating in the middle of it, however short the three choices run. */

.armory-bench-foot { flex: 0 0 auto; margin-top: auto; }

/* ---- Rerolling one slot -------------------------------------------------- */

.armory-reroll {
  flex: 0 0 auto;
  display: grid;
  gap: calc(6px * var(--ui-scale));
  padding: calc(8px * var(--ui-scale)) calc(10px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(7px * var(--ui-scale));
  background: rgba(5, 10, 12, 0.3);
}

.armory-reroll h3 {
  margin: 0;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.armory-reroll-deal {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  margin: 0;
  font-size: calc(11px * var(--ui-scale));
}

.armory-reroll-before { color: var(--muted); }

.armory-reroll-after { color: #f4d58d; font-weight: 700; }

/* The direction of the trade, drawn rather than spelled. */

.armory-reroll-arrow {
  width: 0;
  height: 0;
  border-block: calc(4px * var(--ui-scale)) solid transparent;
  border-inline-start: calc(7px * var(--ui-scale)) solid var(--accent);
}

.armory-reroll-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(8px * var(--ui-scale));
}

/* A narrow frame stacks the bench's two rails and drops the row to its two
   readings rather than squeezing five columns into a phone. */

@media (max-width: 720px) {
  .armory-steps { grid-template-columns: minmax(0, 1fr); }
  .armory-row:not(.armory-row--donate) {
    grid-template-columns: calc(34px * var(--ui-scale)) minmax(0, 1fr) calc(88px * var(--ui-scale));
  }
  .armory-row.armory-row--seal { grid-template-columns: minmax(0, 1fr) calc(88px * var(--ui-scale)); }
  .armory-row .armory-track-cell,
  .armory-row .armory-track-open { grid-column: 2; }
  .armory-row .sw-price { justify-content: flex-start; grid-column: 2; }
}

/* The informant window. The frame, the header, the purse and the status line
   come from the shared service-window chrome; everything here is the card grid
   and the one picker the map watch needs. A card's state is a word first — the
   tint only agrees with it. */

.informant-panel {
  width: min(calc(660px * var(--ui-scale)), calc(100vw - 32px));
  max-height: calc(100vh - 96px);
}

.informant-head { --sw-head-accent: #b98ad8; }

.informant-lede {
  margin: 0;
  padding: calc(8px * var(--ui-scale)) calc(12px * var(--ui-scale)) 0;
  color: var(--muted);
  font-size: max(12px, calc(11px * var(--ui-scale)));
  line-height: 1.4;
}

.informant-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(calc(260px * var(--ui-scale)), 1fr));
  gap: calc(8px * var(--ui-scale));
  overflow-y: auto;
  padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale));
}

.informant-empty {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
}

.informant-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-content: center;
  gap: calc(3px * var(--ui-scale)) calc(10px * var(--ui-scale));
  min-height: calc(68px * var(--ui-scale));
  border-block: 1px solid rgb(255 255 255 / 9%);
  border-inline-end: 1px solid rgb(255 255 255 / 7%);
  border-left: 2px solid var(--informant-edge, #6d7889);
  padding: calc(7px * var(--ui-scale)) calc(8px * var(--ui-scale));
  background: linear-gradient(90deg, rgb(16 20 28 / 68%), rgb(10 13 19 / 30%));
}

/* Live is the state the whole Crown is spending on right now, so it is the only
   one that carries the informant's colour. */

.informant-card.is-live { --informant-edge: #b98ad8; background: linear-gradient(90deg, rgb(46 32 60 / 54%), rgb(10 13 19 / 30%)); }

.informant-card.is-cooldown { --informant-edge: #4c5666; }

.informant-name {
  margin: 0;
  color: var(--gold-ink, #f7e3a5);
  font-family: var(--font-display);
  font-size: max(12px, calc(12px * var(--ui-scale)));
  font-weight: 850;
  line-height: 1.2;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.informant-reveals {
  margin: 0;
  color: #cbd2dc;
  font-size: max(12px, calc(11px * var(--ui-scale)));
  line-height: 1.35;
}

.informant-map { grid-column: 1; justify-self: stretch; }

.informant-foot {
  grid-column: 2;
  grid-row: 1 / span 3;
  display: grid;
  align-content: center;
  justify-items: end;
  gap: calc(3px * var(--ui-scale));
}

.informant-state {
  min-width: 0;
  color: var(--muted);
  font-size: max(10px, calc(10px * var(--ui-scale)));
  font-variant-numeric: tabular-nums;
  font-weight: 850;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.informant-state.is-live { color: #c9a6e8; }

.informant-state.is-cooldown { color: #8fb8d8; }

.informant-buy { min-width: calc(64px * var(--ui-scale)); }

.informant-status { margin-block-start: auto; }

body.high-contrast .informant-card { background: #0b1016; }

[data-ui-profile="handheld"] .informant-panel {
  width: min(calc(700px * var(--ui-scale)), calc(100vw - var(--hud-safe-left) - var(--hud-safe-right)));
  max-height: var(--hud-window-max-h);
}

[data-ui-profile="handheld"] .informant-cards {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}

[data-ui-profile="handheld"] .informant-buy {
  min-width: var(--hud-touch-min);
  min-height: var(--hud-touch-min);
  padding-inline: 10px;
  font-size: 12px;
}

/* Player-to-player trade. Two facing offer plates, the ritual rail between the
   deal and the bag strip, and one detail card that reads whatever the pointer
   rests on. The frame itself comes from the shared service-window family. */

.trade-panel {
  width: min(calc(560px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(700px * var(--ui-scale)), calc(100vh - 48px));
}

.trade-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: calc(8px * var(--ui-scale));
}

/* The partner is who this window is about, so the name carries the accent while
   the window keeps its own one-word title. */

.trade-partner {
  min-width: 0;
  overflow: hidden;
  color: color-mix(in srgb, var(--accent) 70%, var(--text));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trade-body {
  display: flex;
  flex-direction: column;
  gap: calc(9px * var(--ui-scale));
  min-height: 0;
  overflow-y: auto;
  padding: calc(11px * var(--ui-scale)) calc(13px * var(--ui-scale)) calc(13px * var(--ui-scale));
}

/* ---- the deal ---- */

.trade-deal {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: calc(9px * var(--ui-scale));
  min-width: 0;
}

.trade-plate {
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--ui-scale));
  min-width: 0;
  padding: calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(7px * var(--ui-scale));
  background: rgba(7, 13, 15, 0.4);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

/* A plate that has stopped moving says so on its own edge: locked takes the
   accent, confirmed the success tone. Neither is hue alone — the state word sits
   in the plate's own header. */

.trade-plate[data-state='locked'] {
  border-color: color-mix(in srgb, var(--accent) 62%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 16%, transparent);
}

.trade-plate[data-state='confirmed'] {
  border-color: color-mix(in srgb, var(--good) 58%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--good) 16%, transparent);
}

.trade-plate-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(6px * var(--ui-scale));
  min-width: 0;
}

.trade-plate-title {
  overflow: hidden;
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trade-plate-state {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.trade-plate[data-state='locked'] .trade-plate-state { color: var(--accent); }

.trade-plate[data-state='confirmed'] .trade-plate-state { color: var(--good); }

.trade-plate-body {
  display: flex;
  align-items: center;
  padding: calc(4px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(0, 0, 0, 0.22);
}

/* A fixed lattice, not a list that grows a box: both plates hold the same shape
   whatever is in them, so a partner adding an item never reflows the deal. */

.trade-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-content: start;
  justify-items: center;
  gap: calc(4px * var(--ui-scale));
  width: 100%;
}

/* Square, and capped: a plate given a whole narrow screen must not blow its
   cells up to the size of the art they hold. */

.trade-list .item-slot {
  --slot-size: min(100%, calc(56px * var(--ui-scale)));
  width: min(100%, calc(56px * var(--ui-scale)));
  aspect-ratio: 1;
  height: auto;
}

.trade-list .item-slot.filled { animation: trade-cell-in 160ms ease-out; }

.trade-list .item-slot.empty {
  background: rgba(255, 255, 255, 0.018);
  border-color: var(--ui-line-soft);
  box-shadow: none;
}

.trade-list .item-slot.empty::before { background: none; }

.trade-plate-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(6px * var(--ui-scale));
  font-size: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.trade-plate-count { color: var(--muted); }

.trade-plate-gold { color: var(--accent); font-weight: 700; }

/* ---- the detail card ---- */

.trade-detail {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  min-height: calc(46px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(255, 255, 255, 0.025);
}

.trade-detail.is-empty { grid-template-columns: minmax(0, 1fr); }

.trade-detail .item-slot { --slot-size: calc(38px * var(--ui-scale)); }

.trade-detail-copy { display: grid; gap: calc(2px * var(--ui-scale)); min-width: 0; }

.trade-detail-name {
  overflow: hidden;
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 650;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trade-detail-facts { display: grid; gap: calc(1px * var(--ui-scale)); min-width: 0; }

.trade-detail-tier {
  margin-inline-start: calc(7px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.trade-detail-hint { color: var(--muted); font-size: calc(10px * var(--ui-scale)); }

/* ---- the ritual rail ---- */

/* Four columns: who, then one per step. Both rows are read at a glance, which
   is the whole question a trade asks — who has moved and who has not. */

.trade-steps {
  display: grid;
  grid-template-columns: auto repeat(3, 1fr);
  align-items: center;
  gap: calc(3px * var(--ui-scale)) calc(6px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) calc(11px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(0, 0, 0, 0.18);
}

.trade-step-name {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.09em;
  text-align: center;
  text-transform: uppercase;
}

.trade-step-name.is-current {
  color: var(--accent);
  text-shadow: 0 0 calc(10px * var(--ui-scale)) color-mix(in srgb, var(--accent) 40%, transparent);
}

.trade-steps-who {
  max-width: calc(90px * var(--ui-scale));
  padding-inline-end: calc(6px * var(--ui-scale));
  overflow: hidden;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The track that turns three states into progress: each cell draws the rail
   behind its own node, and the end cells draw only their half of it. */

.trade-pip-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(16px * var(--ui-scale));
}

.trade-pip-cell::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--ui-line-soft);
}

.trade-pip-cell[data-pos='first']::before { left: 50%; }

.trade-pip-cell[data-pos='last']::before { right: 50%; }

.trade-pip {
  position: relative;
  width: calc(11px * var(--ui-scale));
  height: calc(11px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: 999px;
  background: #0a1013;
  transition: background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}

.trade-pip.is-reached {
  border-color: color-mix(in srgb, var(--accent) 78%, transparent);
  background: var(--accent);
  box-shadow:
    0 0 0 calc(2px * var(--ui-scale)) color-mix(in srgb, var(--accent) 16%, transparent),
    0 0 calc(7px * var(--ui-scale)) color-mix(in srgb, var(--accent) 45%, transparent);
}

/* ---- the bag strip ---- */

.trade-stage {
  display: flex;
  flex-direction: column;
  gap: calc(6px * var(--ui-scale));
  padding-top: calc(7px * var(--ui-scale));
  border-top: 1px solid var(--ui-line-soft);
}

.trade-stage-head {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.trade-bag {
  display: flex;
  gap: calc(4px * var(--ui-scale));
  overflow-x: auto;
  padding-bottom: calc(3px * var(--ui-scale));
}

.trade-bag-cell {
  flex: 0 0 auto;
  padding: calc(2px * var(--ui-scale));
  border: 1px solid transparent;
  border-radius: calc(8px * var(--ui-scale));
  background: transparent;
  cursor: var(--cursor-action);
}

.trade-bag-cell .item-slot { --slot-size: calc(42px * var(--ui-scale)); }

.trade-bag-cell.is-selected {
  border-color: var(--accent);
  background: var(--row-hover);
}

.trade-bag-cell:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.trade-bag-cell:disabled { cursor: not-allowed; opacity: 0.45; }

.trade-bag-empty {
  margin: 0;
  padding: calc(12px * var(--ui-scale)) 0;
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  text-align: center;
}

.trade-offer-picker {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  flex-wrap: wrap;
}

.trade-amount,
.trade-gold-entry { display: flex; align-items: center; gap: calc(3px * var(--ui-scale)); }

/* The gold lane sits opposite the item lane, with its own hairline between them
   so the two acts never read as one long row of controls. */

.trade-gold-entry {
  margin-inline-start: auto;
  padding-inline-start: calc(10px * var(--ui-scale));
  border-inline-start: 1px solid var(--ui-line-soft);
}

.trade-gold-mark {
  color: var(--accent);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 800;
}

.trade-offer-picker input {
  width: calc(74px * var(--ui-scale));
  min-height: calc(28px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

.trade-amount input { width: calc(54px * var(--ui-scale)); text-align: center; }

/* The picker's steppers and offer button are kit buttons at the row's own
   height; the surface only seats them. */

.trade-step-btn,
.trade-offer-btn { min-height: calc(28px * var(--ui-scale)); }

.trade-step-btn { min-width: calc(28px * var(--ui-scale)); padding: 0 calc(5px * var(--ui-scale)); }

.trade-offer-btn { padding: 0 calc(11px * var(--ui-scale)); }

/* ---- commit ---- */

/* Weight carries the ladder: Confirm is the gold act, Lock the step before it,
   Cancel the quiet way out on the far side of the row. */

.trade-actions {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  padding-top: calc(9px * var(--ui-scale));
  border-top: 1px solid var(--ui-line-soft);
}

.trade-actions-gap { flex: 1 1 auto; }

/* The commit row's three weights — Confirm gold, Lock the matte control,
   Cancel danger — are the kit's variants at this row's height. */

.trade-quit,
.trade-secondary,
.trade-primary {
  min-height: calc(31px * var(--ui-scale));
  padding: 0 calc(14px * var(--ui-scale));
  font-size: calc(12px * var(--ui-scale));
}

@keyframes trade-cell-in {
  from { opacity: 0; transform: scale(0.86); }
  to { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .trade-list .item-slot.filled { animation: none; }
  .trade-plate,
  .trade-pip { transition: none; }
}

body.reduce-motion .trade-list .item-slot.filled { animation: none; }

body.reduce-motion .trade-plate,
body.reduce-motion .trade-pip { transition: none; }

/* One column of plates once the window is narrower than two can hold, and the
   bag strip's controls stack rather than squeeze. */

@media (max-width: 560px) {
  .trade-deal { grid-template-columns: minmax(0, 1fr); }
  .trade-gold-entry {
    margin-inline-start: 0;
    padding-inline-start: 0;
    border-inline-start: 0;
  }
}

/* ===== NPC conversation ================================================
   A talk is staged, not printed: the speaker's own world illustration stands
   at the left of a wide plate, the script's words land a character at a time,
   and every answer wears the key that picks it.
   The frame, the gold and the panel fill are the shared HUD chrome — this
   surface adds the stage, not a second visual language. */

/* The talk stands in the bottom-centre rail, above the action bars and under the
   cast lane. The plate takes a share of the screen's height — a share, because
   what it is holding clear is the top of the screen — but never less than the
   room an answer needs: a talk you cannot read is a nuisance, a talk you cannot
   answer is a trap. Its answers scroll when a long menu asks for more than that. */

.npc-win {
  bottom: 90px; left: 50%; transform: translateX(-50%);
  width: calc(560px * var(--ui-scale)); max-width: 92vw;
  max-height: max(36vh, calc(240px * var(--ui-scale)));
  flex-direction: row; align-items: stretch; gap: 14px;
  background: var(--ui-surface-raised);
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(8px * var(--ui-scale));
  padding: 15px 18px; pointer-events: auto;
  box-shadow: 0 16px 40px rgba(0,0,0,0.55);
}

/* An offer is a whole page — title, asks, pay, answer — so its window may take
   more of the screen than a talk line; the answers area still scrolls if a small
   viewport cannot seat the card whole. */

.npc-win.npc-offer {
  max-height: max(62vh, calc(430px * var(--ui-scale)));
}

.npc-body { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; }

/* Placed by the HUD layout editor, the window carries nothing but its label. */

.npc-win.hud-layout-ghost .npc-body, .npc-win.hud-layout-ghost .npc-speaker { display: none; }

/* ---- the speaker -------------------------------------------------------
   The standee is taller than its column and rises out of the frame, so the
   figure reads as standing in front of the player rather than pasted into a
   tile. It is decorative: the name plate carries the identity. */

.npc-speaker {
  position: relative; flex: 0 0 calc(140px * var(--ui-scale));
  align-self: stretch; min-height: calc(124px * var(--ui-scale));
}

/* The standee grows with the conversation: a one-line greeting gets a figure the
   height of the plate it rises out of, a long menu gets a full-length one. */

.npc-speaker-art {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: calc(204px * var(--ui-scale));
  height: calc(100% + 88px);
  min-height: calc(196px * var(--ui-scale)); max-height: calc(340px * var(--ui-scale));
  object-fit: contain; object-position: bottom center;
  filter: drop-shadow(0 10px 16px rgba(0,0,0,0.65)) drop-shadow(0 0 16px rgba(230,204,128,0.12));
  transform-origin: 50% 100%;
  animation: npc-speaker-breathe 4.6s ease-in-out infinite;
  pointer-events: none;
}

/* A warm wash behind the figure, so the art separates from the panel fill. */

.npc-speaker-glow {
  position: absolute; left: 50%; bottom: calc(18px * var(--ui-scale));
  width: calc(190px * var(--ui-scale)); height: calc(190px * var(--ui-scale));
  transform: translateX(-50%);
  background: radial-gradient(ellipse at 50% 55%, rgba(230,204,128,0.17), rgba(230,204,128,0.05) 46%, transparent 70%);
  pointer-events: none;
}

/* The contact shadow that puts their feet on the panel. */

.npc-speaker-floor {
  position: absolute; left: 50%; bottom: calc(-2px * var(--ui-scale));
  width: calc(112px * var(--ui-scale)); height: calc(18px * var(--ui-scale));
  transform: translateX(-50%);
  background: radial-gradient(ellipse at 50% 50%, rgba(0,0,0,0.55), transparent 70%);
  pointer-events: none;
}

@keyframes npc-speaker-breathe {
  0%, 100% { transform: translateX(-50%) scaleY(1); }
  50% { transform: translateX(-50%) scaleY(1.012); }
}

body.reduce-motion .npc-speaker-art { animation: none; }

/* ---- who is speaking --------------------------------------------------- */

.npc-nameplate { display: flex; align-items: center; gap: 9px; margin: -2px 0 9px; }

.npc-name {
  color: var(--accent); font-weight: 800;
  font-size: calc(15px * var(--ui-scale)); letter-spacing: 0.06em;
  text-transform: uppercase; text-shadow: 0 0 12px rgba(230,204,128,0.28);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The rule runs out from the name to the far edge and fades — a spoken line
   opening, not a box. */

.npc-nameplate-rule {
  flex: 1 1 auto; height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 70%, transparent), transparent);
}

/* The plate a script's cutin gets when the speaker has no illustration. */

.npc-cutin {
  grid-template-columns: 52px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  margin: -4px 0 12px;
  padding: 8px;
  border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--ui-line-soft));
  border-radius: 8px;
  background: linear-gradient(90deg, rgba(230, 204, 128, 0.12), rgba(13, 18, 27, 0.72));
}

.npc-cutin-sigil {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border: 1px solid color-mix(in srgb, var(--accent) 58%, transparent);
  border-radius: 8px;
  background:
    radial-gradient(circle at 35% 25%, rgba(255,255,255,0.18), transparent 36%),
    linear-gradient(145deg, rgba(230,204,128,0.28), rgba(59,71,86,0.24));
  color: var(--accent);
  font-weight: 800;
  font-size: calc(15px * var(--ui-scale));
  letter-spacing: 0;
  box-shadow: inset 0 0 18px rgba(230,204,128,0.08);
}

.npc-cutin-copy { min-width: 0; }

.npc-cutin-name {
  color: var(--accent);
  font-weight: 800;
  font-size: calc(13px * var(--ui-scale));
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.npc-cutin-id {
  margin-top: 2px;
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- the tomb memorial -------------------------------------------------
   A boss tomb's body: the fallen boss's own card, printed as a spirit — drained
   of colour, brightened, translucent, adrift — beside the facts of its fall.
   The tomb itself remains the speaker standee; this figure is the one being
   remembered, so it floats inside the page rather than standing on the frame. */

.npc-tomb-slot { margin: 0 0 10px; }

.npc-tomb { display: flex; align-items: center; gap: 14px; }

.npc-tomb-spirit {
  flex: 0 0 auto;
  display: grid; place-items: end center;
  width: calc(84px * var(--ui-scale));
}

.npc-tomb-spirit-img {
  max-width: calc(84px * var(--ui-scale));
  max-height: calc(112px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 0 14px rgba(186, 208, 236, 0.4));
  opacity: 0.55;
  animation: npc-tomb-drift 5.5s ease-in-out infinite;
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent);
  mask-image: linear-gradient(to bottom, #000 78%, transparent);
}

/* Stand-in while grayscalePrintImageUrl bakes the card through the 'spirit'
   recipe, and wherever it cannot bake: a CSS approximation of that recipe.
   The baked print replaces the src and drops this class. */

.npc-tomb-spirit-unprinted {
  filter: grayscale(1) brightness(1.6) drop-shadow(0 0 14px rgba(186, 208, 236, 0.4));
}

@keyframes npc-tomb-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(calc(-5px * var(--ui-scale))); }
}

@media (prefers-reduced-motion: reduce) {
  .npc-tomb-spirit-img { animation: none; }
}

.npc-tomb-copy { min-width: 0; display: flex; flex-direction: column; gap: 3px; }

.npc-tomb-name {
  color: var(--accent);
  font-weight: 800;
  font-size: calc(15px * var(--ui-scale));
  margin-bottom: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.npc-tomb-line {
  color: var(--text);
  opacity: 0.85;
  font-size: calc(13px * var(--ui-scale));
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}

.npc-tomb-returns { color: var(--accent); opacity: 1; }

/* ---- the words ---------------------------------------------------------
   Every character is in the page from the first frame; only its landing is
   animated, so skipping the reveal costs nothing and reads nothing back. */

/* The words land in a block that is already the size it will be: a one-line
   greeting and a five-line paragraph leave the plate, the speaker and the answer
   button exactly where the player last saw them, instead of the window growing
   and shrinking under the cursor page after page. A longer speech scrolls inside
   the block. The answers below it are the one thing that still changes the
   window's height, because a list of six choices is not a page of one. */

.npc-text {
  /* Five lines wherever there is room, and the first thing to give way when the
     plate is capped shorter than the page it is holding. */
  flex: 0 1 auto;
  height: calc(14px * var(--ui-scale) * 1.55 * 5);
  min-height: calc(14px * var(--ui-scale) * 1.55 * 2);
  overflow-y: auto;
  overscroll-behavior: contain;
  white-space: pre-wrap; margin-bottom: 12px;
  font-size: calc(14px * var(--ui-scale)); line-height: 1.55;
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}

.npc-ch { animation: npc-ch-in 150ms ease-out backwards; }

@keyframes npc-ch-in {
  from { opacity: 0; filter: blur(2px); }
  to { opacity: 1; filter: blur(0); }
}

.npc-text-settled .npc-ch, body.reduce-motion .npc-ch { animation: none; }

.npc-progress { width: 100%; margin: 2px 0 12px; }

.npc-progress-track { position: relative; overflow: hidden; height: 12px; border: 1px solid var(--ui-control-line); border-radius: 999px; background: #111720; box-shadow: inset 0 1px 2px rgba(0,0,0,0.55); }

.npc-progress-fill { height: 100%; width: 0%; background: var(--accent); box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent); }

.npc-progress-text { margin-top: 5px; color: var(--muted); font-size: calc(12px * var(--ui-scale)); font-variant-numeric: tabular-nums; text-align: center; }

body.reduce-motion .npc-progress-fill { transition: none !important; }

/* ---- the answers ------------------------------------------------------- */

.npc-controls {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch; margin-top: auto;
  min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  /* The panel scrolls, so it reserves the travel a held button sinks by: a sunk
     box is scrollable overflow, and without room to sink into, a button held
     down raises this panel's scrollbar for as long as it is held. */
  padding-bottom: var(--ui-press-travel, 1px);
}

/* The answer buttons are the kit's primary/ghost; the plate only seats them
   (right-aligned, like a spoken reply) and breathes the primary one once the
   line has landed. */

.npc-controls .ron-btn { align-self: flex-end; }

.npc-said .npc-controls .ron-btn--primary { animation: npc-btn-wait 1.6s ease-in-out infinite; }

@keyframes npc-btn-wait {
  0%, 100% { box-shadow: 0 0 0 rgba(230,204,128,0); }
  50% { box-shadow: 0 0 14px rgba(230,204,128,0.45); }
}

body.reduce-motion .npc-said .npc-controls .ron-btn--primary { animation: none; }

.npc-menu-item {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: 10px; width: 100%; padding: 7px 10px; text-align: left;
  background: var(--panel-2);
  border: 1px solid var(--ui-control-line); border-radius: 6px;
  color: var(--accent);
  transition: border-color 120ms ease, background 120ms ease;
}

.npc-menu-item:hover, .npc-menu-item:focus-visible {
  border-color: var(--accent);
  background: linear-gradient(90deg, rgba(230,204,128,0.14), var(--panel-2) 60%);
}

.npc-menu-item:focus-visible, .npc-input:focus-visible {
  outline: calc(2px * var(--ui-scale)) solid var(--accent); outline-offset: 2px;
}

/* The key that picks the entry, worn as a keycap so the shortcut is visible
   instead of learned. */

.npc-menu-key {
  display: grid; place-items: center;
  min-width: calc(22px * var(--ui-scale)); height: calc(22px * var(--ui-scale));
  padding: 0 4px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--ui-control-line));
  border-radius: 5px; background: rgba(8,11,17,0.72);
  color: var(--accent); font-size: calc(11px * var(--ui-scale)); font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.npc-menu-item:hover .npc-menu-key, .npc-menu-item:focus-visible .npc-menu-key {
  background: color-mix(in srgb, var(--accent) 22%, rgba(8,11,17,0.72));
}

.npc-menu-face { min-width: 0; }

/* What is waiting at the place this option sends you: the same mark the NPCs
   standing there wear, at the far edge of the row so a list of destinations
   sorts itself down one column. */

.npc-menu-quest-mark {
  width: calc(20px * var(--ui-scale)); height: calc(20px * var(--ui-scale));
  object-fit: contain;
}

.npc-menu-cancel { color: var(--muted); }

.npc-menu-cancel .npc-menu-key { color: var(--muted); border-color: var(--ui-control-line); }

/* An option that hands something over reads as the thing itself: its art beside
   the line the NPC said, with the real name and the stats that decide the pick. */

.npc-menu-item-rich { padding: 8px 10px; }

.npc-menu-item-rich .npc-menu-face { display: flex; align-items: center; gap: 10px; }

.npc-item-art { display: flex; flex: 0 0 auto; gap: 4px; }

.npc-item-icon {
  width: calc(40px * var(--ui-scale)); height: calc(40px * var(--ui-scale));
  border: 1px solid var(--ui-control-line); border-radius: 6px; background: #111720; object-fit: contain;
}

.npc-item-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.npc-item-label { color: var(--text); }

.npc-item-name { color: var(--accent); font-size: calc(12px * var(--ui-scale)); }

.npc-item-stats { color: var(--muted); font-size: calc(12px * var(--ui-scale)); font-variant-numeric: tabular-nums; }

.npc-input {
  width: 100%; padding: 8px 10px; background: var(--panel-2);
  border: 1px solid var(--ui-control-line); border-radius: 6px; color: var(--text);
}

/* ---- the quest offer ---------------------------------------------------
   The premium accept/decline moment: what the quest asks and what it pays,
   framed in the dialog's own gold, with Accept as the one bright thing on
   the plate. It rises in once; a redraw for late item art does not replay it. */

.npc-quest-offer {
  display: flex; flex-direction: column; gap: 7px;
  margin-bottom: 10px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--ui-line-soft));
  border-radius: 8px;
  background:
    radial-gradient(circle at 14% 0%, rgba(230,204,128,0.14), transparent 44%),
    linear-gradient(180deg, rgba(230,204,128,0.08), rgba(13,18,27,0.78));
  box-shadow: inset 0 0 22px rgba(230,204,128,0.05);
}

.npc-quest-offer-enter { animation: npc-quest-offer-in 180ms ease-out; }

@keyframes npc-quest-offer-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

body.reduce-motion .npc-quest-offer-enter { animation: none; }

@media (prefers-reduced-motion: reduce) { .npc-quest-offer-enter { animation: none; } }

.npc-quest-offer-eyebrow {
  color: color-mix(in srgb, var(--accent) 72%, var(--muted));
  font-size: calc(10px * var(--ui-scale)); font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase;
}

.npc-quest-offer-title {
  color: var(--accent); font-weight: 800;
  font-size: calc(16px * var(--ui-scale)); letter-spacing: 0.02em;
  text-shadow: 0 0 12px rgba(230,204,128,0.28);
}

.npc-quest-offer-desc {
  color: var(--text); font-size: calc(13px * var(--ui-scale)); line-height: 1.5;
}

.npc-quest-offer-objectives {
  display: flex; flex-direction: column; gap: 4px;
  margin: 2px 0 0; padding: 0; list-style: none;
}

.npc-quest-offer-objective {
  display: flex; align-items: center; gap: 8px;
  font-size: calc(13px * var(--ui-scale));
}

.npc-quest-offer-objective-icon {
  width: calc(18px * var(--ui-scale)); height: calc(18px * var(--ui-scale));
  object-fit: contain; filter: drop-shadow(0 1px 2px #000);
}

.npc-quest-offer-objective-label { min-width: 0; }

/* The count sits on the far edge of its line, in the gold of the ask. */

.npc-quest-offer-count {
  margin-inline-start: auto;
  color: var(--accent); font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.npc-quest-offer-rewards-title {
  margin-top: 3px; padding-top: 8px;
  border-top: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  color: color-mix(in srgb, var(--accent) 72%, var(--muted));
  font-size: calc(10px * var(--ui-scale)); font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase;
}

.npc-quest-offer-rewards { display: flex; flex-direction: column; gap: 6px; }

.npc-quest-offer-reward { display: flex; align-items: center; gap: 10px; }

/* The reward wears the bag's own slot scale, so it reads as the thing that
   will land in the inventory. */

.npc-quest-offer-reward-icon {
  width: calc(44px * var(--ui-scale)); height: calc(44px * var(--ui-scale));
  border: 1px solid var(--ui-control-line); border-radius: 6px; background: #111720; object-fit: contain;
}

.npc-quest-offer-reward-name { color: var(--text); font-size: calc(13px * var(--ui-scale)); min-width: 0; }

.npc-quest-offer-actions { display: flex; justify-content: flex-end; gap: 8px; }

.npc-quest-offer-actions .ron-btn { align-self: auto; }

/* A met hand-in line reads as done the way the tracker's checked rows do. */

.npc-quest-turnin-objective-done .npc-quest-offer-objective-label {
  color: var(--good, #63c08a); text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--good, #63c08a) 75%, transparent);
}

.npc-quest-turnin-objective-done .npc-quest-offer-count { color: var(--good, #63c08a); }

/* Hotbar (bottom-center) */

.hotbar {
  bottom: 12px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 6px;
}

.hot-slot {
  width: calc(56px * var(--ui-scale)); height: calc(56px * var(--ui-scale));
  background: linear-gradient(145deg, rgba(29, 40, 43, 0.96), rgba(9, 15, 17, 0.98));
  border: 1px solid var(--ui-line); border-radius: 5px; position: relative;
  display: flex; align-items: flex-end; justify-content: center; padding: 4px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), inset 0 -2px 5px rgba(0,0,0,0.34);
  transition: border-color .12s, box-shadow .12s, transform .08s;
}

.hot-slot.filled { border-color: #56676b; box-shadow: inset 0 0 0 1px rgba(184, 203, 202, 0.08); }

.hot-slot:hover { border-color: var(--accent); transform: translateY(-1px); }

/* Top-left corner badge: the assigned hotkey, the slot's primary identity
 * (skills are single-rank, so no level is shown). A pill keeps it legible over
 * the icon. */

.hot-key {
  position: absolute; top: 2px; left: 4px; z-index: 3; line-height: 1;
  font-size: calc(11px * var(--ui-scale)); font-weight: 700;
  color: #eef1f5; text-shadow: 0 1px 2px #000;
  padding: 1px 4px; border-radius: 5px; background: rgba(8,12,18,0.6);
}

.hot-slot.needs-target { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 32%, transparent), 0 2px 8px rgba(0,0,0,0.45); }

/* Bottom-right corner badge: item stack count only (skills leave it empty). */

.hot-name {
  position: absolute; right: 3px; bottom: 2px; z-index: 2;
  font-size: calc(11px * var(--ui-scale)); font-weight: 700; line-height: 1;
  color: #eef1f5; text-shadow: 0 1px 2px #000;
  padding: 1px 4px; border-radius: 5px; background: rgba(8,12,18,0.6);
  max-width: 84%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.hot-name:empty { display: none; }

/* Chat log (lives inside .chat-wrap; positioning resolved there, not fixed here) */

.chat-log {
  /* Height is owned by the resizable rule below (--chat-log-h); keep only a
     viewport safety cap so a large drag can't push the panel off-screen. */
  max-height: 72vh;
  overflow-y: auto;
  padding: 2px 2px; font-size: calc(12px * var(--ui-scale));
  pointer-events: auto;
}

.chat-line { margin: 1px 0; line-height: 1.3; word-wrap: break-word; font-size: calc(12px * var(--ui-scale)); }

.chat-line.public { color: var(--text); }

.chat-line.system { color: var(--accent); }

.chat-line.whisper { color: #4fd0e0; }

.chat-line.party { color: #5fd35f; }

.chat-line.guild { color: #e0913d; }

/* An entity a line of copy names, drawn as that entity. The kind classes carry
   the accent; an item overrides it inline with the rarity it is stamped at, so
   one authored sentence never has to know what colour the thing it names is. */

.entity-ref { font-weight: 600; }

.entity-ref-skill { color: var(--accent-2); }

.entity-ref-status { color: var(--accent); }

/* Inventory / skill rows (equipped vs bag, action buttons) */

.inv-section { display:flex; align-items:center; gap:6px; font-weight:600; color:var(--accent); margin:8px 0 4px; font-size:calc(12px * var(--ui-scale)); text-transform:uppercase; letter-spacing:0.5px; border-bottom:1px solid var(--ui-line-soft); padding-bottom:4px; }

/* Small accent tab before a section label, premium-HUD style. */

.inv-section::before { content:''; flex:0 0 auto; width:3px; height:12px; border-radius:2px; background:linear-gradient(180deg, var(--accent), var(--accent-2)); }

.inv-empty { color:var(--muted); font-size:12px; padding:2px 0 6px; }

/* Inventory footer — labelled weight gauge + gold purse, pinned at the bottom. */

.inv-footer { flex:0 0 auto; display:flex; align-items:center; gap:calc(12px * var(--ui-scale)); padding-top:9px; margin-top:2px; border-top:1px solid var(--ui-line-soft); }

/* Weight gauge: icon + track + numeric readout. */

.inv-weight { flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:calc(7px * var(--ui-scale)); }

.inv-weight-ico { flex:0 0 auto; display:inline-flex; color:#8a93a3; }

.inv-weight-ico .inv-ico-img { width:calc(19px * var(--ui-scale)); height:calc(19px * var(--ui-scale)); object-fit:contain; display:block; }

/* The track is the shared `.hud-gauge --sm`; the footer only says how wide it
   sits. The number stays outside the bar here — a window footer has the room
   the always-on panel does not. */

.inv-weight-bar { flex:1 1 0; width:auto; min-width:0; }

.inv-weight-text { flex:0 0 auto; font-size:calc(11px * var(--ui-scale)); font-weight:700; color:#c7ceda; font-variant-numeric:tabular-nums; white-space:nowrap; }

/* The band's edge colour, not its fill or the muted warn tone: this is text on a
   dark panel, and a state that escalates must not make its own number harder to
   read than the resting one. */

.inv-weight-text[data-band="heavy"] { color:var(--hud-load-heavy-ring); }

.inv-weight-text[data-band="overloaded"] { color:var(--hud-load-overloaded-ring); }

/* Gold purse. */

.inv-gold { flex:0 0 auto; display:inline-flex; align-items:center; gap:calc(5px * var(--ui-scale)); }

.inv-gold-ico { display:inline-flex; color:#f2cf7a; filter:drop-shadow(0 1px 1px rgba(0,0,0,0.5)); }

.inv-gold-ico .inv-ico-img { width:calc(21px * var(--ui-scale)); height:calc(21px * var(--ui-scale)); object-fit:contain; display:block; }

.inv-gold-val { font-size:calc(12px * var(--ui-scale)); font-weight:800; color:var(--gold-value); font-variant-numeric:tabular-nums; white-space:nowrap; text-shadow:0 1px 1px #000; }

.inv-row { display:flex; justify-content:space-between; align-items:center; gap:8px; padding:4px 0; }

.inv-row-name { font-size:calc(12px * var(--ui-scale)); flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Equipped rows stack the name over a typed-capability sub-line (design §6:
   "items decide who you are"), so the cell becomes a vertical flex column. */

.inv-row-name--stacked { display:flex; flex-direction:column; gap:1px; overflow:visible; white-space:normal; }

.inv-row-label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.inv-row-opts { font-size:calc(10px * var(--ui-scale)); opacity:0.85; line-height:1.2; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.inv-row-acts { display:flex; gap:4px; flex-shrink:0; }

.inv-act {
  pointer-events:auto; font-size:11px; padding:3px 8px; cursor: var(--cursor-action);
  background:var(--panel-2); border:1px solid var(--accent); border-radius:5px; color:var(--accent);
}

.inv-act:hover { background:var(--accent); color:#1a1206; }

.inv-act.drop { border-color:#5a4453; color:#a98; }

.inv-act.drop:hover { background:#7a3a3a; color:#fff; border-color:#7a3a3a; }

/* Target HUD (top-center) with HP bar */

.target-panel { flex-direction: column;
  align-items: center; gap: 4px; background: transparent; border: 0;
  border-radius: 0; padding: 0; min-width: 160px; pointer-events: none; }

.target-name { font-weight: 700; font-size: calc(14px * var(--ui-scale)); text-shadow: 0 1px 2px #000; }

/* The focused unit's health is the shared `.hud-gauge` at the same height the
   character's own vitals use — the two frames face each other across the screen
   and must read as one kind of thing. The fill takes the shared health bands and
   the out-of-range ring is the gauge's `--oor` state, so nothing here is local. */

/* Target unit frame (ElvUI-grade): horizontal portrait tile + body column. The
   base .target-panel rule above keeps top/left/transform/pointer-events; this
   only reshapes the panel from a column into a row and adds the new sub-nodes. */

.target-panel.unit-frame { flex-direction: row; align-items: flex-start; gap: 8px;
  width: min(78vw, calc(278px * var(--ui-scale)));
  min-width: min(70vw, calc(232px * var(--ui-scale))); max-width: min(78vw, calc(278px * var(--ui-scale)));
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.76)); }

/* The unit medallion, shared by the character the player is and the one they have
   selected. One tile, so a face never reads one way in the corner of the screen
   and another way beside it. */

.unit-portrait { position: relative; width: calc(50px * var(--ui-scale)); height: calc(50px * var(--ui-scale));
  flex: 0 0 auto; border-radius: 50%; border: 1px solid rgba(196, 211, 208, 0.55); overflow: hidden;
  background: #0c1016 center/cover no-repeat; display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
  color: #9aa7b5; }

/* Live model viewport — absolutely fills the tile; shown only when a model is
   mounted (.has-3d), which also drops the drawn art behind it. */

.unit-portrait > canvas.unit-portrait-live { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; display: none; }

.unit-portrait.has-3d > canvas.unit-portrait-live { display: block; }

.unit-portrait.has-3d { background-image: none !important; }

/* The unit's own world illustration (.has-art) — the same art its billboard draws.
   Creatures keep their whole body inside the circle; humanoids, whose faces carry the
   read, are framed on the head and shoulders. */

.unit-portrait.has-art { background-size: cover; background-position: 50% 24%; }

.unit-portrait.has-art.art-humanoid { background-size: auto 260%; background-position: 50% 4%; }

.target-portrait.elite { border-color: #ffb030; box-shadow: 0 0 6px rgba(255,176,48,.45); }

.target-portrait.mvp { border-color: #ffd54a; box-shadow: 0 0 8px rgba(255,213,74,.55);
  animation: tgt-mvp-breath 2.4s ease-in-out infinite; }

@keyframes tgt-mvp-breath { 0%,100% { box-shadow: 0 0 6px rgba(255,213,74,.40); } 50% { box-shadow: 0 0 12px 2px rgba(255,213,74,.70); } }

/* Identity block, then gauges: the name and its caption sit tight together, and
   the first gauge takes the only real gap in the column. */

.target-body { display: flex; flex-direction: column; flex: 1; min-width: 0; gap: calc(2px * var(--ui-scale)); }

.target-head { display: flex; align-items: baseline; gap: 8px; justify-content: space-between; }

.target-body > .target-hp { margin-top: calc(3px * var(--ui-scale)); }

.unit-frame .target-name { display: flex; align-items: center; gap: 5px; font-weight: 700; font-size: calc(14px * var(--ui-scale)); letter-spacing: .01em; text-shadow: 0 1px 2px #000;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }

.target-tier-icon { width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale)); object-fit: contain; flex: none; }

.target-dist { font-size: max(9px, calc(10px * var(--ui-scale))); color: #7f8b99; font-variant-numeric: tabular-nums;
  white-space: nowrap; flex: 0 0 auto; transition: color 0.12s ease; }

.target-dist.oor { color: var(--danger,#e0533d); font-weight: 700; }

/* The unit's classification is a caption, not a control strip: one divided
   line under the name, kept to a single row so a cast or a status row never shifts
   down when a target with more facts is picked. */

/* A caption too long for the frame is cut at the row, never inside a token: each
   token keeps its own width and the row's trailing fade shows the reading
   continues past the edge. */

.target-class { display: flex; align-items: baseline; gap: 0; flex-wrap: nowrap;
  min-width: 0; overflow: hidden;
  mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent); }

.tgt-class-chip { display: inline-flex; align-items: baseline; gap: 3px; flex: 0 0 auto;
  font-size: max(9px, calc(10px * var(--ui-scale))); line-height: 1.45;
  padding: 0; border: 0; border-radius: 0; background: none;
  color: #8e9aa8;
  white-space: nowrap; }

.tgt-class-chip + .tgt-class-chip { margin-left: 6px; padding-left: 6px; border-left: 1px solid #4f5a67; }

.tgt-class-chip img { width: 11px; height: 11px; display: block; align-self: center; }

/* A downed unit is named in the danger red everywhere — painted inline at the
   chip so every palette mode (high contrast brightens the token) reaches it. */

.tgt-class-chip.dead { font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }

/* The focused unit's windup is the HUD gauge at cast height, sitting directly
   under that unit's health — two rows of the same box. A cast is a clock rather
   than a resource, so only the edge and the fill take the tier accent. */

.target-cast {
  --target-cast-accent: var(--cast-strong);
  --gauge-h: max(27px, calc(30px * var(--ui-scale)));
  --gauge-ring: color-mix(in srgb, var(--target-cast-accent) 62%, #373224);
  margin-top: 2px;
}

.target-cast[data-tier='routine'] { --target-cast-accent: var(--cast-routine); }

.target-cast[data-tier='strong'] { --target-cast-accent: var(--cast-strong); }

.target-cast[data-tier='signature'] { --target-cast-accent: var(--cast-signature); }

.target-cast[data-tier='critical'] { --target-cast-accent: var(--cast-critical); }

.target-cast-fill {
  background: linear-gradient(90deg, color-mix(in srgb, var(--target-cast-accent) 68%, #211a10), color-mix(in srgb, var(--target-cast-accent) 42%, transparent));
  transition: width 0.06s linear;
}

.target-cast-icon {
  position: absolute; z-index: 3; left: calc(2px * var(--ui-scale)); top: 50%;
  width: calc(19px * var(--ui-scale)); height: calc(19px * var(--ui-scale));
  transform: translateY(-50%); border: 1px solid color-mix(in srgb, var(--target-cast-accent) 72%, #171b22);
  border-radius: calc(3px * var(--ui-scale)); object-fit: cover; box-shadow: 0 1px 3px rgba(0,0,0,.7);
}

.target-cast-label {
  position: absolute; z-index: 2; left: calc(25px * var(--ui-scale)); right: calc(43px * var(--ui-scale)); top: calc(2px * var(--ui-scale));
  font: 800 calc(10px * var(--ui-scale))/calc(13px * var(--ui-scale)) 'Segoe UI', sans-serif;
  color: #fff; text-shadow: 0 1px 1px #000; pointer-events: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.target-cast-countdown {
  position: absolute; z-index: 3; right: calc(4px * var(--ui-scale)); top: calc(3px * var(--ui-scale));
  color: #fff; font: 800 calc(8px * var(--ui-scale))/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums; text-shadow: 0 1px 1px #000;
}

.target-cast-markers {
  position: absolute; z-index: 4; left: calc(25px * var(--ui-scale)); right: calc(3px * var(--ui-scale)); bottom: calc(2px * var(--ui-scale));
  display: flex; align-items: center; gap: calc(2px * var(--ui-scale));
}

.target-panel[data-hud-anchor-x="right"] .target-cast-icon {
  right: calc(2px * var(--ui-scale));
  left: auto;
}

.target-panel[data-hud-anchor-x="right"] .target-cast-label {
  right: calc(25px * var(--ui-scale));
  left: calc(43px * var(--ui-scale));
  text-align: right;
}

.target-panel[data-hud-anchor-x="right"] .target-cast-countdown {
  right: auto;
  left: calc(4px * var(--ui-scale));
}

.target-panel[data-hud-anchor-x="right"] .target-cast-markers {
  right: calc(25px * var(--ui-scale));
  left: calc(3px * var(--ui-scale));
  flex-direction: row-reverse;
}

.target-cast-marker {
  max-width: calc(74px * var(--ui-scale)); padding: 0 calc(4px * var(--ui-scale));
  overflow: hidden; border: 1px solid rgba(255,255,255,.24); border-radius: 999px;
  background: rgba(8,11,16,.84); color: #f4f6fa; font-size: max(9px, calc(7px * var(--ui-scale)));
  font-weight: 950; letter-spacing: .05em; line-height: 1.25; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap;
}

.target-cast-marker--cc { border-color: #d9a8ff; color: #f0d8ff; }

.target-cast-marker--lethal { border-color: #ff796e; color: #ffd0cb; }

.target-cast-marker--you { border-color: #ff9a8f; background: #7b1f1b; color: #fff; }

/* The panel frame already turns danger-red and glows while the cast is aimed at
   the player, and the chip repeating it in words costs most of the bar. A
   nameplate has no frame to turn, which is the one place the words are the
   only tell. */

.target-panel .target-cast-marker--you { display: none; }

.target-cast-marker--locked { color: #c7ceda; }

.target-cast.targets-player {
  --gauge-ring: #ff7c70;
  --gauge-ring-w: 2px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.72), 0 0 calc(8px * var(--ui-scale)) rgba(255, 75, 60, 0.26);
}

/* An interrupt is a danger beat, so it takes the shared danger tone the cast
   ribbon and the rest of the HUD use rather than a red of its own. */

.target-cast.interrupted { --gauge-ring: var(--danger); }

.target-cast.interrupted .target-cast-fill { background: var(--danger); }

.target-cast.interrupted .target-cast-label { color: color-mix(in srgb, var(--danger) 45%, #fff); }

.target-status { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; justify-content: flex-start; }

@media (prefers-reduced-motion: reduce) {
  .target-portrait.mvp { animation: none; box-shadow: 0 0 8px rgba(255,213,74,.55); }
}

/* Mirrors the shared gauge's reduce-flashing gate. */

body.reduce-flashing .target-portrait.mvp { animation: none; box-shadow: 0 0 8px rgba(255,213,74,.55); }

body.reduce-flashing .target-cast.targets-player,
.reduce-flashing .target-cast.targets-player {
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.72);
}

body.high-contrast .tgt-class-chip { color: #fff; font-weight: 700; }

/* The dead chip is a fact, not chrome: its danger red is painted inline, so
   the base white here never flattens it and no override rule is needed. */

/* A windup keeps its danger tier in high contrast — the tier IS the warning, so
   the shared white ring must not flatten every cast to the same edge. */

body.high-contrast .target-cast {
  --gauge-ring: color-mix(in srgb, var(--target-cast-accent) 82%, #fff);
  --gauge-ring-w: 2px;
}

/* "This one is aimed at me" and "this one broke" outrank the tier, in high
   contrast as everywhere else. */

body.high-contrast .target-cast.targets-player { --gauge-ring: #ff7c70; }

body.high-contrast .target-cast.interrupted { --gauge-ring: var(--danger); }

/* Hotbar slot flash when a skill is assigned */

.hot-slot.flash { animation: hotflash 0.6s ease-out; }

@keyframes hotflash { 0% { box-shadow: 0 0 0 3px var(--accent); } 100% { box-shadow: 0 0 0 0 transparent; } }

/* Hotbar rejected-action pulse, fed by server acks so failures point back to the pressed slot. */

.hot-slot.failed { animation: hotfail 0.42s ease-out; }

@keyframes hotfail {
  0% { border-color: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 72%, transparent); transform: translateX(-2px); }
  35% { transform: translateX(2px); }
  100% { box-shadow: 0 0 0 0 transparent; transform: translateX(0); }
}

body.reduce-flashing .hot-slot.failed { animation: none; border-color: var(--danger); }

/* Hotbar slot 'ready' pulse the moment a skill comes off cooldown (re-press cue) */

.hot-slot.ready { animation: hotready 0.5s ease-out; }

@keyframes hotready { 0% { box-shadow: 0 0 0 3px var(--accent-2, #5fd38a); } 100% { box-shadow: 0 0 0 0 transparent; } }

/* Entity right-click context menu */

/* Popover spawned from a window (bag context menu). z-index sits ABOVE the window
   stacking band (WINDOW_Z_CEIL) so a raised window can never render on top of its
   own menu — which holds only while the menu is NOT inside a .window. A window
   isolates its stacking context, clips its overflow, and (when centered) is the
   containing block for fixed positions, so a menu placed within one is trapped
   in that window's band, cut off, and drawn at the wrong coordinates. */

.context-menu {
  position: fixed; z-index: 200; min-width: 130px; background: rgba(12,16,24,0.97);
  border: 1px solid #3a4453; border-radius: 7px; padding: 4px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  pointer-events: auto; display: flex; flex-direction: column; /* items stack as a list */
}

.context-title { font-weight: 700; font-size: 12px; color: var(--accent); padding: 4px 8px 6px; border-bottom: 1px solid var(--ui-line-soft); }

.context-item.drop { color: #d9a3a3; }

.context-item.drop:hover { background: #7a3a3a; color: #fff; }

/* A menu row is a <button>, so it arrives with native chrome — grey face, OS font,
   centered label. Strip it here rather than per menu: a row that keeps it reads as
   a form control dropped on the HUD. */

.context-item {
  display: block; width: 100%; border: none; background: none; color: inherit; font: inherit;
  text-align: left; font-size: calc(13px * var(--ui-scale)); padding: 7px 10px;
  border-radius: 5px; cursor: var(--cursor-action);
}

.context-item:hover { background: var(--accent); color: #1a1206; }

/* Buff/debuff status tray (top-left, clear of the taller status panel) */

.status-tray-content { display: flex; flex-wrap: wrap; gap: 4px; max-width: calc(220px * var(--ui-scale)); }

.status-icon {
  position: relative;
  width: calc(40px * var(--ui-scale)); height: calc(40px * var(--ui-scale));
  background: rgba(10,14,20,0.9); border: 1px solid var(--accent-2);
  border-radius: 6px; display: flex; flex-direction: column; align-items: center; justify-content: center;
  pointer-events: auto; cursor: var(--cursor-default);
  --frac: 1;
}

/* Duration-depletion ring: a conic sweep (full at --frac:1, empty at 0) lets a
   player glance-read how close a Stun/Cloaking/buff is to expiring without
   parsing the tiny numeric label (which stays as the redundant channel). The
   --frac CSS var is set per tick by setStatuses; only timed statuses opt in. */

.status-icon.timed::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; z-index: 0;
  pointer-events: none;
  background: conic-gradient(
    var(--ring, rgba(255,255,255,0.16)) calc(var(--frac) * 360deg),
    transparent 0
  );
  /* Hollow it into a ring so the abbr/time stay legible over the icon body. */
  -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%);
  mask: radial-gradient(circle, transparent 58%, #000 60%);
}

.status-icon.timed.buff { --ring: rgba(111,208,106,0.55); }

.status-icon.timed.debuff { --ring: rgba(224,83,61,0.6); }

/* A budget the application spends down — a shield's unspent absorb — is its own
   reading, because it empties on a different clock from the duration: one hit
   can eat a shield with most of its ring still to sweep. A bar across the base
   rather than a second ring, so the two can never be confused. */

.status-icon.charged::after,
.tgt-status-chip.charged::after,
.pf-chip.charged::after {
  content: ''; position: absolute; left: 2px; right: 2px; bottom: 2px; z-index: 2;
  height: max(3px, calc(4px * var(--ui-scale)));
  border-radius: 2px;
  /* A track under the fill, so an almost-empty shield still reads as a meter
     rather than as a stray mark on the tile. */
  background: linear-gradient(
    90deg,
    #7fd3ff 0 calc(var(--charge, 1) * 100%),
    rgba(2, 6, 12, 0.72) calc(var(--charge, 1) * 100%) 100%
  );
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

/* Keep the inner content above the ring. */

.status-icon .status-abbr, .status-icon .status-time { position: relative; z-index: 1; }

.status-abbr { font-size: calc(11px * var(--ui-scale)); font-weight: 700; color: var(--accent); }

.status-time { font-size: calc(9px * var(--ui-scale)); color: var(--muted); }

/* Art-faced status tiles: the family's procedural icon fills the tile face; the
   countdown rides a dark scrim pinned to the base so it stays legible over the
   art. The depletion ring (::before) still sweeps on top. */

.status-icon.arted {
  background-size: cover; background-position: center; background-repeat: no-repeat;
  justify-content: flex-end; cursor: help;
}

.status-icon.arted .status-time {
  width: 100%; text-align: center; color: #fff; font-weight: 700;
  text-shadow: 0 1px 2px #000; line-height: 1.6;
  background: linear-gradient(transparent, rgba(0,0,0,0.8));
  border-radius: 0 0 5px 5px;
}

.status-icon.arted .status-time:empty { background: none; }

/* The status chip everywhere but the self tray: the focused target's strip and
   the party rows. One status reads the same on every frame — the same art, the
   same buff(green)/debuff(red)/passive(gold) frame, the same depleting ring, the
   same hover card. A surface chooses only its tile size and whether it has room
   to print the countdown beside the ring. */

.tgt-status-chip, .pf-chip {
  position: relative; box-sizing: border-box; border-radius: 3px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  background-size: cover; background-position: center; background-repeat: no-repeat;
  border: 1px solid color-mix(in srgb, #4f9d4f 68%, transparent);
  pointer-events: auto; cursor: help;
  --frac: 1;
}

.tgt-status-chip.debuff, .pf-chip.debuff {
  border-color: color-mix(in srgb, var(--danger) 68%, transparent);
}

.tgt-status-chip.passive, .pf-chip.passive { border-color: var(--accent); }

/* Trigger states mirror the tray: a mid-build chip is dimmed, a ready one glows
   gold, and a spent capstone washes dark like a skill on cooldown. */

.tgt-status-chip.charging, .pf-chip.charging { filter: saturate(0.65) brightness(0.62); }

.tgt-status-chip.ready, .pf-chip.ready {
  border-color: var(--accent); box-shadow: 0 0 6px 1px color-mix(in srgb, var(--gold) 65%, transparent);
}

.tgt-status-chip.cooldown, .pf-chip.cooldown { box-shadow: inset 0 0 0 100px rgba(9,13,21,0.62); }

/* The same conic sweep the tray tiles carry. On a tile too small for digits this
   is the only reading of time there is, so it is never the part a surface drops. */

.tgt-status-chip.timed::before, .pf-chip.timed::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; z-index: 0;
  pointer-events: none;
  background: conic-gradient(
    var(--ring, rgba(255,255,255,0.16)) calc(var(--frac) * 360deg),
    transparent 0
  );
  -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%);
  mask: radial-gradient(circle, transparent 58%, #000 60%);
}

.tgt-status-chip.timed.buff, .pf-chip.timed.buff { --ring: rgba(111,208,106,0.55); }

.tgt-status-chip.timed.debuff, .pf-chip.timed.debuff { --ring: rgba(224,83,61,0.6); }

/* A party row's tile prints no countdown, so the ring is the entire reading of
   time there is, and at 16px it has to survive being drawn over icon art. The
   band hugs the tile's edge at an even thickness rather than being cut out of a
   circle — a circle on a tile this small paints a corner block, not a ring —
   and a near-opaque sweep over a dark track keeps the arc separate from the art
   instead of tinting it. The track is a layer of the same background shorthand
   as the sweep, so an engine without conic-gradient drops both and takes the bar
   fallback whole. Surfaces with room for their countdown keep the thin circular
   ring: there the digits are the reading and the ring is only the glance. */

.pf-chip:not(.pf-chip-focus).timed::before {
  box-sizing: border-box;
  padding: max(2px, calc(2px * var(--ui-scale)));
  background:
    conic-gradient(
      var(--ring, rgba(255,255,255,0.16)) calc(var(--frac) * 360deg),
      transparent 0
    ),
    rgba(2,6,11,0.82);
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  mask-composite: exclude;
}

.pf-chip:not(.pf-chip-focus).timed.buff { --ring: rgba(126,224,111,0.95); }

.pf-chip:not(.pf-chip-focus).timed.debuff { --ring: rgba(255,106,80,0.95); }

/* Only the countdown takes the tile's base — the stack badge keeps the gold
   corner pill it wears in the tray, or it would cover the art it annotates. */

.tgt-status-chip .status-time, .pf-chip .status-time {
  position: relative; z-index: 1; width: 100%; text-align: center;
  color: #fff; font-weight: 700; line-height: 1.35;
  font-size: max(8px, calc(9px * var(--ui-scale)));
  font-variant-numeric: tabular-nums; text-shadow: 0 1px 2px #000;
  background: linear-gradient(transparent, rgba(0,0,0,0.82));
  border-radius: 0 0 2px 2px;
}

.tgt-status-chip .status-time:empty, .pf-chip .status-time:empty { background: none; }

/* The stack badge keeps the tray's gold corner pill, sized for a tile a third
   smaller — at the tray's measurements it stops being a badge and becomes a
   block over the art. */

.tgt-status-chip .status-stacks, .pf-chip .status-stacks {
  min-width: calc(9px * var(--ui-scale)); padding: 0 calc(1px * var(--ui-scale));
  font-size: max(7px, calc(8px * var(--ui-scale))); line-height: 1.3;
  border-radius: 0 2px 0 4px;
}

/* The tile standing in for what a row had no room to draw. It names those
   statuses rather than only counting them, and where the surrounding chips take
   focus it does too — a "+3" a player cannot reach is a "+3" they cannot read. */

.status-chip-overflow {
  background-image: none; background: rgba(10,14,20,0.86);
  border-color: var(--accent-2); justify-content: center; cursor: help;
  padding: 0; font: inherit;
}

/* Only the compact rows shrink the "+N" to their own type size — the tray tile is
   a full-size tile and keeps the tray's label. */

.tgt-status-chip .status-abbr,
.pf-chip .status-abbr,
.plate-status-chip .status-abbr {
  font-size: max(8px, calc(9px * var(--ui-scale)));
}

.tgt-status-chip:focus-visible, .pf-chip:focus-visible {
  outline: 1px solid var(--accent); outline-offset: 1px;
}

/* The focused target's strip: room for the countdown to be printed. */

.tgt-status-chip {
  width: calc(26px * var(--ui-scale)); height: calc(26px * var(--ui-scale));
}

/* A party row's strip: too small for digits, so the ring carries the time alone. */

.pf-chip {
  width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale));
}

/* The focused ally fills their own panel, so their tiles get the countdown back. */

.pf-buffs-focus { gap: 3px; }

.pf-chip-focus {
  width: calc(24px * var(--ui-scale)); height: calc(24px * var(--ui-scale));
}

/* Buffs read green, harmful debuffs/CC read red and pulse so a Stun/Freeze
   threat is unmistakable. Colour is backed by the "Debuff"/"Harmful effect" text
   in the chip's label and hover card, so meaning survives colourblindness. */

.status-icon.buff { border-color: #4f9d4f; }

.status-icon.buff .status-abbr { color: #6fd06a; }

.status-icon.debuff { border-color: var(--danger); animation: debuff-pulse 1.1s ease-in-out infinite; }

.status-icon.debuff .status-abbr { color: #ff8a76; }

/* Always-on passive bonuses (Hardy's +Max HP, Bulwark's damage reduction…): a
   separate timerless lane below the timed buffs. Gold frame = owned/permanent
   (game identity), and the lane wraps onto its own row(s) with a faint divider so
   passives never crowd out the timed combat statuses. */

.status-passive-lane {
  flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 4px;
  margin-top: calc(4px * var(--ui-scale)); padding-top: calc(4px * var(--ui-scale));
  border-top: 1px solid var(--grid-line, var(--ui-line-soft));
}

.status-icon.passive { border-color: var(--accent); }

.status-icon.passive.arted { justify-content: center; }

/* Stack/progress badge for windowed triggers (e.g. Overload "2/3"): a gold pill
   pinned to the top-right so the running count reads at a glance. */

.status-stacks {
  position: absolute; top: 0; right: 0; z-index: 4;
  min-width: calc(14px * var(--ui-scale)); padding: 0 calc(3px * var(--ui-scale));
  font-size: calc(10px * var(--ui-scale)); font-weight: 700; line-height: 1.4;
  text-align: center; color: #0b0f16; background: var(--accent);
  border-radius: 0 5px 0 6px; text-shadow: none;
}

/* Windowed-trigger chip states. While CHARGING the ::before ring is a fill meter
   (--frac = stacks/max, set by the tray), tinted gold and the icon dimmed so a
   mid-build chip never reads as an active buff. At the firing count it flips to a
   bright READY glow with a pulsing count. */

.status-icon.charging { --ring: var(--accent); filter: saturate(0.65) brightness(0.62); }

.status-icon.ready {
  --ring: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 10px 2px color-mix(in srgb, var(--gold) 65%, transparent);
}

.status-icon.ready .status-stacks { animation: stack-ready-pulse 1s ease-in-out infinite; }

@keyframes stack-ready-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.14); } }

/* Spent-capstone cooldown chip: an inset wash greys the icon art so it never reads
   as an active buff, while the ::before depletion ring (--frac = seconds left /
   total), and the centered countdown stay legible
   above it — the same "recharging" language as a skill on cooldown in the hotbar.
   The wash is an inset box-shadow so it does not compete with the ring or dim
   the countdown. */

.status-icon.cooldown {
  --ring: rgba(210,224,255,0.6);
  box-shadow: inset 0 0 0 100px rgba(9,13,21,0.62);
}

.status-icon.cooldown .status-time {
  position: absolute; inset: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(11px * var(--ui-scale)); font-weight: 700; line-height: 1;
  color: #eef3ff; background: none; text-shadow: 0 1px 2px rgba(0,0,0,0.9);
}

/* Generated HUD chrome on square tiles. Map the full frame into the tile
   (inset: 0) — negative insets were clipped by overflow:hidden hosts and left
   only a stretched centre patch of the art, which read as soft/warped buttons. */

.inv-cell::before,
.hot-slot::before,
.unit-portrait::after {
  content: '';
  position: absolute;
  pointer-events: none;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

.inv-cell::before {
  inset: 0;
  z-index: 1;
  opacity: 0;
  background-image: none;
  border-radius: inherit;
}

.inv-cell .inv-icon {
  position: relative;
  z-index: 0;
}

.inv-amt,
.inv-refine {
  z-index: 3;
}

.hot-slot::before {
  inset: 0;
  z-index: 1;
  opacity: 0;
  background-image: none;
  border-radius: inherit;
}

.hot-icon,
.hot-key,
.hot-name,
.hot-cd {
  z-index: 3;
}

.unit-portrait {
  overflow: visible;
}

.unit-portrait::after {
  inset: calc(-3px * var(--ui-scale));
  z-index: 6;
  border: 1px solid color-mix(in srgb, var(--ui-line) 76%, transparent);
  border-radius: 50%;
  background: none;
}

/* HP bars keep the wet-glass ::after gloss from the unit-frame rules above.
   A rectangular healthbar-frame.png must not be stretched with background-size
   100% 100% — that warps the end caps and softens the centred HP text. */

.status-icon .status-abbr,
.status-icon .status-time {
  z-index: 3;
}

.status-icon.buff {
  border-radius: 8px;
}

.status-icon.debuff {
  border-radius: 3px;
}

/* The radar is a restrained navigation lens: real terrain remains dominant,
   while a soft optical edge keeps the circular crop legible without becoming
   another framed panel. */

.minimap-frame {
  width: calc(152px * var(--ui-scale));
  height: calc(152px * var(--ui-scale));
  z-index: 30;
  pointer-events: none;
  overflow: hidden;
  isolation: isolate;
  background: radial-gradient(circle at 40% 36%, #526b4d, #20372f 58%, #13221f);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--ui-line));
  border-radius: 50%;
  box-shadow:
    0 8px 24px rgba(0, 0, 0, 0.28),
    0 0 0 1px rgba(255, 255, 255, 0.035);
}

.minimap-content::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 47%, transparent 57%, rgba(3, 9, 10, 0.18) 74%, rgba(3, 8, 9, 0.62) 100%),
    linear-gradient(145deg, rgba(255, 255, 255, 0.09), transparent 28%);
  box-shadow:
    inset 0 0 0 1px rgba(226, 235, 226, 0.11),
    inset 0 0 18px rgba(2, 8, 9, 0.28);
}

/* Radar as the mouse route into the world map (M stays the keyboard route). */

.minimap-content.minimap-clickable { pointer-events: auto; cursor: var(--cursor-action); }

.minimap-content.minimap-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.minimap-canvas {
  position: absolute;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 36%, rgba(82, 107, 77, 0.82), rgba(26, 47, 41, 0.92));
  box-shadow: none;
  z-index: 1;
}

/* Overflow chip: collapses the auras beyond the tray cap into one '+N' tile
   (full name list in its title) so the tray never grows past its max-width and
   pushes a survival-critical Stun/Freeze out of view. */

.status-icon.status-overflow { border-style: dashed; border-color: var(--accent-2); cursor: help; }

.status-icon.status-overflow .status-abbr { color: var(--muted); }

@keyframes debuff-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(224,83,61,0); }
  50% { box-shadow: 0 0 7px 1px rgba(224,83,61,0.7); }
}

/* Bottom-fill fallback for engines lacking conic-gradient or CSS mask: a thin
   bar across the icon's base whose width tracks --frac, conveying the same
   remaining-fraction read as the ring above. */

@supports not (background: conic-gradient(#000 0deg, #000 0)) {
  .status-icon.timed::before {
    inset: auto 0 0 0; height: 3px; border-radius: 0 0 6px 6px;
    width: calc(var(--frac) * 100%);
    background: var(--ring, rgba(255,255,255,0.3)); -webkit-mask: none; mask: none;
  }
  /* The chip rows take the same bar, sized for a tile a third the tray's. The
     party row repeats itself at its own specificity so its edge band cannot
     leave it a padded box with no sweep. */
  .tgt-status-chip.timed::before,
  .pf-chip.timed::before,
  .pf-chip:not(.pf-chip-focus).timed::before {
    inset: auto 0 0 0; height: 2px; padding: 0; border-radius: 0 0 2px 2px;
    width: calc(var(--frac) * 100%);
    background: var(--ring, rgba(255,255,255,0.3)); -webkit-mask: none; mask: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .status-icon.debuff { animation: none; box-shadow: 0 0 6px 1px rgba(224,83,61,0.6); }
  /* The depletion ring is updated by discrete per-tick var writes, but pin out
     any transition so it can never tween under reduced-motion. */
  .status-icon.timed::before { transition: none; }
}

/* Item tooltip */

.item-tooltip {
  position: fixed; z-index: 210; min-width: 150px; max-width: 250px; pointer-events: none;
  background: linear-gradient(180deg, rgba(16,21,30,0.98), rgba(8,11,16,0.98));
  border: 1px solid color-mix(in srgb, var(--gold) 32%, transparent); border-radius: 7px; padding: 8px 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.05);
}

.item-tooltip--comparison {
  grid-auto-flow: column;
  grid-auto-columns: minmax(calc(180px * var(--ui-scale)), calc(250px * var(--ui-scale)));
  gap: calc(8px * var(--ui-scale));
  width: max-content;
  min-width: 0;
  max-width: calc(100vw - 16px);
  max-height: calc(100vh - 16px);
  padding: 0;
  overflow: auto;
  pointer-events: auto;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.item-tooltip--comparison.item-tooltip--left { direction: rtl; }

.item-tooltip--comparison .tt-panel {
  direction: ltr;
  min-width: 0;
  padding: calc(8px * var(--ui-scale)) calc(10px * var(--ui-scale));
  overflow-wrap: anywhere;
  background: linear-gradient(180deg, rgba(16,21,30,0.98), rgba(8,11,16,0.98));
  border: 1px solid color-mix(in srgb, var(--gold) 32%, transparent);
  border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.05);
}

.item-tooltip--comparison .tt-id { display: none; }

.tt-equipped-label {
  color: var(--accent);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: .45px;
  text-transform: uppercase;
  margin-bottom: calc(4px * var(--ui-scale));
}

.tt-compare-hint {
  margin-top: calc(7px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  text-align: right;
}

.tt-compare-section {
  margin-top: calc(7px * var(--ui-scale));
  padding-top: calc(5px * var(--ui-scale));
  border-top: 1px solid var(--grid-line, var(--ui-line-soft));
}

.tt-line.tt-neutral { color: var(--muted); }

/* Swap verdict: a stat name reads down the left edge and its net amount down
   the right, so the whole change scans as one column of gains and losses
   instead of a sentence per row. */

.tt-compare-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: calc(2px * var(--ui-scale)) calc(10px * var(--ui-scale));
  align-items: baseline;
  margin-bottom: calc(2px * var(--ui-scale));
}

.tt-compare-row { display: contents; }

.tt-compare-label, .tt-compare-value, .tt-compare-effect {
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.35;
}

.tt-compare-label { color: #b8c0cc; }

.tt-compare-value {
  font-weight: 700; font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap;
}

.tt-compare-row.tt-gain .tt-compare-value { color: var(--good); }

.tt-compare-row.tt-loss .tt-compare-value { color: var(--danger); }

.tt-compare-row.tt-neutral .tt-compare-label,
.tt-compare-row.tt-neutral .tt-compare-value { color: var(--muted); font-weight: 400; }

.tt-compare-row--effect .tt-compare-effect { grid-column: 1 / -1; }

.tt-compare-row--effect.tt-gain .tt-compare-effect { color: var(--good); }

.tt-compare-row--effect.tt-loss .tt-compare-effect { color: var(--danger); }

/* The Gained/Lost tag is the row's only lane marker, so it stays legible at the
   smallest UI scale rather than shrinking with the sentence it introduces. */

.tt-compare-tag {
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
  opacity: .75;
}

.tt-name { font-weight: 700; font-size: calc(13px * var(--ui-scale)); margin-bottom: 4px; }

.tt-heading {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  margin-bottom: calc(6px * var(--ui-scale));
}

.tt-heading .tt-name { margin-bottom: 0; }

.tt-icon {
  flex: 0 0 auto;
  width: calc(32px * var(--ui-scale));
  height: calc(32px * var(--ui-scale));
  object-fit: contain;
}

.tt-line { font-size: calc(11px * var(--ui-scale)); color: #b8c0cc; margin: 1px 0; }

.tt-description { margin: calc(5px * var(--ui-scale)) 0; color: #cdd3dd; line-height: 1.35; }

/* Quest-item chip — the game's gold/ivory accent, deliberately not a rarity colour. */

.tt-quest {
  display: inline-block; margin: calc(3px * var(--ui-scale)) 0 calc(2px * var(--ui-scale));
  padding: 1px 6px; border-radius: 3px;
  font-size: calc(10px * var(--ui-scale)); font-weight: 700;
  text-transform: uppercase; letter-spacing: .5px;
  color: #ead8a5; border: 1px solid var(--accent-2);
  background: rgba(201, 164, 94, 0.14);
}

.tt-on { color: var(--accent); }

.tt-id { font-size: 10px; color: var(--muted); margin-top: 4px; }

/* Structured stat-tooltip body: a scannable rate → effect list (Status sheet). */

.tt-rowlabel { font-size: calc(10px * var(--ui-scale)); text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted); margin: 6px 0 3px; }

.tt-stats { display: grid; grid-template-columns: auto 1fr; gap: 3px calc(10px * var(--ui-scale)); }

.tt-amt { font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }

.tt-eff { color: #cdd3dd; font-size: calc(11px * var(--ui-scale)); }

@media (max-width: 760px) {
  .item-tooltip--comparison {
    grid-auto-columns: minmax(calc(190px * var(--ui-scale)), calc(220px * var(--ui-scale)));
  }
}

/* FPS readout (bottom-right): the render frame rate, movable and hideable */

.fps-hud {
  position: fixed;
  right: calc(var(--hud-safe-right) + 56px * var(--ui-scale));
  bottom: var(--hud-safe-bottom);
  z-index: 18;
  pointer-events: none;
  font-size: 11px; color: #8a93a3; text-shadow: 0 1px 2px #000;
  background: rgba(10,14,20,0.5); padding: 2px 8px; border-radius: 5px;
}

.fps-hud.is-hidden { display: none; }

/* A hidden chip still has a place on screen, so the layout editor shows it as a
   ghost — otherwise turning the readout off would strand its saved position. */

.hud-layout-editing .fps-hud.is-hidden { display: block; opacity: 0.45; }

/* Clickable chat speaker name (whisper) + scrollable chat log */

.chat-name { color: #9fd3ff; cursor: var(--cursor-action); pointer-events: auto; font-weight: 600; }

.chat-name:hover { text-decoration: underline; }

.chat-log { pointer-events: auto; overflow-y: auto; }

/* Map-name banner on entry */

.map-banner {
  position: fixed; top: 28%; left: 50%; transform: translate(-50%,-50%) scale(0.9);
  /* Above the warp cover (44): the destination name is the transition's title card and
     must read while the screen is still covered for the map build. */
  z-index: 45; pointer-events: none; font-size: 34px; font-weight: 800; letter-spacing: 1px;
  color: #fff; text-shadow: 0 2px 10px #000, 0 0 24px color-mix(in srgb, var(--gold) 50%, transparent);
  opacity: 0; transition: opacity 0.6s ease, transform 0.6s ease;
}

.map-banner.show { opacity: 1; transform: translate(-50%,-50%) scale(1); }

/* Draggable windows need pointer events + fixed positioning */

.window { pointer-events: auto; }

.win-head { user-select: none; }

/* Hotbar skill cooldown overlay */

.hot-slot { position: relative; overflow: hidden; }

.hot-cd {
  position: absolute; left: 0; bottom: 0; width: 100%;
  background: rgba(10,14,20,0.72); display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: #fff; text-shadow: 0 1px 2px #000; pointer-events: none;
}

/* Map transition fade. Hidden covers must leave layout: iOS keeps a leftover
   compositor veil from a full-viewport `opacity: 0` layer over the canvas. */

.map-transition {
  display: none;
  position: fixed; inset: 0; z-index: 44; pointer-events: none; background: #05070b;
  opacity: 0; transition: opacity 0.28s ease;
}

.map-transition:not(.show) { display: none; }

.map-transition.show { display: block; opacity: 1; pointer-events: auto; }

/* A warp raises the cover instantly: the render thread stops compositing during the
   map rebuild, so a fading-in cover would show the frozen previous map through it.
   Only the reveal (cover removal) fades. */

.map-transition.instant { transition: none; }

.virtual-stick {
  position: fixed;
  left: max(18px, var(--safe-left));
  bottom: max(24px, var(--safe-bottom));
  z-index: 19;
  display: none;
  width: 112px;
  height: 112px;
  pointer-events: auto;
  touch-action: none;
  user-select: none;
}

.virtual-stick-base {
  position: absolute;
  inset: 0;
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  border-radius: 999px;
  background: radial-gradient(circle at center, rgba(230,204,128,0.15), rgba(10,14,20,0.58) 62%, rgba(10,14,20,0.74));
  box-shadow: 0 12px 28px rgba(0,0,0,0.42), inset 0 0 18px rgba(230,204,128,0.08);
}

.virtual-stick-knob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 42px;
  height: 42px;
  margin: -21px 0 0 -21px;
  border: 1px solid rgba(230,204,128,0.78);
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(230,204,128,0.92), rgba(158,111,45,0.86));
  box-shadow: 0 5px 16px rgba(0,0,0,0.42), inset 0 1px 0 rgba(255,255,255,0.28);
}

.virtual-stick.active .virtual-stick-base {
  border-color: rgba(230,204,128,0.86);
  background: radial-gradient(circle at center, rgba(230,204,128,0.23), rgba(10,14,20,0.6) 62%, rgba(10,14,20,0.78));
}

@media (pointer: coarse) {
  .virtual-stick { display: block; }
}

/* Safe-zone compass (BR) */

.safe-compass { position: fixed; top: 96px; left: 50%; transform: translateX(-50%); z-index: 17;
  pointer-events: none; text-align: center; }

.safe-arrow { font-size: 30px; color: #7fd0ff; text-shadow: 0 0 8px rgba(127,208,255,0.8), 0 2px 3px #000; line-height: 1; }

.safe-dist { font-size: 11px; color: #cfe6ff; text-shadow: 0 1px 2px #000; margin-top: 2px; }

/* Off-screen threat / aggro indicator (BR): a chevron flashing on the screen
   edge toward an attacker that hit us from off-screen. */

.screen-threat-indicators { position: fixed; inset: 0; z-index: 18; pointer-events: none; }

.threat-chevron { position: absolute; pointer-events: none;
  font-size: 34px; color: #ff5a4a; line-height: 1;
  text-shadow: 0 0 10px rgba(255,90,74,0.9), 0 2px 4px #000; opacity: 0; }

.threat-chevron.show { animation: threat-flash 1s ease-out; }

@keyframes threat-flash {
  0% { opacity: 0; }
  12% { opacity: 1; }
  60% { opacity: 0.9; }
  100% { opacity: 0; }
}

body.reduce-flashing .threat-chevron.show { animation: none; opacity: 0.85; }

/* Inventory item icons */

.inv-icon { flex-shrink: 0; image-rendering: auto; border-radius: 5px; }

.inv-row { align-items: center; gap: 8px; }

/* Equipment paperdoll — a functional equipment surface: worn gear down the left column,
   held gear + accessories down the right, a character figure in the middle. Click
   a filled slot to unequip; drag a bag item onto a matching slot to equip; the
   hover/focus tooltip carries refine/options/cards. */

.paperdoll {
  display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center;
  margin-bottom: 2px; padding: 10px;
  background: radial-gradient(120% 90% at 50% 0%, rgba(36,46,62,0.5), rgba(8,12,18,0.5));
  border: 1px solid var(--ui-control-line); border-radius: 8px;
}

.pd-col { display: flex; flex-direction: column; gap: 6px; }

.pd-slot {
  position: relative; width: 44px; height: 44px; display: flex;
  align-items: center; justify-content: center; text-align: center;
  background: linear-gradient(160deg, rgba(22,28,38,0.9), rgba(12,16,22,0.9));
  border: 1px solid var(--ui-control-line); border-radius: 6px; cursor: var(--cursor-action);
  transition: border-color 0.12s, box-shadow 0.12s;
}

.pd-slot.pd-filled { border-color: var(--accent); background: linear-gradient(160deg, #1e2a3a, #16202c); }

.pd-slot:not(.pd-filled) { cursor: var(--cursor-default); }

.pd-slot:not(.pd-filled):hover { border-color: #3a4555; }

.pd-slot.pd-filled:hover { box-shadow: 0 0 0 1px var(--accent) inset, 0 0 10px rgba(80,140,200,0.25); }

.pd-label { font-size: 9px; line-height: 1.15; color: var(--muted); padding: 0 2px; overflow-wrap: anywhere; }

/* Character figure placeholder between the two slot columns. */

.pd-figure { display: flex; align-items: center; justify-content: center; min-width: 64px; align-self: stretch; }

.pd-figure svg { width: 80%; max-width: 84px; height: auto; fill: #46566a; filter: drop-shadow(0 3px 6px rgba(0,0,0,0.4)); }

/* Refine (+N) corner badge on a filled slot, mirroring the bag cell. */

.pd-refine { position: absolute; left: 2px; top: 1px; z-index: 1;
  font-size: 10px; font-weight: 700; color: var(--gold-value); line-height: 1; text-shadow: 0 1px 1px #000; }

/* Drag-and-drop equip */

.inv-draggable { cursor: grab; }

.inv-draggable:active { cursor: grabbing; }

.pd-slot.pd-drop { border-color: #6fd06a; box-shadow: 0 0 0 2px rgba(111,208,106,0.5) inset; }

/* Compact equipped-roll readout: only rolled/carded gear appears, so the loot
   identity is visible without turning the paperdoll into a second inventory. */

.equip-roll-summary { display: grid; gap: 3px; margin: 4px 0 2px; }

.equip-roll-row {
display: grid; grid-template-columns: minmax(72px, 0.7fr) minmax(0, 1fr); gap: 6px;
align-items: baseline; padding: 3px 6px; border-left: 2px solid var(--accent);
background: rgba(255,255,255,0.035); border-radius: 4px;
}

.equip-roll-name,
.equip-roll-options { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.equip-roll-name { font-size: calc(10px * var(--ui-scale)); color: #eef1f5; }

.equip-roll-options { font-size: calc(10px * var(--ui-scale)); color: var(--muted); }

/* Accessibility: visible keyboard-focus ring (mouse clicks don't trigger it) */

:focus-visible { outline: 2px solid #7fd0ff; outline-offset: 2px; }

button:focus-visible, .menu-btn:focus-visible, .hot-slot:focus-visible, .inv-act:focus-visible {
  outline: 2px solid #7fd0ff; outline-offset: 1px;
}

/* Respect reduced-motion for non-essential transitions */

@media (prefers-reduced-motion: reduce) {
  .map-banner, .map-transition, .target-dist, .menu-btn, .menu-items, .menu-collapse-icon, .chat-wrap, .chat-content, .chat-collapse-btn, .chat-collapse-icon { transition: none !important; }
  .menu-items .menu-btn.has-attention,
  .menu-buttons.is-collapsed .menu-collapse-btn.has-attention,
  .chat-collapse-btn.attention-arrive,
  .chat-collapse-btn.attention-arrive::after { animation: none !important; }
}

/* Server broadcast / announcement banner (top strip) */

.announce-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 38; pointer-events: none;
  padding: 7px 16px; text-align: center; font-weight: 600; font-size: 13px; color: #ffe9a8;
  background: linear-gradient(90deg, rgba(60,40,10,0) 0%, rgba(80,55,12,0.92) 20%, rgba(80,55,12,0.92) 80%, rgba(60,40,10,0) 100%);
  text-shadow: 0 1px 2px #000; transform: translateY(-110%); transition: transform 0.35s ease;
}

.announce-bar.show { transform: translateY(0); }

/* Touch + responsive layout (I10) */

#game-canvas { touch-action: none; }

/* prevent browser gestures eating taps */

@media (max-width: 820px), (pointer: coarse) {
  /* Handheld landscape is short, not narrow — profile CSS owns that chrome. */
  html:not([data-ui-profile="handheld"]) .hot-slot { width: 52px; height: 52px; }
  html:not([data-ui-profile="handheld"]) .menu-btn { width: 48px; height: 48px; }
  html:not([data-ui-profile="handheld"]) .menu-glyph,
  html:not([data-ui-profile="handheld"]) .menu-icon { width: 38px; height: 38px; }
  html:not([data-ui-profile="handheld"]) .inv-act { padding: 8px 12px; }
  html:not([data-ui-profile="handheld"]) .window { max-width: 94vw; }
  html:not([data-ui-profile="handheld"]) .chat-wrap { width: 64vw; }
  html:not([data-ui-profile="handheld"]) .status-tray { max-width: 60vw; }
  html:not([data-ui-profile="handheld"]) .target-panel.unit-frame {
    min-width: min(70vw, calc(200px * var(--ui-scale)));
    max-width: 78vw;
  }
}

@media (max-width: 560px) {
  html:not([data-ui-profile="handheld"]) .fps-hud { font-size: 9px; }
  html:not([data-ui-profile="handheld"]) .target-class { display: none; }
  html:not([data-ui-profile="handheld"]) .window.win-quests {
    top: 72px; left: 3vw; right: auto; width: 94vw; max-width: 94vw;
  }
}

/* Chat panel: one translucent box stacking tabs + scrollable log + input
   bottom-left, so a player can read messages and see where to type. */

.chat-wrap {
  position: fixed; bottom: var(--hud-safe-bottom); left: var(--hud-safe-left); z-index: 19;
  width: var(--chat-w, 360px); max-width: 92vw;
  /* The launcher and the entry box share the panel's bottom row. The launcher is
     absolute (it has to outlive the collapse, which folds the rest away), so the
     row states its own height instead of taking it from the entry box — nothing
     above the row can end up underneath the launcher at any UI scale. */
  --chat-row: calc(34px * var(--ui-scale));
  display: flex; flex-direction: column; box-sizing: border-box;
  background: linear-gradient(90deg, rgba(7, 12, 14, 0.46), rgba(7, 12, 14, 0.18) 78%, transparent);
  border: 0;
  border-left: 1px solid var(--ui-line-soft);
  border-radius: 2px;
  padding: 8px 11px; pointer-events: auto;
  box-shadow: none;
  transition: width 180ms cubic-bezier(.2,.78,.24,1), padding 180ms ease, box-shadow 180ms ease;
}

.chat-content {
  /* The collapse animates this bound, so it tracks the panel's own parts: the log,
     the tab strip and gaps, the catch-up pill's row, and the entry row. */
  max-height: calc(var(--chat-log-h, 150px) + 56px + var(--chat-row));
  opacity: 1;
  transform: translateY(0);
  overflow: hidden;
  transition:
    max-height 180ms cubic-bezier(.2,.78,.24,1),
    opacity 120ms ease,
    transform 180ms cubic-bezier(.2,.78,.24,1);
}

.chat-wrap.is-collapsed {
  width: calc(68px * var(--ui-scale));
  min-width: calc(68px * var(--ui-scale));
  height: calc(68px * var(--ui-scale));
  min-height: calc(68px * var(--ui-scale));
  padding: 3px;
  box-shadow: 0 5px 16px rgba(0,0,0,0.48);
}

.chat-wrap.is-collapsed .chat-content {
  max-height: 0;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
}

.chat-tabs { display: flex; gap: 8px; margin-bottom: 4px; padding-right: 15px; pointer-events: auto; }

.chat-tab {
  font-size: 11px; font-weight: 650; padding: 3px 2px 4px; cursor: var(--cursor-action); border-radius: 0;
  background: transparent; border: 0; border-bottom: 1px solid transparent; color: var(--muted);
  transition: color .12s, background .12s, border-color .12s, box-shadow .12s;
}

.chat-tab:hover { color: #e6e6e6; border-color: #3a4453; }

.chat-tab.active {
  background: linear-gradient(180deg, transparent 58%, color-mix(in srgb, var(--accent) 9%, transparent));
  color: var(--accent); border-color: var(--accent);
  box-shadow: 0 2px 5px color-mix(in srgb, var(--gold) 12%, transparent);
}

.chat-tab { position: relative; display: inline-flex; align-items: center; gap: 5px; }

/* Per-tab unread badge: count of unseen lines in that tab's channels. */

.chat-badge {
  min-width: 16px; padding: 0 4px; border-radius: 9px; font-size: 10px; line-height: 15px;
  text-align: center; color: #1a1206; font-weight: 700;
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
}

.chat-log {
  /* Fixed height (player-resizable via --chat-log-h) so the panel never resizes
     itself as messages arrive or a tab filters the view. Messages pin to the
     bottom; empty space sits above when there are few. */
  height: var(--chat-log-h, 150px); overflow-y: auto;
  display: flex; flex-direction: column;
  /* Older lines fade as they scroll off the top edge. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 16px);
  mask-image: linear-gradient(180deg, transparent 0, #000 16px);
}

/* Bottom-pin messages when there are few, WITHOUT clipping scroll when there are
   many: an auto top-margin on the first row absorbs slack space but collapses to
   0 once the log overflows (justify-content:flex-end would make the overflowed
   top rows unreachable). */

.chat-log > :first-child { margin-top: auto; }

/* Resize grip: the free corner of the tab strip, on the side the panel grows
   towards — the bottom row is the launcher's and the entry box's. It mirrors to the
   left when the HUD editor hangs the panel off the right edge, so the drag always
   pulls towards the open screen. Subtle until hovered; drag handled in the chat view. */

.chat-resize {
  position: absolute; top: 4px; right: 4px; z-index: 4;
  width: 15px; height: 15px; cursor: nesw-resize; pointer-events: auto;
  opacity: 0; transition: opacity .12s;
  background:
    linear-gradient(45deg, transparent 42%, var(--accent) 42%, var(--accent) 52%, transparent 52%),
    linear-gradient(45deg, transparent 64%, var(--accent) 64%, var(--accent) 74%, transparent 74%);
}

.chat-wrap[data-hud-anchor-x="right"] .chat-resize {
  right: auto; left: 4px; cursor: nwse-resize; transform: scaleX(-1);
}

/* Keep the tab strip clear of the grip on whichever side it sits. */

.chat-wrap[data-hud-anchor-x="right"] .chat-tabs { padding-right: 0; padding-left: 15px; }

/* A top-anchored panel keeps its launcher beside the tabs. Reserve that corner
   while the opposite corner remains available to the resize grip. */

.chat-wrap[data-hud-anchor-y="top"] .chat-tabs {
  min-height: var(--chat-row);
  align-items: center;
  padding-left: max(var(--hud-fold-reserve-left), 15px);
  padding-right: max(var(--hud-fold-reserve-right), 15px);
}

.chat-wrap.is-collapsed .chat-resize { display: none; }

.chat-wrap:hover .chat-resize { opacity: 0.65; }

.chat-resize:hover { opacity: 1; filter: drop-shadow(0 0 4px color-mix(in srgb, var(--gold) 60%, transparent)); }

/* The chat launcher mirrors the menu dock: one promoted bitmap remains visible,
   and authority-owned unread state moves onto it while the panel is collapsed.
   It stays on the saved HUD corner in both states, so opening never moves the
   pointer target and the panel unfolds away from the player's cursor. */

/* 'New messages ↓' catch-up pill: shown only when a line arrives while the player
   has scrolled up; click re-pins to the bottom. */

.chat-new-msgs {
  align-self: center; margin-top: 3px; padding: 2px 12px; cursor: var(--cursor-action);
  font-size: 11px; font-weight: 600; color: #1a1206; pointer-events: auto;
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  border: none; border-radius: 10px; box-shadow: 0 2px 6px rgba(0,0,0,0.4);
}

.chat-new-msgs:hover { background: linear-gradient(180deg, #ffe08f, #d99230); }

/* Chat entry box: the rest of the bottom row, beside the launcher. */

.chat-entry {
  display: flex;
  align-items: center;
  gap: 4px;
  width: calc(100% - var(--chat-row) - 8px);
  box-sizing: border-box;
  margin-top: 5px;
  margin-left: calc(var(--chat-row) + 8px);
}

.chat-wrap[data-hud-anchor-x="right"] .chat-entry {
  margin-right: calc(var(--chat-row) + 8px);
  margin-left: 0;
}

.chat-wrap[data-hud-anchor-y="top"] .chat-entry {
  width: 100%;
  margin-right: 0;
  margin-left: 0;
}

.chat-input {
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  height: var(--chat-row);
  padding: 7px 9px;
  background: rgba(10,14,20,0.64); border: 1px solid rgba(92, 104, 123, 0.58); border-radius: 3px;
  color: #e6e6e6; font-size: calc(12px * var(--ui-scale));
}

.chat-send {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  height: var(--chat-row);
  padding: 0 10px;
  border: none;
  border-radius: 3px;
  background: var(--accent);
  color: #1a1206;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  cursor: var(--cursor-action);
}

.chat-input:focus { outline: none; border-color: var(--accent); }

/* Line kinds beyond the per-channel colors above (set in chat-view.row). */

.chat-line.channel { color: var(--accent-2); }

.chat-line.server { color: #8fd6ff; }

.chat-line.announce { color: var(--accent); font-weight: 600; }

.chat-line.error { color: #ff7a7a; }

.chat-line.emote { color: #c9a0ff; font-style: italic; }

.chat-source {
  display: inline-block;
  margin-right: 4px;
  padding: 0 4px;
  border: 1px solid rgba(143, 214, 255, 0.45);
  border-radius: 4px;
  color: #c9ecff;
  background: rgba(65, 157, 220, 0.18);
  font-size: 0.85em;
  font-weight: 700;
  line-height: 1.25;
  text-transform: uppercase;
}

/* Optional timestamp gutter, toggled by the 🕘 button. */

.chat-time { color: var(--muted); font-size: 0.85em; opacity: 0.8; }

/* Quantity / confirm modal (drop, withdraw, skill level) */

.qd-overlay {
  position: fixed; inset: 0; z-index: 220; display: flex;
  align-items: center; justify-content: center; background: rgba(8, 10, 16, 0.55);
}

.qd-panel { width: 320px; }

.qd-field { margin: 14px 0 18px; }

.qd-field input[type='number'] {
  width: 100%; padding: 9px 12px; background: var(--panel-2);
  border: 1px solid var(--ui-control-line); border-radius: 8px; color: var(--text); font-size: 14px;
}

.qd-field input[type='number']:focus { outline: none; border-color: var(--accent); }

.qd-slider { width: 100%; margin-top: 10px; accent-color: var(--accent); }

.qd-actions { display: flex; gap: 8px; }

.qd-actions .ron-btn { flex: 1; }

/* Low-HP urgency: pulsing HP bar + red screen-edge vignette. The pulse brightens
   the fill rather than repainting it, so a bar that is already saying "nearly
   dead" in its band does not have that reading overwritten by the alarm. */

.hud-gauge-fill.hp.critical { animation: vital-pulse 0.7s ease-in-out infinite; }

/* Out-of-SP urgency: the same pulse on the SP bar — the caster's no-verbs cue. */

.hud-gauge-fill.sp.critical { animation: vital-pulse 0.7s ease-in-out infinite; }

@keyframes vital-pulse {
  0%, 100% { filter: none; }
  50% { filter: brightness(1.5) saturate(1.1); }
}

/* Critical HP adds a red glow without changing the panel geometry. */

.status-panel.critical { box-shadow: 0 0 9px rgba(224, 83, 61, 0.7), 0 10px 24px rgba(0, 0, 0, 0.45); }

/* Hidden covers must leave layout entirely: iOS keeps a leftover compositor
   veil from a full-viewport `opacity: 0` fixed layer over the canvas. */

.crit-vignette {
  display: none;
  position: fixed; inset: 0; z-index: 9; pointer-events: none;
  box-shadow: inset 0 0 110px 30px rgba(224, 53, 45, 0.55);
}

.crit-vignette.show { display: block; animation: vignette-pulse 1.4s ease-in-out infinite; }

@keyframes vignette-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .hud-gauge-fill.hp.critical, .hud-gauge-fill.sp.critical, .crit-vignette.show { animation: none; }
}

/* Menu opt-in ("Reduce screen flashing") quiets the strongest pulsing flashes,
   mirroring the OS prefers-reduced-motion block above (which only the OS sets).
   Without this the low-HP bar, crit vignette and debuff icons kept pulsing for
   users who opted in via the menu rather than the OS. */

body.reduce-flashing .hud-gauge-fill.hp.critical,
body.reduce-flashing .hud-gauge-fill.sp.critical,
body.reduce-flashing .crit-vignette.show,
body.reduce-flashing .status-icon.ready .status-stacks,
body.reduce-flashing .status-icon.debuff,
body.reduce-flashing .menu-items .menu-btn.has-attention { animation: none; }

/* In-app "Reduce motion" body class (Group A's ESC-menu checkbox). The renderer's
   JS damping only covers ambient/storm motion; these rules kill the DOM keyframe
   animations + transitions the OS @media(prefers-reduced-motion) blocks above
   disable, so an opted-in user (not just an OS-set one) gets the same stillness. */

body.reduce-motion .map-banner,
body.reduce-motion .map-transition,
body.reduce-motion .target-dist,
body.reduce-motion .announce-bar,
body.reduce-motion .menu-items,
.reduce-motion .chat-wrap,
.reduce-motion .chat-content { transition: none !important; }

/* OOR cue snaps (no fade) when flashing is reduced — value still updates per cell. */

body.reduce-flashing .target-dist { transition: none !important; }

body.reduce-motion .hud-gauge-fill.hp.critical,
body.reduce-motion .hud-gauge-fill.sp.critical,
body.reduce-motion .crit-vignette.show,
body.reduce-motion .status-icon.debuff,
body.reduce-motion .coach-banner.coach-banner-enter,
body.reduce-motion .threat-chevron.show,
body.reduce-motion .menu-items .menu-btn.has-attention { animation: none; }

body.reduce-motion .status-icon.timed::before { transition: none; }

/* Bottom-centre prompt column: the pickup line and the kit-swap offer. It docks
   above the alert feed and the action bars, so a prompt never lands on them. The
   fixed anchor below is the un-railed fallback; a railed column takes its place
   from the rail. */

.screen-interaction-prompts {
  position: fixed;
  bottom: calc(var(--hud-safe-bottom) + var(--hotbar-h));
  left: 50%;
  z-index: 20;
  display: flex;
  /* The column hangs off its bottom edge and grows upward, so a strip appearing
     must arrive at the top and never shift the one already there — the kit-swap
     offer is a button, and a button that moves under the thumb is a lost click. */
  flex-direction: column;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  max-width: min(calc(560px * var(--ui-scale)), calc(100vw - 2 * var(--hud-edge)));
  transform: translateX(-50%);
  pointer-events: none;
}

.screen-interaction-prompts > [hidden] { display: none; }

/* Parked against a side, the strips face the edge they hang from rather than
   staying centred on nothing. */

.screen-interaction-prompts[data-hud-anchor-x='left'] { align-items: flex-start; }

.screen-interaction-prompts[data-hud-anchor-x='right'] { align-items: flex-end; }

/* An idle column measures nothing, so the layout editor is given a body to
   measure and, since the column is otherwise click-through, one to drag. */

.hud-layout-editing .screen-interaction-prompts {
  min-width: calc(200px * var(--ui-scale));
  min-height: calc(40px * var(--ui-scale));
  pointer-events: auto;
}

/* High-contrast theme (low-vision aid). Group E toggles body.high-contrast;
   here we raise panel opacity, lighten muted text, strengthen borders/shadows
   and brighten the status bar fills. palette.ts brightens the rarity/minimap
   colours in lockstep so the 3D-adjacent HUD colours track the same toggle. */

body.high-contrast {
  --muted: #d2d8e2;        /* was #8a93a3 — readable on dark panels */
  --text: #ffffff;
  --danger: #ff5a44;
  --hud-load-heavy-ring: #ffd35c;
  --hud-load-overloaded-ring: #ff7a62;
}

body.high-contrast .status-panel,
body.high-contrast .target-panel,
body.high-contrast .menu-buttons,
body.high-contrast .menu-btn,
body.high-contrast .panel,
body.high-contrast .panel-box,
body.high-contrast .hot-slot,
body.high-contrast .chat-log,
body.high-contrast .chat-tab,
body.high-contrast .npc-win,
body.high-contrast .shop-panel,
body.high-contrast .cashshop-panel,
body.high-contrast .mail-panel,
body.high-contrast .itemsvc-panel,
body.high-contrast .refine-panel,
body.high-contrast .armory-panel,
body.high-contrast .informant-panel,
body.high-contrast .trade-panel {
  background: #0a0e14 !important;     /* opaque, not translucent */
  border-color: #6f7d92 !important;  /* stronger panel edges */
}

body.high-contrast .window,
body.high-contrast .enhance-panel { background: #0a0e14; border-color: #6f7d92; }

body.high-contrast .win-head { border-bottom-color: #6f7d92; }

body.high-contrast .shop-row,
body.high-contrast .cashshop-row,
body.high-contrast .mail-row,
body.high-contrast .itemsvc-row,
body.high-contrast .mail-field input,
body.high-contrast .mail-field textarea {
  border-color: #6f7d92;
  background: #0a0e14;
}

/* Coach directive banner (doc "Coach Directives"): Renn-led teaching surface,
   distinct from the quest tracker and transient bottom-of-screen chat. */

/* Not docked: the banner has to stay above the window it just told the player to
   open, and a dock row's z-index is a ceiling its own could never climb out of.
   It opens under the top-centre cluster instead, off that cluster's published
   edge rather than off a copy of its height. */

.coach-banner {
  position: absolute;
  top: max(
    11%,
    var(--safe-top),
    calc(var(--hud-top-center-bottom, 0px) + var(--hud-gap))
  );
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  justify-content: center;
  pointer-events: none;
  z-index: 40;
  width: min(calc(620px * var(--ui-scale)), calc(100% - 2 * var(--hud-edge)));
  max-width: calc(100% - 2 * var(--hud-edge));
  --feedback-enter-x: 0px;
  --feedback-enter-y: calc(-7px * var(--ui-scale));
}

.coach-banner-text {
  padding: 10px 20px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--panel, #14110b) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent, #e6cc80) 55%, transparent);
  color: var(--text, #f2e9d0);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(0, 0, 0, 0.3);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.coach-banner[data-hud-anchor-x="left"] { --feedback-enter-x: calc(-7px * var(--ui-scale)); }

.coach-banner[data-hud-anchor-x="right"] { --feedback-enter-x: calc(7px * var(--ui-scale)); }

.coach-banner[data-hud-anchor-y="bottom"] { --feedback-enter-y: calc(7px * var(--ui-scale)); }

.coach-banner.coach-banner-enter .coach-banner-card {
  animation: coach-banner-in var(--hud-fold-duration, 180ms) var(--hud-fold-ease, cubic-bezier(.2, .78, .24, 1));
}

@keyframes coach-banner-in {
  from {
    opacity: 0;
    transform: translate(var(--feedback-enter-x), var(--feedback-enter-y)) scale(.985);
  }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}

body.reduce-flashing .coach-banner.coach-banner-enter .coach-banner-card,
body.reduce-motion .coach-banner.coach-banner-enter .coach-banner-card { animation: none; }

.coach-banner-card {
  position: relative;
  display: grid;
  grid-template-columns: calc(76px * var(--ui-scale)) minmax(0, 1fr);
  align-items: stretch;
  width: 100%;
  min-height: calc(76px * var(--ui-scale));
  overflow: hidden;
  border: 0;
  border-left-width: calc(3px * var(--ui-scale));
  border-left-style: solid;
  border-left-color: color-mix(in srgb, var(--accent) 72%, #111820);
  border-radius: calc(8px * var(--ui-scale));
  background: linear-gradient(105deg, rgba(22, 28, 38, 0.97), rgba(10, 14, 20, 0.92));
  box-shadow:
    0 calc(10px * var(--ui-scale)) calc(28px * var(--ui-scale)) rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.coach-banner[data-hud-anchor-x="left"] .coach-banner-card {
  background: linear-gradient(90deg, rgba(22, 28, 38, .96), rgba(10, 14, 20, .72) 78%, rgba(10, 14, 20, .22));
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-card {
  grid-template-columns: minmax(0, 1fr) calc(76px * var(--ui-scale));
  border-right: calc(3px * var(--ui-scale)) solid color-mix(in srgb, var(--accent) 72%, #111820);
  border-left: 0;
  background: linear-gradient(270deg, rgba(22, 28, 38, .96), rgba(10, 14, 20, .72) 78%, rgba(10, 14, 20, .22));
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-portrait-frame {
  grid-column: 2;
  grid-row: 1;
  border-right: 0;
  border-left: 1px solid color-mix(in srgb, var(--accent) 28%, #26303e);
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-copy {
  grid-column: 1;
  grid-row: 1;
  align-items: flex-end;
  text-align: right;
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-heading {
  justify-content: flex-end;
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-text { text-align: right; }

.coach-banner-card::after {
  content: '';
  position: absolute;
  left: calc(76px * var(--ui-scale));
  right: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 70%, transparent), transparent 72%);
}

.coach-banner[data-hud-anchor-x="right"] .coach-banner-card::after {
  right: calc(76px * var(--ui-scale));
  left: 0;
  background: linear-gradient(270deg, color-mix(in srgb, var(--accent) 70%, transparent), transparent 72%);
}

.coach-banner-card--anonymous {
  grid-template-columns: minmax(0, 1fr);
}

.coach-banner-card--anonymous::after {
  left: 0;
}

.coach-banner-portrait-frame {
  position: relative;
  overflow: hidden;
  border-right: 1px solid color-mix(in srgb, var(--accent) 28%, #26303e);
  background: radial-gradient(circle at 52% 40%, color-mix(in srgb, var(--gold) 18%, transparent), rgba(8, 12, 18, 0.88) 70%);
}

.coach-banner-portrait-frame[hidden],
.coach-banner-heading[hidden] {
  display: none;
}

/* The presenter's world illustration is a full standing card; the tile enlarges it
   until only the head and shoulders sit inside, so the banner reads as a face at
   HUD size while staying the same art the player meets in the world. */

.coach-banner-portrait {
  position: absolute;
  top: 0;
  left: 50%;
  width: auto;
  height: 300%;
  /* The card's subject stands a shade left of its own center; the extra 3% puts his
     face, not the illustration's midpoint, in the middle of the tile. */
  transform: translateX(-47%);
  filter: drop-shadow(0 calc(2px * var(--ui-scale)) calc(4px * var(--ui-scale)) rgba(0, 0, 0, 0.45));
}

.coach-banner-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: calc(4px * var(--ui-scale));
  min-width: 0;
  padding: calc(10px * var(--ui-scale)) calc(16px * var(--ui-scale));
}

.coach-banner-heading {
  display: flex;
  align-items: baseline;
  gap: calc(8px * var(--ui-scale));
  min-width: 0;
}

.coach-banner-name {
  color: var(--accent);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 800;
  line-height: 1;
}

.coach-banner-role {
  overflow: hidden;
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.12em;
  line-height: 1;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

.coach-banner .coach-banner-text {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: calc(15px * var(--ui-scale));
  line-height: 1.35;
  text-align: left;
  box-shadow: none;
}

body.high-contrast .coach-banner-card {
  background: #0a0e14;
  border-color: var(--accent);
}

@media (max-width: 620px) {
  .coach-banner {
    top: max(8%, var(--safe-top));
    width: calc(100% - 2 * var(--hud-edge));
  }
  .coach-banner-card {
    grid-template-columns: calc(62px * var(--ui-scale)) minmax(0, 1fr);
    min-height: calc(68px * var(--ui-scale));
  }
  .coach-banner[data-hud-anchor-x="right"] .coach-banner-card {
    grid-template-columns: minmax(0, 1fr) calc(62px * var(--ui-scale));
  }
 .coach-banner-card::after { left: calc(62px * var(--ui-scale)); }
 .coach-banner[data-hud-anchor-x="right"] .coach-banner-card::after {
   right: calc(62px * var(--ui-scale));
   left: 0;
 }
 .coach-banner-card--anonymous {
 grid-template-columns: minmax(0, 1fr);
 }
 .coach-banner-card--anonymous::after { left: 0; }
  .coach-banner-copy {
    gap: calc(3px * var(--ui-scale));
    padding: calc(8px * var(--ui-scale)) calc(11px * var(--ui-scale));
  }
  .coach-banner .coach-banner-text { font-size: calc(13px * var(--ui-scale)); }
}

/* Wayfind bar (doc "Wayfinding"): names the place the ground trail leads to, and
   carries the control that stops following it. It is an always-on HUD strip and
   wears the strip grammar the alert line and the notice row wear — a scrim that
   fades into the world, a tone tick in the route's own tint, and no box of its
   own — so it belongs to the column it stands in instead of reading as a window
   parked over the world. It docks in the bottom-centre rail directly above the
   action bars and states no offset of its own, so the rail's flow — not a
   snapshot of a neighbour's height — decides where it lands. */

/* The tint the ground trail is drawn in, so the bar and every Navi control read
   as belonging to the chevrons on the floor; WAYFIND_TRAIL_STYLE carries the same
   value for the geometry. */

:root { --wayfind-accent: #d8c798; }

.wayfind-bar {
  display: flex;
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(14px * var(--ui-scale));
  border: 0;
  border-right: calc(2px * var(--ui-scale)) solid transparent;
  border-left: calc(2px * var(--ui-scale)) solid transparent;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, rgba(7, 12, 14, 0.74) 18%, rgba(7, 12, 14, 0.74) 82%, transparent);
  box-shadow: inset 0 1px color-mix(in srgb, var(--wayfind-accent) 58%, transparent);
  color: var(--text);
  text-shadow: 0 1px 2px #000;
  pointer-events: auto;
  max-width: 100%;
}

/* The lift belongs to a strip with a route in it and to no other state of this
   widget: it is a filter so it follows the scrim's own alpha instead of drawing
   a rectangle past where the scrim has faded out, and a slot the layout editor
   is holding open wears the editor's own grey rather than a second filter.
   The strip takes no enter beat, which is the one place it parts company with
   the alert line and the notice row. Those are transient rows nothing measures.
   This is a placeable HUD surface, and the layout budget proves it stands clear
   of the action bars it shares the bottom centre with, to a few pixels — an
   opening frame that is offset or transparent is a surface reported as
   overlapping or as never drawn. The route arriving is already announced: the
   trail is drawn on the ground and the copy is a live region. */

.wayfind-bar:not(.hud-layout-ghost) {
  filter: drop-shadow(0 calc(4px * var(--ui-scale)) calc(10px * var(--ui-scale)) rgba(0, 0, 0, 0.3));
}

/* Off the centre column the strip takes the leading-edge tick the alert line
   takes there, and mirrors with the edge it was parked against. */

.wayfind-bar[data-hud-anchor-x='left'] {
  border-left-color: var(--wayfind-accent);
  background: linear-gradient(90deg, rgba(7, 12, 14, 0.82), rgba(7, 12, 14, 0.48) 72%, transparent);
  box-shadow: none;
}

.wayfind-bar[data-hud-anchor-x='right'] {
  flex-direction: row-reverse;
  border-right-color: var(--wayfind-accent);
  background: linear-gradient(270deg, rgba(7, 12, 14, 0.82), rgba(7, 12, 14, 0.48) 72%, transparent);
  box-shadow: none;
}

.wayfind-bar[data-hud-anchor-x='right'] .wayfind-bar-copy {
  align-items: flex-end;
  text-align: right;
}

/* The mark repeats the trail's tint and its silhouette split — a faceted rune for
   the destination itself, a ring for the gate that leads to it — so the bar says
   which of the two the trail currently ends at without relying on colour. */

.wayfind-bar-mark {
  width: calc(10px * var(--ui-scale));
  height: calc(10px * var(--ui-scale));
  flex: none;
  border-radius: 2px;
  background: var(--wayfind-accent);
  transform: rotate(45deg);
}

.wayfind-bar[data-waypoint='warp'] .wayfind-bar-mark { border-radius: 50%; transform: none; }

.wayfind-bar-copy { display: flex; flex-direction: column; min-width: 0; }

.wayfind-bar-label {
  font-size: calc(13px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wayfind-bar-distance {
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.2;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The stop control is a kit ghost button: this places it and drops the strip's
   own scrim treatment off it, and paints nothing. */

.wayfind-bar-stop {
  flex: none;
  text-shadow: none;
}

/* Held open by the layout editor with no route to name: the placeholder is the
   whole widget, so the strip's own parts — including a live Stop — stand down. */

.wayfind-bar.hud-layout-ghost .wayfind-bar-mark,
.wayfind-bar.hud-layout-ghost .wayfind-bar-copy,
.wayfind-bar.hud-layout-ghost .wayfind-bar-stop { display: none; }

body.high-contrast .wayfind-bar {
  background: #0a0e14;
  box-shadow: inset 0 calc(2px * var(--ui-scale)) var(--wayfind-accent);
}

body.high-contrast .wayfind-bar[data-hud-anchor-x='left'],
body.high-contrast .wayfind-bar[data-hud-anchor-x='right'] {
  background: #0a0e14;
  box-shadow: none;
}

/* Recall: a one-slot action bar on the left end of the cluster, set off from the
   main bar by a gap (action-bars.css owns that column). The slot itself is a
   hotbar slot and inherits that chrome, so only the row and the two states the
   offer set can put it in are styled here. See "Recall". */

.action-bar--recall {
  display: flex;
  justify-content: flex-end;
}

/* Not offered on this map: dimmed and inert, with no sweep and no number, so it
   never reads as a cooldown the player only has to wait out. */

.recall-slot.is-blocked {
  opacity: 0.45;
  filter: grayscale(0.8);
}

.recall-slot:disabled {
  cursor: default;
}

/* Fold identity: every collapsible HUD surface keeps this same anchored core.
   Expanded content grows away from the trigger; collapse preserves the surface
   icon plus its critical live state instead of replacing it with a generic arrow. */

:root {
  --hud-fold-size: max(36px, calc(42px * var(--ui-scale, 1)));
  --hud-fold-inset: max(4px, calc(6px * var(--ui-scale, 1)));
  --hud-fold-shell-size: calc(var(--hud-fold-size) + 2 * var(--hud-fold-inset));
  --hud-fold-reserve-gap: 0px;
  --hud-fold-reserve: calc(var(--hud-fold-shell-size) + var(--hud-fold-reserve-gap));
  --hud-fold-reserve-left: var(--hud-fold-reserve);
  --hud-fold-reserve-right: 0px;
  --hud-fold-reserve-top: var(--hud-fold-reserve);
  --hud-fold-reserve-bottom: 0px;
  --hud-fold-duration: 180ms;
  --hud-fold-fade-duration: 110ms;
  --hud-fold-ease: cubic-bezier(.2, .78, .24, 1);
  --hud-fold-offset: 6px;
  --hud-fold-scale: 0.97;
}

@media (pointer: coarse) {
  :root {
    --hud-fold-size: max(44px, calc(42px * var(--ui-scale, 1)));
  }
}

.hud-fold-trigger {
  position: relative;
  isolation: isolate;
  box-sizing: border-box;
  width: var(--hud-fold-size);
  height: var(--hud-fold-size);
  flex: 0 0 var(--hud-fold-size);
  display: grid;
  place-items: center;
  padding: calc(3px * var(--ui-scale, 1));
  overflow: visible;
  border: 0;
  border-radius: 0;
  color: var(--muted);
  background: transparent;
  cursor: var(--cursor-action);
  box-shadow: none;
  filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.72));
  transition:
    color 120ms ease,
    transform 100ms ease;
}

.hud-fold-trigger::before,
.hud-fold-trigger::after {
  position: absolute;
  content: '';
  pointer-events: none;
  clip-path: polygon(6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px);
}

.hud-fold-trigger::before {
  inset: 0;
  z-index: -2;
  background: color-mix(in srgb, var(--ui-line) 82%, transparent);
}

.hud-fold-trigger::after {
  inset: 1px;
  z-index: -1;
  background:
    linear-gradient(145deg, rgb(36, 46, 48), rgb(8, 13, 15) 66%),
    rgb(8, 13, 15);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.07);
}

.hud-fold-trigger:hover,
.hud-fold-trigger[aria-expanded="true"] {
  color: var(--text);
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  filter:
    drop-shadow(0 3px 4px rgba(0, 0, 0, 0.72))
    drop-shadow(0 0 5px color-mix(in srgb, var(--accent) 32%, transparent));
  transform: none;
}

.hud-fold-trigger:hover::before,
.hud-fold-trigger[aria-expanded="true"]::before {
  background: var(--accent);
}

.hud-fold-trigger:active {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7));
  transform: translateY(0) scale(0.97);
}

.hud-fold-trigger:focus-visible {
  outline: 2px solid var(--focus-ring, #fff0bd);
  outline-offset: 3px;
  box-shadow: none;
}

.hud-fold-glyph {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: calc(var(--hud-fold-size) - 8px);
  height: calc(var(--hud-fold-size) - 8px);
  border-radius: 0;
  background: transparent;
}

.hud-fold-icon {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: auto;
  pointer-events: none;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.78));
  transition:
    opacity var(--hud-fold-fade-duration) ease;
  will-change: opacity;
}

.hud-fold-icon--compact {
  opacity: 1;
  transform: none;
}

.hud-fold-icon--expanded {
  opacity: 0;
  transform: none;
}

.hud-fold-trigger[aria-expanded="true"] .hud-fold-icon--compact {
  opacity: 0;
  transform: none;
}

.hud-fold-trigger[aria-expanded="true"] .hud-fold-icon--expanded {
  opacity: 1;
  transform: none;
}

/* The shared opposing rail used by folded unit and Core identities. Each
   surface supplies only its authoritative fill and semantic paint. */

.hud-fold-side-meter {
  position: absolute;
  z-index: 3;
  top: 5px;
  bottom: 5px;
  width: 3px;
  pointer-events: none;
  background: linear-gradient(
    to top,
    var(--hud-fold-meter-paint, var(--muted)) 0 var(--hud-fold-meter, 0%),
    var(--hud-fold-meter-track, rgba(10, 16, 15, 0.9)) var(--hud-fold-meter, 0%) 100%
  );
  box-shadow: 0 0 0 1px rgba(5, 9, 11, 0.84), 0 0 4px rgba(0, 0, 0, 0.7);
}

.hud-fold-side-meter--left { left: -3px; }

.hud-fold-side-meter--right { right: -3px; }

.hud-fold-side-meter[data-hp-band='healthy'] { --hud-fold-meter-paint: var(--hud-hp-healthy-line); }

.hud-fold-side-meter[data-hp-band='hurt'] { --hud-fold-meter-paint: var(--hud-hp-hurt-line); }

.hud-fold-side-meter[data-hp-band='execute'] { --hud-fold-meter-paint: var(--hud-hp-execute-line); }

.hud-fold-surface[data-hud-anchor-x='right'] .hud-fold-side-meter--left {
  right: -3px;
  left: auto;
}

.hud-fold-surface[data-hud-anchor-x='right'] .hud-fold-side-meter--right {
  right: auto;
  left: -3px;
}

/* A unit medallion whose art is a world illustration. The illustration is a
   full-body card and the medallion has to read as a face, so the card is scaled
   past the tile and the glyph clips everything below the shoulders. */

.hud-fold-glyph--portrait {
  overflow: hidden;
  clip-path: polygon(5px 0, calc(100% - 5px) 0, 100% 5px, 100% calc(100% - 5px), calc(100% - 5px) 100%, 5px 100%, 0 calc(100% - 5px), 0 5px);
}

.hud-fold-icon.hud-fold-icon--portrait {
  top: -6%;
  height: 260%;
  object-fit: cover;
  object-position: 50% 0;
}

.hud-fold-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  z-index: 3;
  display: grid;
  place-items: center;
  min-width: 15px;
  height: 15px;
  box-sizing: border-box;
  padding: 0 3px;
  border: 1px solid #fff0bd;
  border-radius: 999px;
  color: #171006;
  background: var(--accent);
  font-size: calc(8px * var(--ui-scale, 1));
  font-weight: 900;
  line-height: 1;
  box-shadow: 0 0 0 2px rgba(9, 12, 17, 0.92), 0 0 9px color-mix(in srgb, var(--gold) 62%, transparent);
  pointer-events: none;
}

.hud-fold-badge[hidden] {
  display: none;
}

/* Compact telemetry belongs to the folded identity. Expanded surfaces already
   carry the same information, so removing the duplicate ring lets the trigger
   read as part of the full frame instead of a button with ornaments around it. */

.hud-fold-decoration,
.hud-fold-badge {
  transition: opacity var(--hud-fold-fade-duration) ease,
    visibility 0s linear;
}

.hud-fold-trigger[aria-expanded="true"] > .hud-fold-decoration:not(.hud-fold-decoration--persistent),
.hud-fold-trigger[aria-expanded="true"] > .hud-fold-badge {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--hud-fold-fade-duration) ease,
    visibility 0s linear var(--hud-fold-fade-duration);
}

.hud-fold-surface.hud-fold-anchor-x-right:not([data-hud-anchor-x]) .hud-fold-badge,
.hud-fold-surface[data-hud-anchor-x="right"] .hud-fold-badge {
  right: auto;
  left: -5px;
}

.hud-fold-trigger.attention-arrive {
  animation: hud-fold-attention-arrive 180ms ease-out;
}

.menu-fold-slot,
.hud-self-fold-slot {
  display: contents;
}

@keyframes hud-fold-attention-arrive {
  0% { filter: brightness(1); }
  50% { filter: brightness(1.4) drop-shadow(0 0 9px color-mix(in srgb, var(--gold) 72%, transparent)); }
  100% { filter: brightness(1) drop-shadow(0 3px 4px rgba(0, 0, 0, 0.72)); }
}

/* Shared fold geometry. The root keeps the saved corner while its content
   reveals inward from the trigger. Surfaces only provide their expanded size
   and content layout; motion, clipping, inertness and trigger placement live
   here. Explicit layout-editor anchors override each surface's dock default. */

.hud-fold-surface {
  /* The room the trigger needs, on the side it is on, and nothing on the other
     three. A surface whose content would otherwise run under the trigger spends
     these instead of naming an edge: the anchor already decided which corner the
     trigger holds, and a second opinion about it is wrong on whichever edge it
     was not written for. A surface that wants air between its content and the
     trigger sets --hud-fold-reserve-gap rather than adding it at each use, so
     the far side stays zero and a width budget written for one side still fits.
     The same four are declared at :root, because a surface drawn in a shadow
     root reads this sheet through its own link and paints once before it. */
  --hud-fold-reserve: calc(var(--hud-fold-shell-size) + var(--hud-fold-reserve-gap, 0px));
  --hud-fold-reserve-left: var(--hud-fold-reserve);
  --hud-fold-reserve-right: 0px;
  --hud-fold-reserve-top: var(--hud-fold-reserve);
  --hud-fold-reserve-bottom: 0px;
  --hud-fold-trigger-left: var(--hud-fold-inset);
  --hud-fold-trigger-right: auto;
  --hud-fold-trigger-top: var(--hud-fold-inset);
  --hud-fold-trigger-bottom: auto;
  --hud-fold-origin-x: left;
  --hud-fold-origin-y: top;
  --hud-fold-shift-x: calc(-1 * var(--hud-fold-offset));
  --hud-fold-shift-y: calc(-1 * var(--hud-fold-offset));
  --hud-fold-clip-top: 0;
  --hud-fold-clip-right: 100%;
  --hud-fold-clip-bottom: 100%;
  --hud-fold-clip-left: 0;
  position: relative;
  box-sizing: border-box;
  width: var(--hud-fold-expanded-width, var(--hud-fold-shell-size));
  min-width: var(
    --hud-fold-expanded-min-width,
    var(--hud-fold-expanded-width, var(--hud-fold-shell-size))
  );
  height: var(--hud-fold-expanded-height, var(--hud-fold-shell-size));
  min-height: var(
    --hud-fold-expanded-min-height,
    var(--hud-fold-expanded-height, var(--hud-fold-shell-size))
  );
  overflow: visible;
  transition:
    width var(--hud-fold-duration) var(--hud-fold-ease),
    min-width var(--hud-fold-duration) var(--hud-fold-ease),
    height var(--hud-fold-duration) var(--hud-fold-ease),
    min-height var(--hud-fold-duration) var(--hud-fold-ease);
}

/* A collapsed surface shows nothing but its trigger, yet a railed one keeps the
   full expanded slot so the widgets around it do not jump. That reserved area is
   empty screen: a tap there is a tap on the world and has to reach it, or a
   player on a touch screen taps to walk and simply does not move. The trigger
   re-arms itself; so does anything else that stays visible while folded. */

.hud-fold-surface.is-collapsed {
  width: var(--hud-fold-shell-size);
  min-width: var(--hud-fold-shell-size);
  height: var(--hud-fold-shell-size);
  min-height: var(--hud-fold-shell-size);
  pointer-events: none;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* Except while the player is arranging the HUD: there the widget itself is the
   thing being grabbed, empty area and all. */

.hud-layout-editing .hud-fold-surface.is-collapsed {
  pointer-events: auto;
}

/* A surface that has a unit's face puts it in the trigger's place while open: the
   tile covers the medallion, so the face reads at the corner the collapsed
   trigger occupies, and folding hands the corner back to the trigger. One rule
   for every such surface — the character the player is and the one they have
   selected wear the same tile, so they must also sit and crop the same. */

.hud-fold-surface .unit-portrait {
  position: absolute;
  top: calc(var(--hud-fold-inset) + 4px);
  left: calc(var(--hud-fold-inset) + 4px);
  z-index: 9;
  width: calc(var(--hud-fold-size) - 8px);
  height: calc(var(--hud-fold-size) - 8px);
  border: 0;
  border-radius: 0;
  clip-path: polygon(5px 0, calc(100% - 5px) 0, 100% 5px, 100% calc(100% - 5px), calc(100% - 5px) 100%, 5px 100%, 0 calc(100% - 5px), 0 5px);
  box-shadow: none;
  animation: none;
  pointer-events: none;
}

.hud-fold-surface[data-hud-anchor-x="right"] .unit-portrait {
  right: calc(var(--hud-fold-inset) + 4px);
  left: auto;
}

.hud-fold-surface[data-hud-anchor-y="bottom"] .unit-portrait {
  top: auto;
  bottom: calc(var(--hud-fold-inset) + 4px);
}

.hud-fold-surface.is-collapsed .unit-portrait,
.hud-fold-surface .unit-portrait::after {
  display: none;
}

.hud-fold-surface:has(.unit-portrait) .hud-fold-trigger[aria-expanded="true"] .hud-fold-icon {
  opacity: 0;
}

/* Railed folds are visual disclosures, not layout changes. Hidden content still
   measures the auto-sized surfaces; fixed-size surfaces reuse their authored
   expanded dimensions. Detached widgets keep the compact dimensions above. */

.hud-fold-surface.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed {
  width: var(--hud-fold-expanded-width, var(--hud-fold-shell-size));
  min-width: var(
    --hud-fold-expanded-min-width,
    var(--hud-fold-expanded-width, var(--hud-fold-shell-size))
  );
  height: var(--hud-fold-expanded-height, var(--hud-fold-shell-size));
  min-height: var(
    --hud-fold-expanded-min-height,
    var(--hud-fold-expanded-height, var(--hud-fold-shell-size))
  );
}

.hud-fold-surface-trigger {
  position: absolute;
  left: var(--hud-fold-trigger-left);
  right: var(--hud-fold-trigger-right);
  top: var(--hud-fold-trigger-top);
  bottom: var(--hud-fold-trigger-bottom);
  z-index: 8;
  pointer-events: auto;
}

.hud-fold-content {
  opacity: 1;
  visibility: visible;
  transform: translate(0, 0) scale(1);
  transform-origin: var(--hud-fold-origin-x) var(--hud-fold-origin-y);
  clip-path: inset(0);
  transition:
    opacity var(--hud-fold-fade-duration) ease 45ms,
    transform var(--hud-fold-duration) var(--hud-fold-ease),
    clip-path var(--hud-fold-duration) var(--hud-fold-ease),
    visibility 0s linear;
  will-change: opacity, transform, clip-path;
}

.hud-fold-surface.is-collapsed .hud-fold-content {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform:
    translate(var(--hud-fold-shift-x), var(--hud-fold-shift-y))
    scale(var(--hud-fold-scale));
  clip-path: inset(
    var(--hud-fold-clip-top)
    var(--hud-fold-clip-right)
    var(--hud-fold-clip-bottom)
    var(--hud-fold-clip-left)
  );
  transition:
    opacity var(--hud-fold-fade-duration) ease,
    transform var(--hud-fold-duration) var(--hud-fold-ease),
    clip-path var(--hud-fold-duration) var(--hud-fold-ease),
    visibility 0s linear var(--hud-fold-duration);
}

.hud-fold-surface.hud-fold-anchor-x-center:not([data-hud-anchor-x]),
.hud-fold-surface[data-hud-anchor-x="center"] {
  --hud-fold-reserve-left: 0px;
  --hud-fold-reserve-right: 0px;
  --hud-fold-trigger-left: calc(50% - var(--hud-fold-size) / 2);
  --hud-fold-origin-x: center;
  --hud-fold-shift-x: 0px;
  --hud-fold-clip-right: 50%;
  --hud-fold-clip-left: 50%;
}

.hud-fold-surface.hud-fold-anchor-x-right:not([data-hud-anchor-x]),
.hud-fold-surface[data-hud-anchor-x="right"] {
  --hud-fold-reserve-left: 0px;
  --hud-fold-reserve-right: var(--hud-fold-reserve);
  --hud-fold-trigger-left: auto;
  --hud-fold-trigger-right: var(--hud-fold-inset);
  --hud-fold-origin-x: right;
  --hud-fold-shift-x: var(--hud-fold-offset);
  --hud-fold-clip-right: 0;
  --hud-fold-clip-left: 100%;
}

.hud-fold-surface[data-hud-anchor-x="left"] {
  --hud-fold-reserve-left: var(--hud-fold-reserve);
  --hud-fold-reserve-right: 0px;
  --hud-fold-trigger-left: var(--hud-fold-inset);
  --hud-fold-trigger-right: auto;
  --hud-fold-origin-x: left;
  --hud-fold-shift-x: calc(-1 * var(--hud-fold-offset));
  --hud-fold-clip-right: 100%;
  --hud-fold-clip-left: 0;
}

.hud-fold-surface.hud-fold-anchor-y-bottom:not([data-hud-anchor-y]),
.hud-fold-surface[data-hud-anchor-y="bottom"] {
  --hud-fold-reserve-top: 0px;
  --hud-fold-reserve-bottom: var(--hud-fold-reserve);
  --hud-fold-trigger-top: auto;
  --hud-fold-trigger-bottom: var(--hud-fold-inset);
  --hud-fold-origin-y: bottom;
  --hud-fold-shift-y: var(--hud-fold-offset);
  --hud-fold-clip-top: 100%;
  --hud-fold-clip-bottom: 0;
}

.hud-fold-surface[data-hud-anchor-y="top"] {
  --hud-fold-reserve-top: var(--hud-fold-reserve);
  --hud-fold-reserve-bottom: 0px;
  --hud-fold-trigger-top: var(--hud-fold-inset);
  --hud-fold-trigger-bottom: auto;
  --hud-fold-origin-y: top;
  --hud-fold-shift-y: calc(-1 * var(--hud-fold-offset));
  --hud-fold-clip-top: 0;
  --hud-fold-clip-bottom: 100%;
}

body.reduce-motion .hud-fold-trigger,
body.reduce-motion .hud-fold-icon,
body.reduce-motion .hud-fold-content,
body.reduce-motion .hud-fold-surface,
body.reduce-motion .hud-fold-decoration,
body.reduce-motion .hud-fold-badge,
.reduce-motion .hud-fold-trigger,
.reduce-motion .hud-fold-icon,
.reduce-motion .hud-fold-content,
.reduce-motion .hud-fold-surface,
.reduce-motion .hud-fold-decoration,
.reduce-motion .hud-fold-badge {
  transition: none !important;
  animation: none !important;
}

body.reduce-flashing .hud-fold-trigger,
.reduce-flashing .hud-fold-trigger {
  animation: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .hud-fold-trigger,
  .hud-fold-icon,
  .hud-fold-content,
  .hud-fold-surface,
  .hud-fold-decoration,
  .hud-fold-badge,
  .hud-self-content {
    transition: none !important;
    animation: none !important;
  }
}

body.high-contrast .hud-fold-trigger::before {
  background: currentColor;
}

/* Chat and the command dock use the same shell footprint around the trigger. */

.menu-buttons.is-collapsible {
  padding: var(--hud-fold-inset);
}

.menu-buttons.is-collapsible.is-collapsed {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.menu-buttons .menu-collapse-btn.hud-fold-trigger {
  width: var(--hud-fold-size);
  height: var(--hud-fold-size);
  flex-basis: var(--hud-fold-size);
}

/* The mark for something the player is owed hangs off the shell rather than
   riding the dock's flow. The dock's box is the trigger shell in both states —
   its commands open as a placed flyout, so the box never grows — and a mark laid
   out inside it overflows on whichever side the dock is anchored to, which is a
   screen edge. It reveals inward from the shell instead, the same move the chat
   preview makes, banded with the trigger so the two read as one row. The flyout
   opens clear above or below the dock, so nothing is ever under it. */

.menu-buttons .menu-badge-slot {
  position: absolute;
  top: var(--hud-fold-trigger-top);
  right: auto;
  bottom: var(--hud-fold-trigger-bottom);
  left: calc(var(--hud-fold-shell-size) + var(--hud-gap, 8px));
  display: flex;
  align-items: center;
  height: var(--hud-fold-size);
  /* An out-of-flow box beside a shell-wide dock is offered no width to fit into,
     so it would shrink to its longest word and wrap the rest onto a second line.
     The mark is one row: it takes the width its text asks for, up to the cap. */
  width: max-content;
  max-width: min(
    calc(220px * var(--ui-scale, 1)),
    calc(100vw - var(--hud-fold-shell-size) - 2 * var(--hud-gap, 8px))
  );
  /* A collapsed dock stops taking presses for the empty area it keeps around the
     trigger, so the world beneath stays clickable. The badge is the one thing in
     that area that is still a button and re-arms itself, and the slot around it
     does not, or an owed reward would put an invisible hole over the world. */
  pointer-events: none;
}

.menu-buttons .menu-badge-slot > * {
  pointer-events: auto;
}

.menu-buttons.hud-fold-anchor-x-right:not([data-hud-anchor-x]) .menu-badge-slot,
.menu-buttons[data-hud-anchor-x="right"] .menu-badge-slot {
  right: calc(var(--hud-fold-shell-size) + var(--hud-gap, 8px));
  left: auto;
}

.chat-wrap {
  --hud-fold-reserve-gap: 8px;
  --chat-row: var(--hud-fold-size);
  padding: calc(8px * var(--ui-scale, 1));
}

.chat-wrap.hud-fold-surface:not(.is-collapsed) {
  width: var(--chat-w, calc(340px * var(--ui-scale, 1)));
  min-width: min(64vw, var(--chat-w, calc(340px * var(--ui-scale, 1))));
  height: auto;
  min-height: var(--hud-fold-shell-size);
}

.chat-wrap.is-collapsed {
  width: var(--hud-fold-shell-size);
  min-width: var(--hud-fold-shell-size);
  height: var(--hud-fold-shell-size);
  min-height: var(--hud-fold-shell-size);
  padding: var(--hud-fold-inset);
  background: transparent;
  border: 0;
  border-left: 1px solid transparent;
  border-radius: 0;
  box-shadow: none;
}

.chat-collapse-badge[hidden] {
  display: none;
}

.chat-compact-preview {
  position: absolute;
  right: auto;
  bottom: var(--hud-fold-inset);
  left: calc(var(--hud-fold-shell-size) + 6px);
  width: max-content;
  max-width: min(calc(300px * var(--ui-scale, 1)), calc(100vw - var(--hud-fold-shell-size) - 32px));
  min-height: calc(22px * var(--ui-scale, 1));
  box-sizing: border-box;
  overflow: hidden;
  padding: calc(4px * var(--ui-scale, 1)) calc(34px * var(--ui-scale, 1)) calc(4px * var(--ui-scale, 1)) calc(9px * var(--ui-scale, 1));
  border-left: 1px solid color-mix(in srgb, currentColor 54%, transparent);
  color: var(--text);
  background: linear-gradient(90deg, rgba(7, 12, 14, 0.68), rgba(7, 12, 14, 0.24) 76%, transparent);
  font-size: calc(11px * var(--ui-scale, 1));
  line-height: 1.25;
  text-overflow: ellipsis;
  text-shadow: 0 1px 2px #000;
  white-space: nowrap;
  pointer-events: none;
}

.chat-compact-preview[hidden],
.chat-wrap:not(.is-collapsed) .chat-compact-preview {
  display: none;
}

.chat-compact-preview.system { color: var(--accent); }

.chat-compact-preview.whisper { color: #4fd0e0; }

.chat-compact-preview.party { color: #79d67a; }

.chat-compact-preview.guild { color: #e2a15d; }

.chat-wrap[data-hud-anchor-x="right"] .chat-compact-preview {
  right: calc(var(--hud-fold-shell-size) + 6px);
  left: auto;
  padding-right: calc(9px * var(--ui-scale, 1));
  padding-left: calc(34px * var(--ui-scale, 1));
  border-right: 1px solid color-mix(in srgb, currentColor 54%, transparent);
  border-left: 0;
  background: linear-gradient(270deg, rgba(7, 12, 14, 0.68), rgba(7, 12, 14, 0.24) 76%, transparent);
  text-align: right;
}

.chat-wrap[data-hud-anchor-y="top"] .chat-compact-preview {
  top: var(--hud-fold-inset);
  bottom: auto;
}

/* The self frame's fold trigger is its compact unit frame: character art remains
   central while edge position distinguishes HP, SP and EXP without color alone. */

.status-panel {
  --hud-self-fold-gap: calc(8px * var(--ui-scale, 1));
  --hud-fold-reserve-gap: var(--hud-self-fold-gap);
}

.status-panel.hud-fold-surface:not(.is-collapsed) {
  width: min(calc(252px * var(--ui-scale)), calc(100vw - 24px));
  min-width: min(calc(252px * var(--ui-scale)), calc(100vw - 24px));
  height: auto;
  min-height: var(--hud-fold-shell-size);
}

.hud-self-content {
  max-height: calc(180px * var(--ui-scale, 1));
}

.status-panel .hud-self-header {
  min-height: var(--hud-fold-shell-size);
  box-sizing: border-box;
  justify-content: center;
  padding-left: var(--hud-fold-reserve-left);
  padding-right: var(--hud-fold-reserve-right);
}

.status-panel .hud-character-level {
  display: none;
}

/* Parked low, the portrait holds the bottom corner, so the identity row it
   belongs to goes there with it and the gauges stack away from the trigger. */

.status-panel[data-hud-anchor-y="bottom"] .hud-self-content {
  display: flex;
  flex-direction: column-reverse;
}

.status-panel[data-hud-anchor-y="bottom"] .hud-self-top {
  margin-block: calc(4px * var(--ui-scale)) 0;
}

.status-panel.is-collapsed {
  width: var(--hud-fold-shell-size);
  height: var(--hud-fold-shell-size);
  min-width: var(--hud-fold-shell-size);
  padding: var(--hud-fold-inset);
  filter: none;
}

.status-panel.is-collapsed .hud-self-content {
  max-height: 0;
}

/* A docked character collapse is visual only: the rail keeps the expanded
   slot so the party below and the auxiliary rail beside it do not jump. A
   detached character still shrinks to the identity core at its saved edge. */

.status-panel.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed {
  width: min(calc(252px * var(--ui-scale)), calc(100vw - 24px));
  min-width: min(calc(252px * var(--ui-scale)), calc(100vw - 24px));
  height: auto;
  min-height: var(--hud-fold-shell-size);
  padding: calc(2px * var(--ui-scale)) 0;
}

.status-panel.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed
  .hud-self-content {
  max-height: calc(180px * var(--ui-scale, 1));
}

.hud-self-fold-trigger .hud-fold-icon {
  transform-origin: center bottom;
}

.hud-self-fold-trigger .hud-self-fold-portrait {
  max-width: none;
}

.hud-self-fold-meter {
  pointer-events: none;
}

.hud-self-fold-meter--hp {
  --hud-fold-meter-track: rgba(10, 16, 15, 0.88);
}

.hud-self-fold-meter--sp {
  --hud-fold-meter-paint: #4ba8c2;
  --hud-fold-meter-track: rgba(19, 43, 54, 0.88);
}

.hud-self-fold-meter--exp {
  position: absolute;
  z-index: 2;
  right: 5px;
  bottom: -3px;
  left: 5px;
  height: 3px;
  background: linear-gradient(
    to right,
    var(--gold) 0 var(--hud-fold-meter),
    rgba(55, 44, 22, 0.88) var(--hud-fold-meter) 100%
  );
  box-shadow: 0 0 0 1px rgba(5, 9, 11, 0.84), 0 0 4px rgba(0, 0, 0, 0.7);
}

.hud-self-fold-level {
  color: #fff2c4;
  background: rgba(20, 15, 8, 0.98);
}

.hud-self-fold-trigger.is-critical::before {
  background: var(--danger);
}

/* Party: the emblem and member count anchor one connected rail of the three most
   urgent ally HP strips. A party can contain twelve members; the total badge and
   overflow label keep that truth visible without shrinking every ally to noise. */

.party-frames.hud-fold-surface {
  --hud-fold-reserve-gap: 5px;
  --hud-fold-expanded-width: min(calc(236px * var(--ui-scale)), calc(100vw - 24px));
  --hud-fold-expanded-height: auto;
  --hud-fold-expanded-min-height: var(--hud-fold-shell-size);
}

.party-frames.hud-fold-surface:not(.is-collapsed),
.party-frames.hud-fold-surface.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed {
  padding-top: var(--hud-fold-reserve-top);
  padding-bottom: var(--hud-fold-reserve-bottom);
}

.party-fold-roster {
  position: absolute;
  top: 50%;
  left: calc(100% - 5px);
  z-index: 4;
  display: grid;
  width: max(22px, calc(24px * var(--ui-scale, 1)));
  box-sizing: border-box;
  gap: max(1px, calc(2px * var(--ui-scale, 1)));
  padding: 0;
  translate: 0 -50%;
  background: transparent;
  pointer-events: none;
}

.party-fold-member {
  position: relative;
  width: 100%;
  height: max(4px, calc(5px * var(--ui-scale, 1)));
  overflow: hidden;
  border: 1px solid rgba(216, 220, 224, 0.3);
  clip-path: polygon(2px 0, 100% 0, calc(100% - 2px) 100%, 0 100%);
  background: linear-gradient(
    to right,
    var(--hud-hp-line) 0 var(--hud-fold-meter),
    rgba(10, 16, 15, 0.9) var(--hud-fold-meter) 100%
  );
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

.party-fold-member.is-dead {
  border-color: color-mix(in srgb, var(--danger) 78%, #fff);
  background: linear-gradient(135deg, var(--danger), rgba(67, 18, 18, 0.94));
}

.party-fold-member.is-unknown {
  border-style: dashed;
  background: rgba(95, 105, 112, 0.58);
}

.party-fold-member[hidden],
.party-fold-overflow[hidden] { display: none; }

.party-fold-overflow {
  justify-self: end;
  padding: 1px 2px;
  border-radius: 2px;
  color: var(--muted);
  background: rgba(7, 13, 15, 0.9);
  font-size: calc(7px * var(--ui-scale, 1));
  font-weight: 800;
  line-height: 1;
  text-shadow: 0 1px 2px #000;
}

.party-frames[data-hud-anchor-x="right"] .party-fold-roster {
  right: calc(100% - 5px);
  left: auto;
}

.party-frames[data-hud-anchor-x="right"] .party-fold-member {
  clip-path: polygon(0 0, calc(100% - 2px) 0, 100% 100%, 2px 100%);
}

.party-frames[data-hud-anchor-x="center"] .party-fold-roster {
  top: auto;
  bottom: calc(100% - 5px);
  left: 50%;
  translate: -50% 0;
}

.party-frames[data-hud-anchor-x="center"][data-hud-anchor-y="top"] .party-fold-roster {
  top: calc(100% - 5px);
  bottom: auto;
}

/* Buffs: the same body-and-runes identity shifts from closed to open aura while
   compact live chips expose urgent statuses. Totals are only overflow. */

.status-tray.hud-fold-surface {
  --hud-fold-reserve-gap: 6px;
  --status-tile-size: calc(40px * var(--ui-scale, 1));
  --hud-fold-expanded-width: calc(var(--hud-fold-shell-size) + 6px + 220px * var(--ui-scale));
  --hud-fold-expanded-height: auto;
  --hud-fold-expanded-min-height: var(--hud-fold-shell-size);
  max-width: none;
}

.status-tray.hud-fold-surface:not(.is-collapsed),
.status-tray.hud-fold-surface.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed {
  padding-left: var(--hud-fold-reserve-left);
  padding-right: var(--hud-fold-reserve-right);
}

.status-tray-fold-slot,
.action-bars-fold-slot {
  display: contents;
}

.status-tray .status-tray-content {
  box-sizing: border-box;
  padding-top: calc(
    var(--hud-fold-inset) +
    (var(--hud-fold-size) - var(--status-tile-size)) / 2
  );
}

/* Compact status telemetry sits beside the trigger in two readable lanes. */

.status-fold-group {
  position: absolute;
  left: calc(100% + 5px);
  z-index: 4;
  display: flex;
  gap: 2px;
  pointer-events: none;
}

.status-fold-group[data-status-fold-group="debuff"] { top: -1px; }

.status-fold-group[data-status-fold-group="buff"] { bottom: -1px; }

.status-fold-group[hidden] { display: none; }

.status-fold-preview {
  position: relative;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: max(18px, calc(18px * var(--ui-scale, 1)));
  height: max(18px, calc(18px * var(--ui-scale, 1)));
  border-radius: 3px;
  pointer-events: none;
}

.status-fold-preview[hidden] { display: none; }

.status-fold-preview .status-time {
  font-size: max(7px, calc(6px * var(--ui-scale, 1)));
  line-height: 1.2;
}

.status-fold-preview .status-stacks {
  min-width: 7px;
  padding: 0 1px;
  font-size: max(7px, calc(6px * var(--ui-scale, 1)));
}

.status-fold-overflow {
  position: relative;
  z-index: 5;
  align-self: center;
  min-width: 10px;
  height: 13px;
  box-sizing: border-box;
  padding: 1px 2px;
  border: 1px solid currentColor;
  border-radius: 999px;
  color: #fff0bd;
  background: #101517;
  font-size: max(7px, calc(6px * var(--ui-scale, 1)));
  font-weight: 900;
  line-height: 9px;
  text-align: center;
  text-shadow: 0 1px 1px #000;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.76);
}

.status-fold-overflow[hidden] { display: none; }

.status-tray[data-hud-anchor-x="right"] .status-fold-group {
  right: auto;
  left: auto;
  right: calc(100% + 5px);
  flex-direction: row-reverse;
}

.status-tray:not(.has-statuses):not(.hud-layout-ghost) {
  display: none;
}

/* Target: its own portrait, health, threat, tier and active cast survive. The fold
   control occupies the portrait slot in the full frame instead of floating above a
   second portrait. The live identity overlays that slot while expanded so model-backed
   targets and world artwork keep the same presentation path. */

.target-panel.unit-frame.hud-fold-surface {
  --hud-fold-expanded-width: min(78vw, calc(278px * var(--ui-scale)));
  --hud-fold-expanded-min-width: min(70vw, calc(232px * var(--ui-scale)));
  --hud-fold-expanded-height: auto;
  --hud-fold-expanded-min-height: max(var(--hud-fold-shell-size), calc(82px * var(--ui-scale)));
  filter: none;
}

/* The top-centre rail centres the whole target frame, not the portrait within
   it. Keep the identity core in the frame's leading slot while the full unit
   frame remains centred as one composition. */

.target-panel.unit-frame.hud-fold-surface[data-hud-anchor-x="center"] {
  --hud-fold-trigger-left: var(--hud-fold-inset);
  --hud-fold-trigger-right: auto;
  --hud-fold-origin-x: left;
  --hud-fold-shift-x: calc(-1 * var(--hud-fold-offset));
  --hud-fold-clip-right: 100%;
  --hud-fold-clip-left: 0;
}

.target-frame-content {
  display: flex;
  align-items: flex-start;
  box-sizing: border-box;
  width: 100%;
  min-height: var(--hud-fold-shell-size);
  padding-left: calc(
    var(--hud-fold-inset) + var(--hud-fold-size) + 8px * var(--ui-scale)
  );
}

.target-frame-content > .target-body {
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.76));
}

/* Placement, framing and the trigger hand-off are the shared portrait tile's own,
   in the fold sheet. What is left here belongs to a target and to nothing else:
   room for the tile beside a right-anchored frame, and rank, whose ring the tile
   drops because the trigger's edge under it carries rank instead. */

.target-panel[data-hud-anchor-x="right"] .target-frame-content {
  padding-right: calc(
    var(--hud-fold-inset) + var(--hud-fold-size) + 8px * var(--ui-scale)
  );
  padding-left: 0;
}

.target-panel > .target-portrait.elite,
.target-panel > .target-portrait.mvp {
  border: 0;
  box-shadow: none;
  animation: none;
}

/* Collapsed, the medallion edge carries rank — the one place rank is about the
   unit rather than about its health. An unranked target leaves the property unset
   and keeps the shared plate line. Expanded, the trigger wears the same accent
   edge and glow as every other open fold surface, so one open HUD widget never
   reads differently from the next. */

.target-panel .hud-fold-trigger[aria-expanded="false"]:not(:hover)::before {
  background: var(--target-tier-edge, color-mix(in srgb, var(--ui-line) 82%, transparent));
}

/* High contrast owns every trigger edge in the HUD; the rank tint is a decoration
   and must not be the one surface where the mode stops applying. */

body.high-contrast .target-panel .hud-fold-trigger::before {
  background: currentColor;
}

/* Matches the expanded tile's framing of a creature's world illustration, so
   folding the frame does not re-crop the art the player is looking at. */

.target-fold-portrait {
  object-fit: cover;
  object-position: 50% 24%;
}

.target-fold-hp,
.target-fold-threat {
  --hud-fold-meter-track: rgba(10, 16, 15, 0.9);
}

.target-fold-hp.is-unknown { --hud-fold-meter-paint: var(--muted); }

.target-fold-threat {
  --hud-fold-meter-paint: var(--target-threat-accent);
  --hud-fold-meter-track: rgba(35, 38, 42, 0.9);
}

.target-fold-threat.is-unknown { --hud-fold-meter-paint: var(--muted); }

.target-fold-cast {
  position: absolute;
  right: 6px;
  bottom: 4px;
  left: 6px;
  z-index: 4;
  height: 2px;
  background: transparent;
}

.target-fold-cast.is-active {
  background: var(--cast-strong);
  box-shadow: 0 0 4px color-mix(in srgb, var(--cast-strong) 54%, transparent);
}

/* Minimap: a live downsampled radar is the compact identity. The lens grows
   inward from the saved edge without moving the launcher. */

.minimap-frame.hud-fold-surface {
  --hud-fold-expanded-width: calc(152px * var(--ui-scale));
  --hud-fold-expanded-height: calc(152px * var(--ui-scale));
  overflow: visible;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.minimap-recenter {
  display: none;
  position: absolute;
  left: 0;
  bottom: calc(-1 * (var(--hud-touch-min, 44px) + 4px));
  width: var(--hud-touch-min, 44px);
  height: var(--hud-touch-min, 44px);
  padding: 0;
  border: 1px solid rgba(92, 104, 123, 0.58);
  border-radius: 8px;
  background: rgba(10,14,20,0.78);
  color: #e6e6e6;
  font-size: 11px;
  font-weight: 700;
  cursor: var(--cursor-action);
}

.minimap-content {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(152px * var(--ui-scale));
  height: calc(152px * var(--ui-scale));
  overflow: hidden;
  isolation: isolate;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--ui-line));
  border-radius: 50%;
  background: radial-gradient(circle at 40% 36%, #526b4d, #20372f 58%, #13221f);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28), 0 0 0 1px rgba(255, 255, 255, 0.035);
}

/* The radar is the fold's expanded size — the handheld profile shrinks that
   size, and a 152px disc in a 75px frame spills off the glass edge. */

.minimap-frame .minimap-content {
  inset: 0;
  width: var(--hud-fold-expanded-width);
  height: var(--hud-fold-expanded-height);
}

.minimap-frame[data-hud-anchor-y="bottom"] .minimap-content {
  top: auto;
  bottom: 0;
}

.minimap-fold-preview {
  position: absolute;
  inset: 4px;
  z-index: 2;
  width: calc(100% - 8px);
  height: calc(100% - 8px);
  clip-path: polygon(5px 0, calc(100% - 5px) 0, 100% 5px, 100% calc(100% - 5px), calc(100% - 5px) 100%, 5px 100%, 0 calc(100% - 5px), 0 5px);
  pointer-events: none;
}

.minimap-fold-readout {
  position: absolute;
  top: 50%;
  right: calc(100% - 1px);
  z-index: 0;
  display: grid;
  width: max(116px, calc(108px * var(--ui-scale, 1)));
  box-sizing: border-box;
  gap: 2px;
  padding: 4px 8px 5px 18px;
  translate: 0 -50%;
  color: var(--text);
  background: linear-gradient(to left, rgba(7, 14, 16, 0.88), rgba(7, 14, 16, 0.58) 68%, transparent);
  text-align: right;
  text-shadow: 0 1px 2px #000;
  pointer-events: none;
}

.minimap-fold-readout::after {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 28%;
  height: 1px;
  content: '';
  background: linear-gradient(to left, color-mix(in srgb, var(--accent) 74%, #fff), transparent);
}

.minimap-fold-map-name {
  overflow: hidden;
  font-size: max(9px, calc(9px * var(--ui-scale, 1)));
  font-weight: 800;
  line-height: 1.1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.minimap-fold-facts {
  display: flex;
  justify-content: flex-end;
  gap: 5px;
  color: var(--muted);
  font-size: max(9px, calc(7px * var(--ui-scale, 1)));
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.minimap-fold-risk { color: #a8ceb1; }

.minimap-fold-risk.is-danger { color: #ff8e76; }

.minimap-frame[data-hud-anchor-x="left"] .minimap-fold-readout {
  right: auto;
  left: calc(100% - 1px);
  padding-right: 18px;
  padding-left: 8px;
  background: linear-gradient(to right, rgba(7, 14, 16, 0.88), rgba(7, 14, 16, 0.58) 68%, transparent);
  text-align: left;
}

.minimap-frame[data-hud-anchor-x="left"] .minimap-fold-readout::after {
  right: 28%;
  left: 0;
  background: linear-gradient(to right, color-mix(in srgb, var(--accent) 74%, #fff), transparent);
}

.minimap-frame[data-hud-anchor-x="left"] .minimap-fold-facts {
  justify-content: flex-start;
}

.minimap-fold-heading {
  position: absolute;
  right: 50%;
  bottom: 1px;
  z-index: 4;
  min-width: 18px;
  padding: 1px 3px;
  translate: 50% 0;
  border: 1px solid rgba(226, 235, 226, 0.24);
  border-radius: 999px;
  color: #f8f1d6;
  background: rgba(6, 13, 14, 0.88);
  font-size: max(9px, calc(7px * var(--ui-scale, 1)));
  font-weight: 800;
  line-height: 1;
  text-align: center;
  letter-spacing: 0.04em;
  text-shadow: 0 1px 2px #000;
  pointer-events: none;
}

.minimap-fold-heading[hidden] { display: none; }

[data-minimap-control-slot="radar-lens"][aria-expanded="true"] .minimap-fold-heading {
  opacity: 0;
  visibility: hidden;
}

.minimap-frame .hud-fold-icon--compact.minimap-direction-icon,
.minimap-frame .hud-fold-icon--expanded.minimap-direction-icon {
  opacity: 0;
  transform: none;
}

.minimap-frame
  [data-minimap-control-slot="radar-lens"][aria-expanded="true"]
  .hud-fold-icon--expanded.minimap-direction-icon {
  opacity: 1;
  transform: rotate(var(--minimap-direction-rotation, 0rad));
}

.minimap-expanded-readout {
  position: absolute;
  right: 18px;
  bottom: calc(var(--hud-fold-reserve-bottom) + 18px);
  left: 18px;
  z-index: 4;
  display: grid;
  gap: 2px;
  padding: 3px 4px;
  border: 1px solid rgba(228, 215, 172, 0.2);
  border-radius: 4px;
  color: var(--text);
  background: rgba(6, 12, 13, 0.76);
  text-align: center;
  text-shadow: 0 1px 2px #000;
  box-shadow: 0 2px 7px rgba(0, 0, 0, 0.34);
  pointer-events: none;
}

.minimap-expanded-map-name {
  overflow: hidden;
  font-size: clamp(8px, calc(8px * var(--ui-scale, 1)), 9px);
  font-weight: 800;
  line-height: 1.1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.minimap-expanded-facts {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: #d9e1de;
  font-size: clamp(7px, calc(7px * var(--ui-scale, 1)), 8px);
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}

.minimap-expanded-risk {
  padding: 1px 2px;
  border-radius: 999px;
  color: #bfe4c6;
  background: rgba(39, 88, 55, 0.64);
}

.minimap-expanded-risk.is-danger {
  color: #ffe0d9;
  background: rgba(145, 48, 39, 0.76);
}

/* Skills/action bar: stable Skills art owns the identity while readiness remains
   secondary. Keeping the full surface geometry in both states prevents its fixed
   bottom anchor from dragging content downward while the shared reveal runs. */

.action-bars.hud-fold-surface {
  --hud-fold-reserve-gap: 5px;
  /* The bars themselves, plus the Recall column standing off the left end. */
  --hud-fold-expanded-width: calc(356px * var(--ui-scale) + var(--action-bars-recall-column));
  --hud-fold-expanded-min-width: var(--hud-fold-expanded-width);
  --hud-fold-expanded-height: auto;
  --hud-fold-expanded-min-height: var(--hud-fold-shell-size);
  width: var(--hud-fold-expanded-width);
  min-width: var(--hud-fold-expanded-min-width);
  height: var(--hud-fold-expanded-height);
  min-height: var(--hud-fold-expanded-min-height);
  padding-top: var(--hud-fold-reserve-top);
  padding-bottom: var(--hud-fold-reserve-bottom);
}

/* The Recall column widens the surface to the left, so the surface centre is no
   longer the bars' centre. The trigger belongs to the bars, and the fold has to
   converge where the trigger is: both are pushed back over the bar stack by half
   that column. */

.action-bars.hud-fold-surface.hud-fold-anchor-x-center:not([data-hud-anchor-x]),
.action-bars.hud-fold-surface[data-hud-anchor-x="center"] {
  --hud-fold-trigger-left: calc(
    50% - var(--hud-fold-size) / 2 + var(--action-bars-recall-column) / 2
  );
  --hud-fold-origin-x: calc(50% + var(--action-bars-recall-column) / 2);
  --hud-fold-clip-left: calc(50% + var(--action-bars-recall-column) / 2);
  --hud-fold-clip-right: calc(50% - var(--action-bars-recall-column) / 2);
}

.action-bars .hud-fold-surface-trigger {
  pointer-events: auto;
}

.action-bars-fold-icon {
  object-fit: contain;
}

.action-bars-fold-readiness {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}

.action-bars-fold-gcd {
  position: absolute;
  inset: 4px;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 1px;
  color: transparent;
  font-size: 0;
  font-weight: 900;
  line-height: 1;
  text-shadow: none;
  pointer-events: none;
}

.action-bars-fold-gcd::before {
  position: absolute;
  inset: 0;
  content: '';
  clip-path: polygon(6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px);
  background: conic-gradient(
    from -90deg,
    var(--cast-strong) 0 var(--hud-fold-gcd-angle),
    rgba(16, 22, 25, 0.26) var(--hud-fold-gcd-angle) 360deg
  );
  -webkit-mask: radial-gradient(circle, transparent 62%, #000 65%);
  mask: radial-gradient(circle, transparent 62%, #000 65%);
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--cast-strong) 48%, transparent));
}

.action-bars-fold-gcd[hidden],
.action-bars-fold-cooldowns[hidden],
.action-bars-fold-cooldown[hidden] { display: none; }

/* Recharging skills ride their own rail beside the Skills emblem rather than
   sitting on top of it: a countdown only helps if the art next to it still names
   the skill, and icons crushed onto the emblem face read as bare numbers. The
   rail reveals inward from the surface's docked edges, like the party roster. */

.action-bars-fold-cooldowns {
  position: absolute;
  bottom: calc(100% + 5px);
  left: 50%;
  z-index: 4;
  display: flex;
  gap: max(2px, calc(3px * var(--ui-scale, 1)));
  translate: -50% 0;
  pointer-events: none;
}

.action-bars[data-hud-anchor-x="left"] .action-bars-fold-cooldowns {
  left: 0;
  translate: none;
}

.action-bars[data-hud-anchor-x="right"] .action-bars-fold-cooldowns {
  right: 0;
  left: auto;
  flex-direction: row-reverse;
  translate: none;
}

.action-bars[data-hud-anchor-y="top"] .action-bars-fold-cooldowns {
  top: calc(100% + 5px);
  bottom: auto;
}

.action-bars-fold-cooldown {
  position: relative;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: max(22px, calc(24px * var(--ui-scale, 1)));
  height: max(22px, calc(24px * var(--ui-scale, 1)));
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--cast-strong) 62%, var(--ui-line));
  border-radius: 3px;
  background: #0a0f12;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.72);
  pointer-events: none;
}

.action-bars-fold-cooldown-icon,
.action-bars-fold-cooldown-shade {
  position: absolute;
  inset: 0;
  width: 100%;
}

/* The art keeps its full read so the chip names its skill; the shade over it
   depletes from the top like the hotbar's own cooldown, so the same skill sweeps
   the same way whether the bars are open or folded. */

.action-bars-fold-cooldown-icon {
  height: 100%;
  object-fit: cover;
}

.action-bars-fold-cooldown-shade {
  top: auto;
  background: rgba(5, 8, 12, 0.62);
}

/* The countdown rides a scrim on the base edge, the way art-faced status tiles
   carry theirs, so the seconds never sit across the face of the art they name. */

.action-bars-fold-cooldown-time {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  color: #fff;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.82));
  font-size: max(9px, calc(9px * var(--ui-scale, 1)));
  font-weight: 900;
  line-height: 1.5;
  text-align: center;
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
}

/* ===== Guild window (guild-window.ts / guild-window-views.ts own these gw-* visuals) =====
   Crest header, info stat tiles, a flat guild-skill card list, and the rank editor,
   themed to match the dark + gold chrome the rest of the social windows use. */

.gw-guildhead { display: flex; align-items: center; gap: 12px; padding: 2px 2px 10px; margin-bottom: 4px; border-bottom: 1px solid var(--ui-control-line); }

.gw-crest {
  width: 48px; height: 48px; flex: none; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 800; color: var(--accent);
  background: radial-gradient(circle at 30% 25%, color-mix(in srgb, var(--gold) 30%, transparent), rgba(20,26,36,0.92));
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05), 0 2px 8px rgba(0,0,0,0.45);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
}

.gw-crest { position: relative; overflow: hidden; }

.gw-crest-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

.gw-crest-img--fallback { object-fit: contain; padding: 6px; }

.gw-crest.editable { cursor: var(--cursor-action); }

.gw-crest.editable:hover { border-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05), 0 0 10px color-mix(in srgb, var(--gold) 35%, transparent); }

.gw-crest-file { display: none; }

/* Always-visible camera badge so the master immediately sees the crest is
   editable (click anywhere on the crest to change the emblem). */

.gw-crest-edit {
  position: absolute; right: -2px; bottom: -2px; z-index: 2; pointer-events: none;
  min-width: 22px; height: 16px; padding: 0 4px; line-height: 16px; font-size: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: #1a1206; border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0,0,0,0.6); opacity: 0.95; transition: transform 0.12s ease;
}

.gw-crest.editable:hover .gw-crest-edit { transform: scale(1.12); }

.gw-crest.uploading { opacity: 0.55; }

.gw-crest.uploading::after {
  content: ''; position: absolute; left: 50%; top: 50%; z-index: 2;
  width: 18px; height: 18px; margin: -11px 0 0 -11px;
  border: 2px solid color-mix(in srgb, var(--gold) 25%, transparent); border-top-color: var(--accent); border-radius: 50%;
  background: rgba(20,26,36,0.6); animation: ui-spin .7s linear infinite;
}

@media (prefers-reduced-motion: reduce) { .gw-crest.uploading::after { animation: none; } }

body.reduce-motion .gw-crest.uploading::after { animation: none; }

.gw-guildhead-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }

.gw-guildname { font-size: 16px; font-weight: 700; color: var(--text); letter-spacing: 0.3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.gw-guildsub { font-size: 11px; color: var(--muted); }

/* Mouseover unit tooltip (ElvUI-style): floating card with the hovered player's
   name + class + guild + emblem. Pointer-transparent so it never eats hover. */

.unit-tooltip {
  position: fixed; /* Popover band, with the item tooltip and the context menus: a card that
     hangs off the world must not paint inside the window stack. */
  z-index: 210; display: none; gap: 9px; align-items: center;
  max-width: 260px; padding: 8px 11px; pointer-events: none;
  background: linear-gradient(180deg, rgba(22,28,38,0.97), rgba(15,20,28,0.97));
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent); border-radius: 9px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.55);
}

.unit-tooltip .ut-crest {
  width: 34px; height: 34px; flex: none; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 800; color: var(--accent);
  background: radial-gradient(circle at 30% 25%, color-mix(in srgb, var(--gold) 28%, transparent), rgba(20,26,36,0.92)) center/cover;
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
}

.unit-tooltip .ut-crest.has-img { color: transparent; }

.unit-tooltip .ut-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }

.unit-tooltip .ut-name { font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.unit-tooltip .ut-job { font-size: 11px; color: var(--muted); }

.unit-tooltip .ut-guild { font-size: 11px; color: var(--gold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Guild-master badge in the Info header. */

.gw-role-chip {
  margin-left: auto; flex: none; align-self: flex-start;
  padding: 3px 8px; border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent); border-radius: 999px;
  color: var(--gold-ink); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}

/* Info facts share one ledger strip instead of three nested cards. */

.gw-statgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  margin-bottom: 2px;
  border-block: 1px solid var(--ui-line-soft);
}

.gw-stattile {
  display: flex; flex-direction: column; gap: calc(2px * var(--ui-scale)); min-width: 0;
  padding: calc(7px * var(--ui-scale)) calc(9px * var(--ui-scale)); border-inline-end: calc(1px * var(--ui-scale)) solid var(--ui-line-soft);
}

.gw-stattile:last-child { border-inline-end: 0; }

.gw-stattile:first-child { padding-inline-start: calc(1px * var(--ui-scale)); }

.gw-stattile:last-child { padding-inline-end: calc(1px * var(--ui-scale)); }

.window.win-guild .gw-expcard,
.window.win-guild .gw-castles,
.window.win-guild .guild-notice {
  border: 0;
  border-radius: 0;
  background: transparent;
}

.window.win-guild .gw-expcard {
  margin: 0;
  padding: 8px 1px;
  border-block-end: 1px solid var(--ui-line-soft);
}

.window.win-guild .gw-castles {
  border-inline-start: 2px solid color-mix(in srgb, var(--accent) 42%, transparent);
  padding: 5px 8px;
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

.window.win-guild .guild-notice {
  border-inline-start: 2px solid color-mix(in srgb, var(--accent) 42%, transparent);
  padding: 6px 8px;
  color: color-mix(in srgb, var(--text) 82%, var(--muted));
  line-height: 1.42;
}

.gw-stattile-label { color: var(--muted); font-size: calc(10px * var(--ui-scale)); text-transform: uppercase; letter-spacing: 0.03em; }

.gw-stattile-val { color: var(--text); font-size: calc(13px * var(--ui-scale)); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.gw-expcard { margin-bottom: 2px; }

/* Ally/enemy relation chips. The ink is the semantic relation colour, resolved
   at paint time by the guild window (Player Relation Presentation) — the chip
   only derives its border and text from that token. */

.gw-relations { display: flex; flex-wrap: wrap; gap: 5px; }

.gw-relation {
  padding: calc(3px * var(--ui-scale)) calc(8px * var(--ui-scale)); border: calc(1px * var(--ui-scale)) solid #344456; border-radius: 999px;
  font-size: calc(11px * var(--ui-scale)); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}

.gw-relation.is-ally,
.gw-relation.is-enemy {
  border-color: color-mix(in srgb, var(--gw-relation-ink) 42%, transparent);
  color: color-mix(in srgb, var(--gw-relation-ink) 55%, white);
}

/* ===== Guild skills: flat card list (mirrors the abilities catalog) ===== */

.gw-points { margin-left: auto; padding: 2px 8px; border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent); border-radius: 999px; color: var(--gold-label); font-size: 11px; font-weight: 700; }

.gw-skills { gap: 6px; }

.gw-skill-card {
  display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; gap: 9px; align-items: center;
  padding: 7px 9px; border: 1px solid #344052; border-left: 3px solid #667080; border-radius: 9px;
  background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(0,0,0,0.1)), var(--panel);
}

.gw-skill-icon { width: 38px; height: 38px; border-radius: 8px; object-fit: cover; box-shadow: 0 0 0 1px rgba(255,255,255,0.08), 0 4px 10px rgba(0,0,0,0.3); }

.gw-skill-copy { min-width: 0; display: flex; flex-direction: column; gap: 3px; }

.gw-skill-name { overflow: hidden; color: var(--text); font-size: 13px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }

.gw-skill-facts { display: flex; flex-wrap: wrap; gap: 4px; }

.gw-skill-facts span { padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,0.045); color: var(--muted); font-size: 10px; }

.gw-skill-facts .gw-skill-lv { border: 1px solid color-mix(in srgb, var(--gold) 42%, transparent); background: color-mix(in srgb, var(--gold) 8%, transparent); color: var(--gold-label); }

.gw-skill-upgrade {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  min-height: 30px; padding: 5px 10px; border: 1px solid #659f5b; border-radius: 7px;
  background: #172719; color: #d6ffd1; font-size: 10px; font-weight: 800; letter-spacing: 0.03em;
  text-transform: uppercase; cursor: var(--cursor-action); transition: transform 0.12s, border-color 0.12s, background-color 0.12s;
}

.gw-skill-upgrade img { width: 18px; height: 18px; object-fit: contain; }

.gw-skill-upgrade:hover { transform: translateY(-1px); border-color: #7ab56f; background: #1e3222; }

/* ===== Guild ranks: read view + master editor ===== */

.gw-ranks { display: flex; flex-direction: column; gap: 6px; }

.gw-rank-card { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border: 1px solid var(--ui-line-soft); border-radius: 8px; background: rgba(255,255,255,0.025); }

.gw-rank-card.is-editing { border-color: color-mix(in srgb, var(--gold) 35%, transparent); background: color-mix(in srgb, var(--gold) 5%, transparent); }

.gw-rank-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }

.gw-rank-name { color: var(--text); font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.gw-rank-seats { flex: none; color: var(--muted); font-size: 11px; }

.gw-rank-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }

.gw-rank-perms { display: flex; flex-wrap: wrap; gap: 4px; }

.gw-rank-tax { flex: none; color: var(--muted); font-size: 11px; }

.gw-rank-name-input { min-width: 0; padding: 5px 8px; border: 1px solid var(--ui-control-line); border-radius: 6px; background: var(--panel-2); color: var(--text); font-size: 13px; font-weight: 600; }

.gw-perm-toggles { display: flex; flex-wrap: wrap; gap: 5px; }

.gw-perm-toggle {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px;
  border: 1px solid #344456; border-radius: 999px; color: var(--muted); font-size: 11px; cursor: var(--cursor-action);
  transition: border-color 0.12s, color 0.12s, background-color 0.12s;
}

.gw-perm-toggle input { margin: 0; accent-color: var(--accent); }

.gw-perm-toggle.is-on { border-color: color-mix(in srgb, var(--gold) 50%, transparent); color: var(--gold-label); background: color-mix(in srgb, var(--gold) 9%, transparent); }

.gw-tax-field { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 11px; }

.gw-tax-field input { width: 58px; padding: 4px 6px; border: 1px solid var(--ui-control-line); border-radius: 6px; background: var(--panel-2); color: var(--text); font-size: 12px; }

.gw-tax-suffix { color: var(--muted); }

.gw-rank-save { margin-top: 2px; }

/* Inline member management (master): rank picker + expel, no hidden right-click. */

.guild-member-manage { display: flex; align-items: center; gap: 5px; }

.guild-rank-pick ron-select { max-width: 108px; }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* Arena status overlay extracted from game-screen composition. */

.screen-combat-overlays {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
}

.screen-combat-overlays__lockout,
.screen-combat-overlays__incoming {
  position: absolute;
  left: 50%;
  z-index: 41;
  width: min(calc(220px * var(--ui-scale)), calc(100vw - 24px - var(--safe-left) - var(--safe-right)));
  padding: calc(7px * var(--ui-scale)) calc(18px * var(--ui-scale));
  border: 0;
  background:
    linear-gradient(90deg, transparent, rgba(7, 12, 14, .76) 18%, rgba(7, 12, 14, .76) 82%, transparent);
  box-shadow: inset 0 1px rgba(255, 224, 102, .52);
  filter: drop-shadow(0 calc(6px * var(--ui-scale)) calc(12px * var(--ui-scale)) rgba(0, 0, 0, .36));
  text-align: center;
  transform: translateX(-50%);
}

.screen-combat-overlays__incoming {
  width: min(calc(196px * var(--ui-scale)), calc(100vw - 24px - var(--safe-left) - var(--safe-right)));
  padding-block: calc(5px * var(--ui-scale));
  box-shadow: inset 0 1px rgba(255, 77, 77, .52);
}

.screen-combat-overlays__lockout-label {
  color: var(--gold-value);
  font: 850 calc(18px * var(--ui-scale))/1.1 "Segoe UI", sans-serif;
  letter-spacing: .11em;
  text-shadow: 0 2px 8px rgba(0, 0, 0, .9);
}

.screen-combat-overlays__incoming-label {
  letter-spacing: .055em;
  text-transform: uppercase;
}

.screen-combat-overlays__lockout-track,
.screen-combat-overlays__incoming-track {
  height: calc(5px * var(--ui-scale));
  margin: calc(6px * var(--ui-scale)) auto 0;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(0, 0, 0, .62);
}

.screen-combat-overlays__lockout-track { width: min(calc(196px * var(--ui-scale)), 100%); }

.screen-combat-overlays__incoming-track { width: min(calc(174px * var(--ui-scale)), 100%); }

.screen-combat-overlays__announcement {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 520px) {
  /* Reclaim the unused desktop header gutter so the target frame cannot cover
     the off-target incoming-cast lane on short phone viewports. */
  .screen-combat-overlays__incoming { top: calc(126px * var(--ui-scale)) !important; }
  .screen-combat-overlays__lockout { top: calc(164px * var(--ui-scale)) !important; }
}

.reduce-flashing .screen-combat-overlays__lockout,
.reduce-flashing .screen-combat-overlays__incoming,
body.reduce-flashing .screen-combat-overlays__lockout,
body.reduce-flashing .screen-combat-overlays__incoming { box-shadow: 0 5px 12px rgba(0, 0, 0, .38); }

/* One strip for the whole life of a skill press: the aim, the windup, and the
   release. It is a compact HUD bit like the target frame and the action bars, so
   it wears the same shared chrome, and its gauge is the vitals gauge built once
   more: one well, one lit fill, the label and the clock riding inside it. The
   focused target's `.target-cast` row is the same reading at nameplate size, and
   both take their tier hue from the one `--cast-*` table in the shell stylesheet.

   The fixed anchor below is the un-railed fallback; docked, the rail places it. */

.screen-skill-flow {
  position: fixed;
  top: max(
    calc(38% + var(--safe-top) / 2),
    calc(200px * var(--ui-scale) + var(--safe-top))
  );
  left: 50%;
  transform: translateX(-50%);
  width: min(calc(360px * var(--ui-scale)), calc(100vw - 32px - var(--safe-left) - var(--safe-right)));
  z-index: 39;
  pointer-events: none;
  --cast-accent: var(--cast-strong);
  --cast-accent-bright: color-mix(in srgb, var(--cast-accent) 42%, #fff);
}

.screen-skill-flow[hidden] { display: none; }

.hud-layout-editing .screen-skill-flow {
  pointer-events: auto;
}

/* The card is on screen only while a death is unresolved, so the layout editor is
   the one place it has to appear on demand: otherwise the surface a player most
   wants to move is the one surface they can never reach. */

.hud-layout-editing .lost-gear {
  display: block;
  min-width: calc(200px * var(--ui-scale));
  min-height: calc(40px * var(--ui-scale));
  pointer-events: auto;
}

.skill-flow--routine { --cast-accent: var(--cast-routine); }

.skill-flow--strong { --cast-accent: var(--cast-strong); }

.skill-flow--signature { --cast-accent: var(--cast-signature); }

.skill-flow--critical { --cast-accent: var(--cast-critical); }

.skill-flow-content {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(7px * var(--ui-scale));
  overflow: hidden;
  border: 0;
  background: linear-gradient(90deg, transparent, rgba(7, 12, 14, .78) 13%, rgba(7, 12, 14, .78) 87%, transparent);
  box-shadow: inset 0 1px color-mix(in srgb, var(--cast-accent) 54%, transparent);
  filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .26));
  --feedback-enter-x: 0px;
  --feedback-enter-y: calc(-7px * var(--ui-scale));
  animation: hud-surface-reveal var(--hud-fold-duration, 180ms) var(--hud-fold-ease, cubic-bezier(.2, .78, .24, 1)) both;
}

.screen-skill-flow[data-hud-anchor-x="left"] .skill-flow-content {
  background: linear-gradient(90deg, rgba(7, 12, 14, .84), rgba(7, 12, 14, .48) 78%, transparent);
  box-shadow: inset calc(2px * var(--ui-scale)) 0 var(--cast-accent);
  --feedback-enter-x: calc(-7px * var(--ui-scale));
}

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-content {
  grid-template-columns: auto minmax(0, 1fr) auto;
  background: linear-gradient(270deg, rgba(7, 12, 14, .84), rgba(7, 12, 14, .48) 78%, transparent);
  box-shadow: inset calc(-2px * var(--ui-scale)) 0 var(--cast-accent);
  --feedback-enter-x: calc(7px * var(--ui-scale));
}

/* Every child the strip can render is placed by hand when the rail is mirrored:
   an unplaced one auto-flows into the first free track and takes the elastic
   column the name needs. */

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-icon-frame { grid-column: 3; }

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-track { grid-column: 2; grid-row: 1; }

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-state { grid-column: 1; grid-row: 1; }

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-body { grid-column: 2; grid-row: 1; }

.screen-skill-flow[data-hud-anchor-x="right"] .skill-flow-actions { grid-column: 1; grid-row: 1; }

.screen-skill-flow[data-hud-anchor-y="bottom"] .skill-flow-content {
  --feedback-enter-y: calc(7px * var(--ui-scale));
}

@keyframes hud-surface-reveal {
  from {
    opacity: 0;
    transform: translate(var(--feedback-enter-x), var(--feedback-enter-y)) scale(.985);
  }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}

/* The skill's own tile, built like a hotbar slot — the player already reads that
   shape as "the skill you pressed". */

.skill-flow-icon-frame {
  position: relative;
  width: max(30px, calc(34px * var(--ui-scale)));
  height: max(30px, calc(34px * var(--ui-scale)));
  padding: calc(2px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--cast-accent) 52%, var(--ui-control-line));
  border-radius: calc(9px * var(--ui-scale));
  background: linear-gradient(180deg, rgba(22, 28, 38, .92), rgba(10, 14, 20, .92));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 2px 6px rgba(0, 0, 0, .4);
}

.skill-flow-icon {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: calc(6px * var(--ui-scale));
  object-fit: cover;
}

/* The player's own cast ribbon is the HUD gauge, at ribbon height: it hangs a few
   rows below the vitals rail and must not read as a different material. A cast is
   a clock rather than a resource, so only the fill's language is its own — the
   tier accent, not a health band. */

.skill-flow-track {
  --gauge-h: max(26px, calc(30px * var(--ui-scale)));
  --gauge-ring: color-mix(in srgb, var(--cast-accent) 52%, rgba(0, 0, 0, .6));
}

.skill-flow-fill {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 1;
  height: 100%;
  width: 0%;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--cast-accent) 74%, #fff) 0%,
    var(--cast-accent) 48%,
    color-mix(in srgb, var(--cast-accent) 58%, #000) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .34), inset 0 -3px 6px rgba(0, 0, 0, .28);
}

/* Name and clock ride inside the gauge, exactly where the target cast row and
   the HP gauges put theirs. */

.skill-flow-track .skill-flow-name {
  position: absolute;
  z-index: 4;
  top: 50%;
  left: calc(8px * var(--ui-scale));
  right: calc(46px * var(--ui-scale));
  overflow: hidden;
  color: #fff;
  font-size: max(11px, calc(12px * var(--ui-scale)));
  font-weight: 800;
  text-overflow: ellipsis;
  text-shadow: var(--hud-vitals-text-shadow);
  transform: translateY(-50%);
  white-space: nowrap;
}

.skill-flow-countdown {
  position: absolute;
  z-index: 4;
  top: 50%;
  right: calc(8px * var(--ui-scale));
  color: #fff;
  font: 800 max(10px, calc(11px * var(--ui-scale)))/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  text-shadow: var(--hud-vitals-text-shadow);
  transform: translateY(-50%);
}

/* Same pill the target cast row uses for its CC / lethal / targets-you marks.
   The cap holds the longest state word whole — a clipped "INTERRUPT…" is the one
   thing on this surface a player cannot afford to guess at. */

.skill-flow-state {
  max-width: max(98px, calc(92px * var(--ui-scale)));
  padding: 0 calc(6px * var(--ui-scale));
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--cast-accent) 60%, rgba(255, 255, 255, .24));
  border-radius: 999px;
  background: rgba(8, 11, 16, .84);
  color: var(--cast-accent-bright);
  font-size: max(9px, calc(7px * var(--ui-scale)));
  font-weight: 950;
  letter-spacing: .05em;
  line-height: 1.45;
  text-align: center;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Phase outranks tier — these follow the tier rules above on purpose. */

.skill-flow--interrupted { --cast-accent: var(--danger); }

.skill-flow--completed { --cast-accent: var(--good); }

.skill-flow--pushback .skill-flow-track { --gauge-ring: var(--warn); }

/* Same reason as the target's cast row: the tier is the warning, so it survives
   the shared high-contrast ring. */

body.high-contrast .skill-flow-track {
  --gauge-ring: color-mix(in srgb, var(--cast-accent) 82%, #fff);
  --gauge-ring-w: 2px;
}

body.high-contrast .skill-flow--pushback .skill-flow-track { --gauge-ring: var(--warn); }

@media (max-width: 720px) {
  .screen-skill-flow {
    top: max(
      calc(34% + var(--safe-top) / 2),
      calc(168px * var(--ui-scale) + var(--safe-top))
    );
  }
  .skill-flow-icon-frame { width: 30px; height: 30px; }
}

@media (max-width: 520px) {
  .screen-skill-flow {
    top: calc(148px * var(--ui-scale) + var(--safe-top));
  }
}

/* Narrow: the aim beat's action group carries the self-cast chord as well as the
   cancel, so it drops to its own row rather than squeezing the skill name to
   nothing. The windup never does — its actions are the cancel and at most the
   committed mark, and a row under the gauge would cost more height than the
   gauge itself while the one thing the beat exists to show is the clock. */

@media (max-width: 700px) {
  .skill-flow--aiming .skill-flow-content {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .skill-flow--aiming .skill-flow-actions {
    grid-column: 1 / -1;
    justify-content: flex-end;
  }
}

@media (prefers-reduced-motion: reduce) {
  .skill-flow-fill { transition: none; }
  .skill-flow-content { animation: none; }
}

.reduce-motion .skill-flow-fill { transition: none; }

.reduce-motion .skill-flow-content,
body.reduce-motion .skill-flow-content { animation: none; }

/* Aim beat. The same row the windup uses, with the skill's instruction where the
   gauge will be — so arming and casting read as one object changing, not two
   surfaces trading places. One accent per aim says what the click will do before
   the words are read. */

.skill-flow--aim-attack { --cast-accent: var(--danger); }

.skill-flow--aim-support { --cast-accent: var(--good); }

.skill-flow--aim-ground { --cast-accent: var(--accent); }

.skill-flow-body {
  display: grid;
  min-width: 0;
  line-height: 1.2;
}

.skill-flow-kicker {
  color: var(--cast-accent);
  font-size: calc(8px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.skill-flow-body .skill-flow-name {
  overflow: hidden;
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The skill name ellipsises; the instruction wraps. Clipping the sentence that
   says what to click would cost the one thing the aim beat exists to say. */

.skill-flow-instruction {
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
}

.skill-flow-actions {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  pointer-events: auto;
}

.skill-flow-actions [hidden] { display: none; }

/* A key chord the player reads, not a control they click: the self-cast press is
   the same hotbar key under Alt, so the strip states it and stays out of the way. */

.skill-flow-self {
  display: inline-flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  color: var(--cast-accent);
  font-size: calc(9px * var(--ui-scale));
  white-space: nowrap;
}

.skill-flow-self[hidden] { display: none; }

.skill-flow-cancel {
  display: inline-flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  white-space: nowrap;
  padding: 0;
  border: none;
  background: transparent;
  cursor: var(--cursor-action);
  pointer-events: auto;
}

.skill-flow-self kbd,
.skill-flow-cancel kbd {
  min-width: calc(22px * var(--ui-scale));
  padding: calc(2px * var(--ui-scale)) calc(4px * var(--ui-scale));
  border: 1px solid var(--border);
  border-bottom-color: color-mix(in srgb, var(--border) 65%, #000);
  border-radius: calc(3px * var(--ui-scale));
  color: var(--text);
  background: color-mix(in srgb, var(--panel-2, #201c15) 92%, #000);
  font: inherit;
  font-weight: 800;
  text-align: center;
}

/* The gauge is the only border high contrast may thicken: the surrounding chrome
   is a 9-slice whose border-width seats the content on the frame's rail, so
   overriding it tears the art off the panel edge. */

body.high-contrast .skill-flow-track { border-width: 2px; }

body.high-contrast .skill-flow-state { border-color: #fff; color: #fff; }

body.high-contrast .skill-flow-name,
body.high-contrast .skill-flow-countdown,
body.high-contrast .skill-flow-kicker,
body.high-contrast .skill-flow-instruction { color: #fff; }

/* Screen interaction prompts extracted from game-screen composition. They stack
   in the bottom-centre prompt column, which owns the placement and the gap; each
   strip states only its own look. */

.loot-prompt {
  max-width: 100%;
  pointer-events: auto;
  padding: 5px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 700;
  background: rgba(10,14,20,0.85);
  border: 1px solid var(--ui-line-soft);
  text-shadow: 0 1px 2px #000;
  cursor: var(--cursor-action);
}

.weapon-swap-prompt {
  max-width: 100%;
  pointer-events: auto;
  padding: 6px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 700;
  cursor: var(--cursor-action);
  background: rgba(20,28,38,0.95);
  border: 1px solid var(--gold);
  color: var(--gold-value);
  text-shadow: 0 1px 2px #000;
}

.weapon-swap-prompt__icon {
  width: 24px;
  height: 24px;
  object-fit: contain;
  vertical-align: middle;
  margin-right: 6px;
}

/* Death card — the modal window shown while the character is dead.

   It speaks two dialects the game already has, in the two places each belongs.
   The scrim and the headline are the critical-interruption language every screen
   modal uses: the danger wash, the display face, the tone hairline. Everything
   under the header is the functional-window language: one frame, labelled bands,
   the shared gauge, the shared controls. Nothing here is a material of its own. */

.death-overlay {
  position: fixed;
  inset: 0;
  /* The card is a full-viewport pointer trap, so it must stay UNDER every
     recovery and system surface a dead player still needs: the warp cover and
     map banner, the ESC menu and settings, party invites, the help overlay, and
     the reconnect / terminal-kick overlays. Those set their z-index inline from
     TypeScript, so only the two CSS-side neighbours are test-guarded; raising
     this above them strands a player who dies and then disconnects. */
  z-index: 40;
  display: none;
  /* The scrim scrolls, not the card: a card taller than the viewport (large
     --ui-scale on a short window) would otherwise centre-overflow and clip its
     own headline off the top of a fixed container, out of reach. */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: max(calc(24px * var(--ui-scale)), var(--safe-top))
    max(calc(24px * var(--ui-scale)), var(--safe-right))
    max(calc(24px * var(--ui-scale)), var(--safe-bottom))
    max(calc(24px * var(--ui-scale)), var(--safe-left));
  color: var(--text);
  /* The danger tone every surface on the card derives from, so the header rule,
     the band edges, the killer line and the loss strip cannot drift apart. The
     first three re-point the shared frame and header variables at that tone; the
     frame itself is inherited whole. */
  --death-tone: var(--danger);
  --death-ink: color-mix(in srgb, var(--danger) 38%, #f7e6e1);
  --death-band-fill: color-mix(in srgb, var(--danger) 8%, var(--panel));
  --death-band-line: color-mix(in srgb, var(--danger) 18%, var(--grid-line));
  --ui-frame-glow: color-mix(in srgb, var(--death-tone) 12%, transparent);
  --ui-tick-fill: linear-gradient(180deg, #f0b3a4, var(--death-tone));
  --ui-tick-glow: color-mix(in srgb, var(--death-tone) 45%, transparent);
  --ui-head-rule-lead: color-mix(in srgb, var(--death-tone) 62%, transparent);
  /* The danger tone a critical screen modal washes the screen with, without its
     backdrop blur: this scrim covers the live scene, and a full-viewport filter
     would blur the whole of it every composited frame. */
  background:
    radial-gradient(ellipse at 50% 46%, rgba(86, 24, 19, 0.28), transparent 60%),
    rgba(9, 5, 5, 0.85);
}

.death-overlay:focus { outline: none; }

/* The card starts centered, then shared window dragging writes a persisted
   viewport position onto this outer surface. Entry motion stays on the inner
   card so its animation transform never fights the dragged position. */

.death-card-positioner {
  position: fixed;
  left: 50%;
  top: 50%;
  width: min(
    calc(424px * var(--ui-scale)),
    calc(100vw - 16px - var(--safe-left) - var(--safe-right))
  );
  max-height: calc(
    100dvh - 16px - var(--safe-top) - var(--safe-bottom)
  );
  overflow-y: auto;
  transform: translate(-50%, -50%);
}

/* ===== Frame ===== */

/* Frame, inner light, header tick and header rule all come from the window plane
   itself; what is left here is the card's own shape and its entry. A card the
   player is meant to read and act on is a window, and death does not get a plane
   of its own. */

.death-card {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  text-align: start;
  animation: death-card-in 180ms cubic-bezier(0.2, 0.78, 0.24, 1) both;
}

@keyframes death-card-in {
  from { opacity: 0; transform: translateY(calc(10px * var(--ui-scale))) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* ===== Header =====

   A title bar, not a hero banner: the same tick, divider and drag relationship
   every persistent window keeps, with the display face and the tone a critical
   interruption is allowed. */

.death-card__head {
  position: relative;
  display: grid;
  gap: calc(3px * var(--ui-scale));
  padding: calc(14px * var(--ui-scale)) calc(16px * var(--ui-scale)) calc(13px * var(--ui-scale))
    calc(19px * var(--ui-scale));
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--death-tone) 16%, transparent),
    transparent 88%
  );
  user-select: none;
  touch-action: none;
}

/* The window's own tick, at the height a two-line title bar needs. */

.death-card__head::before {
  top: calc(17px * var(--ui-scale));
  height: calc(20px * var(--ui-scale));
}

.death-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(25px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.015em;
  color: var(--text);
}

.death-card__killer {
  margin: 0;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 600;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, var(--death-tone) 42%, var(--text));
}

/* The world's own line about what just happened, set quieter and lighter than the
   killer so it reads as the card's voice rather than another fact on it. */

.death-card__lore {
  margin: calc(3px * var(--ui-scale)) 0 0;
  font-size: calc(11px * var(--ui-scale));
  font-style: italic;
  line-height: 1.4;
  color: color-mix(in srgb, var(--text) 62%, transparent);
}

/* ===== Report ===== */

.death-card__body {
  display: grid;
  min-width: 0;
}

/* The header draws the rule under itself, and every block that can open the card
   draws one above itself — whichever of them lands directly under the title bar
   drops its own, or the two stack into a double hairline at different insets. */

.death-card__head + .death-card__foot,
.death-card__head + .death-card__body > :first-child,
.death-card__head + .death-card__body > .death-loss > :first-child {
  border-block-start: 0;
}

/* One band heads every group on the card, the way an owner band heads a group in
   the journal: a full-bleed strip, a small uppercase label, and whatever single
   fact belongs beside it. */

.death-band {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  min-height: calc(30px * var(--ui-scale));
  margin: 0;
  padding: calc(4px * var(--ui-scale)) calc(14px * var(--ui-scale));
  border-block-end: 1px solid var(--death-band-line);
  background: var(--death-band-fill);
}

.death-band__label {
  min-width: 0;
  color: color-mix(in srgb, var(--death-tone) 46%, var(--text));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 900;
  line-height: 1.25;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The one fact that rides beside a label. It keeps its own case: a band that
   states a duration would otherwise print "12S". */

.death-band__meta {
  margin-inline-start: auto;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  text-align: end;
}

.death-band__count {
  display: grid;
  place-items: center;
  min-width: calc(17px * var(--ui-scale));
  height: calc(17px * var(--ui-scale));
  margin-inline-start: auto;
  border: 1px solid var(--death-band-line);
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
}

/* ===== What the death took =====

   One block under the report, holding either the risk that was carried or — once
   the server's receipt lands — the pieces it actually took. */

.death-card__risk {
  position: relative;
  margin: 0;
  padding: calc(8px * var(--ui-scale)) calc(14px * var(--ui-scale)) calc(8px * var(--ui-scale))
    calc(16px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--death-tone) 10%, transparent),
    transparent 70%
  );
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 600;
  line-height: 1.4;
}

.death-card__risk::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: calc(2px * var(--ui-scale));
  background: color-mix(in srgb, var(--death-tone) 65%, transparent);
}

.death-loss {
  display: grid;
  min-width: 0;
}

.death-loss__items {
  display: grid;
  gap: calc(4px * var(--ui-scale));
  margin: 0;
  padding: calc(8px * var(--ui-scale)) calc(14px * var(--ui-scale));
  list-style: none;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--death-tone) 8%, transparent),
    transparent 76%
  );
}

.death-loss__item {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  min-width: 0;
}

/* The piece sits in the same framed slot a bag cell gives it. */

.death-loss__slot {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: calc(28px * var(--ui-scale));
  height: calc(28px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(4px * var(--ui-scale));
  background: var(--ui-control-fill);
}

.death-loss__icon {
  width: calc(22px * var(--ui-scale));
  height: calc(22px * var(--ui-scale));
  object-fit: contain;
}

.death-loss__name {
  min-width: 0;
  overflow: hidden;
  color: var(--text);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-loss__refine {
  flex: 0 0 auto;
  margin-inline-start: auto;
  color: var(--accent);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 850;
  font-variant-numeric: tabular-nums;
}

/* The same slot, when the roll took nothing. Relief reads as relief. */

.death-loss--held {
  position: relative;
  margin: 0;
  padding: calc(9px * var(--ui-scale)) calc(14px * var(--ui-scale)) calc(9px * var(--ui-scale))
    calc(16px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--good) 10%, transparent),
    transparent 76%
  );
  color: color-mix(in srgb, var(--good) 52%, var(--text));
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
}

.death-loss--held::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: calc(2px * var(--ui-scale));
  background: color-mix(in srgb, var(--good) 70%, transparent);
}

/* ===== The wait and the choice =====

   A footer plane, sunk under the report the way a window's action row is. The
   choices are the only thing on this card a player acts on; they must not read
   as one more row of the report above them. */

.death-card__foot {
  display: grid;
  gap: calc(9px * var(--ui-scale));
  padding: calc(12px * var(--ui-scale)) calc(14px * var(--ui-scale)) calc(14px * var(--ui-scale));
  border-block-start: 1px solid var(--ui-line-soft);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.3));
  text-align: center;
}

/* Non-actionable wait: HP hit 0, the authoritative death state has not landed. */

.death-card__pending {
  margin: 0;
  padding: calc(8px * var(--ui-scale)) 0;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 600;
  color: var(--muted);
  animation: death-pending-breathe 1.8s ease-in-out infinite;
}

@keyframes death-pending-breathe { 0%, 100% { opacity: 0.72; } 50% { opacity: 1; } }

/* One centred numeral over a hairline gauge — no panel around it, so the footer
   reads wait → cost → choice as three plain rows instead of nested boxes. */

.death-timer {
  display: grid;
  justify-items: center;
}

/* Fixed height: the wait ending swaps a two-line countdown for one ready word,
   and the choices below must not jump under a cursor at the instant the
   graveyard unlocks. */

.death-timer__readout {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: calc(1px * var(--ui-scale));
  height: calc(50px * var(--ui-scale));
}

.death-timer__label {
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

.death-timer__value {
  font-size: calc(34px * var(--ui-scale));
  font-weight: 800;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.death-timer__unit {
  margin-inline-start: calc(1px * var(--ui-scale));
  font-size: calc(15px * var(--ui-scale));
  font-weight: 700;
  color: var(--muted);
}

/* Ready takes the numeral's place and its weight. */

.death-timer__ready {
  font-family: var(--font-display);
  font-size: calc(24px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.death-timer__track {
  --gauge-h: calc(4px * var(--ui-scale));
  --gauge-ring: rgba(255, 255, 255, 0.08);
  margin-top: calc(5px * var(--ui-scale));
}

.death-timer__fill {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--death-tone) 42%, #52606b),
    color-mix(in srgb, var(--death-tone) 72%, #88929c)
  );
  transition: width 180ms linear;
}

.death-timer.is-ready .death-timer__fill { background: linear-gradient(90deg, var(--accent-2), var(--accent)); }

/* ===== Soul Fatigue ===== */

/* Pips first, count second: the meter carries the reading and the words stay a
   caption. The rule that grows the stacks lives in the tooltip. */

.death-fatigue {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Wraps the caption under the meter on a narrow card rather than squeezing it
     into a column of single words. */
  flex-wrap: wrap;
  gap: calc(2px * var(--ui-scale)) calc(8px * var(--ui-scale));
  cursor: help;
}

.death-fatigue__label {
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.death-fatigue__meter { display: inline-flex; gap: calc(3px * var(--ui-scale)); }

/* An empty stack is a slot, not an absence: it keeps a hairline so the meter
   reads as five places of which two are spent. */

.death-fatigue__seg {
  width: calc(13px * var(--ui-scale));
  height: calc(5px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(2px * var(--ui-scale));
  background: rgba(0, 0, 0, 0.35);
}

.death-fatigue__seg.is-on {
  border-color: color-mix(in srgb, var(--death-tone) 55%, transparent);
  background: color-mix(in srgb, var(--death-tone) 72%, #a7aeb5);
}

/* ===== Choices ===== */

.death-actions {
  display: grid;
  gap: calc(7px * var(--ui-scale));
}

/* Carries each choice's hover detail. A disabled button drops its own pointer
   events, so the tooltip has to live on a box behind it. */

.death-action-slot { display: block; }

/* The choices wear the kit's primary (locked while the wait runs) and its
   ghost; what is theirs is the box: one row, full width, label left and
   consequence right. */

button.death-action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(10px * var(--ui-scale));
  width: 100%;
  min-height: calc(38px * var(--ui-scale));
  padding: calc(9px * var(--ui-scale)) calc(13px * var(--ui-scale));
  font-size: calc(14px * var(--ui-scale));
  text-align: center;
}

/* A choice that carries a cost splits: label left, tag hard right. When the pair
   no longer fits on one line (large --ui-scale on a narrow window) the tag drops
   to its own line instead of breaking the label into a stack of single words. */

.death-action--split { justify-content: space-between; flex-wrap: wrap; }

.death-action--split .death-action__label { text-align: left; }

.death-action--split .death-action__tag { margin-left: auto; }

.death-action__label {
  font-size: calc(14px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
}

/* The consequence, not a sentence about it — one short static tag that never
   rewords under the cursor. */

.death-action__tag {
  flex: none;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.01em;
  opacity: 0.82;
  white-space: nowrap;
}

/* The locked choice is already dimmed as a whole; dimming its tag twice would
   push it under the contrast floor for the entire wait. */

button.death-action:disabled .death-action__tag { opacity: 1; }

/* Secondary choice: the kit's ghost, never a second brass button competing
   with the primary. */

.death-action--ghost .death-action__tag { color: var(--muted); opacity: 1; }

/* ===== Accessibility ===== */

body.high-contrast .death-overlay { background: rgba(3, 4, 7, 0.92); }

body.high-contrast .death-card { background: #0a0e14; border-color: #8fa0b5; }

body.high-contrast .death-card__foot { background: #06090d; }

body.high-contrast button.death-action:disabled { border-color: #6f7d92; }

body.high-contrast button.death-action--ghost { background: #0a0e14; border-color: #6f7d92; }

body.high-contrast .death-action__tag { opacity: 1; }

body.high-contrast .death-fatigue__label,
body.high-contrast .death-timer__label { color: #c9d4e4; }

@media (prefers-reduced-motion: reduce) {
  .death-card { animation: none; }
  .death-card__pending { animation: none; opacity: 1; }
  .death-timer__fill { transition: none; }
}

body.reduce-motion .death-card,
body.reduce-flashing .death-card { animation: none; }

body.reduce-motion .death-card__pending,
body.reduce-flashing .death-card__pending { animation: none; opacity: 1; }

body.reduce-motion .death-timer__fill { transition: none; }

/* Death recap — how the death happened, inside the death card.

   Three groups, each under the card's own labelled band: the blow that landed
   last, the fight's damage split, and the hits themselves behind a control. The
   share strips are the HUD's gauge at hairline height; a post-mortem is still
   the HUD talking. */

.death-recap {
  display: grid;
  min-width: 0;
  text-align: start;
}

/* ===== The blow that ended it ===== */

.death-recap__blow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(10px * var(--ui-scale));
  padding: calc(9px * var(--ui-scale)) calc(14px * var(--ui-scale));
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--death-tone) 14%, transparent),
    transparent 80%
  );
}

.death-recap__blow-icon {
  width: calc(34px * var(--ui-scale));
  height: calc(34px * var(--ui-scale));
  padding: calc(2px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--death-tone) 34%, var(--ui-control-line));
  border-radius: calc(4px * var(--ui-scale));
  background: var(--ui-control-fill);
  object-fit: contain;
}

.death-recap__blow-body {
  display: grid;
  gap: calc(1px * var(--ui-scale));
  min-width: 0;
}

.death-recap__blow-name {
  font-size: calc(15px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-recap__blow-hp {
  font-size: calc(11px * var(--ui-scale));
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-recap__blow-amount {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: calc(2px * var(--ui-scale));
  font-size: calc(22px * var(--ui-scale));
  font-weight: 800;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--death-ink);
}

/* A crit is a chip, not shouted text: it must read next to a four-figure number
   without competing with it. */

.death-recap__crit {
  padding: 0 calc(4px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--accent) 46%, transparent);
  border-radius: calc(3px * var(--ui-scale));
  font-size: calc(8px * var(--ui-scale));
  font-weight: 800;
  line-height: calc(12px * var(--ui-scale));
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ===== Who took the bar down ===== */

.death-recap__split {
  display: grid;
  gap: calc(7px * var(--ui-scale));
  padding: calc(9px * var(--ui-scale)) calc(14px * var(--ui-scale)) calc(10px * var(--ui-scale));
}

.death-recap__sources {
  display: grid;
  gap: calc(8px * var(--ui-scale));
  margin: 0;
  padding: 0;
  list-style: none;
}

.death-recap__source {
  display: grid;
  gap: calc(3px * var(--ui-scale));
  min-width: 0;
}

.death-recap__source-head {
  display: flex;
  align-items: baseline;
  gap: calc(8px * var(--ui-scale));
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  color: var(--text);
}

.death-recap__source-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The amount leads and the share trails, so the bar under them is never the only
   thing carrying the reading. */

.death-recap__source-amount {
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
  color: color-mix(in srgb, var(--text) 82%, var(--muted));
}

.death-recap__source-pct {
  min-width: calc(30px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: end;
  color: var(--muted);
}

.death-recap__bar {
  --gauge-h: calc(5px * var(--ui-scale));
  --gauge-ring: rgba(255, 255, 255, 0.09);
}

.death-recap__bar-fill {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--death-tone) 88%, #000),
    color-mix(in srgb, var(--death-tone) 55%, #f3cdc4)
  );
}

/* A player killer wears the opponent relation colour (danger red), which the
   recap resolves at paint time onto --recap-relation-tone; never the self
   gold. */

.death-recap__source.is-player .death-recap__bar-fill {
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--recap-relation-tone) 88%, #000),
    color-mix(in srgb, var(--recap-relation-tone) 55%, #f3cdc4)
  );
}

/* What that attacker used, as chips — the same quiet control plane the rest of
   the HUD puts small facts on. */

.death-recap__abilities {
  display: flex;
  flex-wrap: wrap;
  gap: calc(4px * var(--ui-scale));
}

.death-recap__ability {
  display: inline-flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  min-width: 0;
  padding: calc(2px * var(--ui-scale)) calc(5px * var(--ui-scale)) calc(2px * var(--ui-scale))
    calc(3px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(4px * var(--ui-scale));
  background: var(--ui-control-fill);
  font-size: calc(10px * var(--ui-scale));
  color: var(--muted);
}

.death-recap__ability-icon {
  width: calc(14px * var(--ui-scale));
  height: calc(14px * var(--ui-scale));
  object-fit: contain;
}

.death-recap__ability-name {
  max-width: calc(108px * var(--ui-scale));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-recap__ability-amount {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: color-mix(in srgb, var(--text) 80%, transparent);
}

.death-recap__earlier {
  margin: 0;
  font-size: calc(10px * var(--ui-scale));
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ===== The hits ===== */

/* A disclosure, on the shared control plane — an underlined link is a web page's
   affordance, not this game's. */

.death-recap__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(6px * var(--ui-scale));
  width: 100%;
  min-height: calc(26px * var(--ui-scale));
  margin-top: calc(1px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid var(--ui-control-line);
  border-radius: calc(5px * var(--ui-scale));
  background: var(--ui-control-fill);
  font: inherit;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
  cursor: var(--cursor-action);
  transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
}

.death-recap__toggle:hover {
  border-color: var(--ui-control-line-hover);
  background: var(--ui-control-fill-hover);
  color: var(--accent);
}

.death-recap__toggle:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

/* The caret states which way the list will move, and turns when it has. */

.death-recap__toggle-caret {
  width: calc(6px * var(--ui-scale));
  height: calc(6px * var(--ui-scale));
  border-right: calc(1.5px * var(--ui-scale)) solid currentcolor;
  border-bottom: calc(1.5px * var(--ui-scale)) solid currentcolor;
  transform: translateY(calc(-2px * var(--ui-scale))) rotate(45deg);
  transition: transform 120ms ease;
}

.death-recap__toggle[aria-expanded='true'] .death-recap__toggle-caret {
  transform: translateY(calc(1px * var(--ui-scale))) rotate(225deg);
}

/* One row per hit, oldest first, each ending in the health it left behind so the
   drain reads as a sequence rather than a total. */

.death-recap__timeline {
  display: grid;
  gap: calc(2px * var(--ui-scale));
  margin: 0;
  padding: 0;
  max-height: calc(206px * var(--ui-scale));
  overflow-y: auto;
  list-style: none;
}

/* The list scrolls inside the card rather than pushing the respawn choices off
   the bottom, so a cut edge fades to read as "more below" instead of as a row
   that failed to draw. Only a list that is actually cut gets it: on a list that
   fits, the fade would dim the killing blow, the one row that must stay clear. */

.death-recap__timeline.is-scrollable {
  mask-image: linear-gradient(180deg, #000 calc(100% - 18px), transparent 100%);
}

.death-recap__hit {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(2px * var(--ui-scale)) calc(7px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(6px * var(--ui-scale));
  border-radius: calc(4px * var(--ui-scale));
  background: rgba(255, 255, 255, 0.02);
}

/* The row that killed is the one a player looks for; it wears the tone. */

.death-recap__hit.is-final {
  background: color-mix(in srgb, var(--death-tone) 12%, transparent);
  box-shadow: inset calc(2px * var(--ui-scale)) 0 0 color-mix(in srgb, var(--death-tone) 70%, transparent);
}

.death-recap__hit-time {
  min-width: calc(54px * var(--ui-scale));
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.death-recap__hit.is-final .death-recap__hit-time {
  color: color-mix(in srgb, var(--death-tone) 44%, var(--text));
}

.death-recap__hit-icon {
  width: calc(18px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  object-fit: contain;
}

.death-recap__hit-body {
  display: grid;
  min-width: 0;
}

.death-recap__hit-label {
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-recap__hit-source {
  font-size: calc(9px * var(--ui-scale));
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.death-recap__hit-amount {
  display: flex;
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  font-size: calc(12px * var(--ui-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--death-ink);
}

.death-recap__hit-hp {
  grid-column: 3 / -1;
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
}

.death-recap__hit-hp-bar {
  --gauge-h: calc(3px * var(--ui-scale));
  --gauge-ring: rgba(255, 255, 255, 0.09);
  flex: 1;
}

.death-recap__hit-hp-text {
  min-width: calc(46px * var(--ui-scale));
  font-size: calc(9px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  text-align: end;
  color: var(--muted);
}

body.high-contrast .death-recap__ability,
body.high-contrast .death-recap__toggle { border-color: #6f7d92; color: #c9d4e4; }

/* The killing blow keeps its tone here too: the row a player is looking for must
   not be the one row high contrast flattens. */

body.high-contrast .death-recap__hit:not(.is-final) { background: rgba(255, 255, 255, 0.06); }

@media (prefers-reduced-motion: reduce) {
  .death-recap__toggle-caret { transition: none; }
}

body.reduce-motion .death-recap__toggle-caret { transition: none; }

/* Critical interruptions and short-lived screen feedback share one material,
   hierarchy, and 180ms motion language. They stay lighter than a normal window:
   the world remains context, while a local band carries the decision. */

.screen-modal {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding:
    max(calc(20px * var(--ui-scale)), var(--safe-top))
    max(calc(20px * var(--ui-scale)), var(--safe-right))
    max(calc(20px * var(--ui-scale)), var(--safe-bottom))
    max(calc(20px * var(--ui-scale)), var(--safe-left));
  color: var(--text);
  background:
    radial-gradient(ellipse at 50% 48%, rgba(13, 20, 23, 0.42), transparent 58%),
    rgba(5, 8, 10, 0.78);
  animation: screen-modal-scrim-in 180ms ease-out both;
}

/* Session band: above every window, or a window paints over the only card that can recover the session. */

.reconnect-overlay { z-index: 400; }

.kick-overlay { z-index: 401; }

.screen-help-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 160;
  align-items: center;
  justify-content: center;
  padding:
    max(calc(18px * var(--ui-scale)), var(--safe-top))
    max(calc(18px * var(--ui-scale)), var(--safe-right))
    max(calc(18px * var(--ui-scale)), var(--safe-bottom))
    max(calc(18px * var(--ui-scale)), var(--safe-left));
  overflow-y: auto;
  background: rgba(5, 8, 10, 0.72);
}

.screen-help-card {
  width: min(calc(520px * var(--ui-scale)), calc(100vw - 28px));
  max-width: none;
  max-height: min(88dvh, calc(720px * var(--ui-scale)));
  display: flex;
  flex-direction: column;
  gap: calc(12px * var(--ui-scale));
  overflow: auto;
  padding: calc(20px * var(--ui-scale)) calc(22px * var(--ui-scale));
  animation: screen-modal-panel-in 180ms cubic-bezier(.2, .78, .24, 1) both;
}

.screen-help-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(22px * var(--ui-scale));
  font-weight: 700;
}

.screen-help-card__lede {
  margin: 0;
  color: color-mix(in srgb, var(--text) 66%, var(--muted));
  font-size: calc(12px * var(--ui-scale));
  line-height: 1.55;
}

.screen-help-card__controls {
  display: grid;
  grid-template-columns: minmax(calc(132px * var(--ui-scale)), auto) minmax(0, 1fr);
  gap: calc(6px * var(--ui-scale)) calc(16px * var(--ui-scale));
  margin: 0;
  padding-block: calc(10px * var(--ui-scale));
  border-block: 1px solid var(--ui-line-soft);
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.36;
}

.screen-help-card__controls dt {
  color: color-mix(in srgb, var(--accent) 76%, var(--text));
  font-weight: 700;
}

.screen-help-card__controls dd {
  min-width: 0;
  margin: 0;
  color: var(--muted);
}

.screen-help-card__close { margin-top: calc(2px * var(--ui-scale)); }

.screen-modal[data-tone='warning'] {
  --screen-modal-tone: var(--accent);
  --screen-modal-tone-soft: rgba(201, 164, 94, 0.12);
}

.screen-modal[data-tone='danger'] {
  --screen-modal-tone: var(--danger);
  --screen-modal-tone-soft: rgba(216, 91, 72, 0.12);
  background:
    radial-gradient(ellipse at 50% 48%, rgba(86, 24, 19, 0.24), transparent 60%),
    rgba(9, 5, 5, 0.84);
}

.screen-modal__panel {
  position: relative;
  width: min(calc(540px * var(--ui-scale)), calc(100vw - 32px));
  display: grid;
  justify-items: center;
  gap: calc(9px * var(--ui-scale));
  padding:
    calc(30px * var(--ui-scale))
    calc(34px * var(--ui-scale))
    calc(28px * var(--ui-scale));
  overflow: hidden;
  border-block: 1px solid var(--ui-line-soft);
  background:
    linear-gradient(
      90deg,
      transparent,
      rgba(13, 20, 23, 0.9) 12%,
      rgba(18, 27, 29, 0.96) 50%,
      rgba(13, 20, 23, 0.9) 88%,
      transparent
    );
  filter: drop-shadow(0 calc(18px * var(--ui-scale)) calc(30px * var(--ui-scale)) rgba(0, 0, 0, 0.42));
  text-align: center;
  animation: screen-modal-panel-in 180ms cubic-bezier(.2, .78, .24, 1) both;
}

.screen-modal__panel::before,
.screen-modal__panel::after {
  content: '';
  position: absolute;
  left: 50%;
  width: 42%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--screen-modal-tone), transparent);
  transform: translateX(-50%);
  opacity: 0.64;
}

.screen-modal__panel::before { top: -1px; }

.screen-modal__panel::after { bottom: -1px; opacity: 0.22; }

.screen-modal__signal {
  width: calc(42px * var(--ui-scale));
  height: calc(3px * var(--ui-scale));
  margin-bottom: calc(3px * var(--ui-scale));
  border-radius: 999px;
  background: var(--screen-modal-tone);
  box-shadow: 0 0 calc(16px * var(--ui-scale)) var(--screen-modal-tone-soft);
}

.screen-modal[data-state='reconnecting'] .screen-modal__signal,
.screen-modal[data-state='restarting'] .screen-modal__signal {
  overflow: hidden;
  background: rgba(201, 164, 94, 0.2);
}

.screen-modal[data-state='reconnecting'] .screen-modal__signal::after,
.screen-modal[data-state='restarting'] .screen-modal__signal::after {
  content: '';
  display: block;
  width: 48%;
  height: 100%;
  border-radius: inherit;
  background: var(--screen-modal-tone);
  animation: screen-modal-scan 1.15s ease-in-out infinite alternate;
}

.screen-modal__title {
  margin: 0;
  color: var(--text);
  font-family: var(--font-display);
  font-size: calc(30px * var(--ui-scale));
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: 0.015em;
}

.screen-modal__message {
  max-width: calc(440px * var(--ui-scale));
  min-height: calc(19px * var(--ui-scale));
  margin: 0;
  color: color-mix(in srgb, var(--text) 70%, var(--muted));
  font-size: calc(13px * var(--ui-scale));
  line-height: 1.48;
}

.screen-modal[data-tone='danger'] .screen-modal__message {
  color: color-mix(in srgb, var(--danger) 42%, var(--text));
}

.screen-modal__actions {
  display: flex;
  justify-content: center;
  gap: calc(9px * var(--ui-scale));
  margin-top: calc(9px * var(--ui-scale));
}

.screen-modal__actions[hidden] { display: none; }

.screen-modal__action,
button.screen-modal__action {
  width: auto;
  min-width: calc(126px * var(--ui-scale));
  min-height: calc(38px * var(--ui-scale));
  padding: calc(9px * var(--ui-scale)) calc(18px * var(--ui-scale));
}

/* Map entry is a location title, not a glowing system toast. */

.map-banner {
  top: 26%;
  display: grid;
  place-items: center;
  min-width: min(calc(430px * var(--ui-scale)), calc(100vw - 32px));
  padding: calc(15px * var(--ui-scale)) calc(34px * var(--ui-scale));
  color: var(--text);
  font-family: var(--font-display);
  font-size: calc(31px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.025em;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.72);
  background:
    linear-gradient(
      90deg,
      transparent,
      rgba(7, 12, 14, 0.52) 20%,
      rgba(7, 12, 14, 0.68) 50%,
      rgba(7, 12, 14, 0.52) 80%,
      transparent
    );
  filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.3));
  transform: translate(-50%, calc(-50% - 8px));
  transition:
    opacity 180ms ease,
    transform 180ms cubic-bezier(.2, .78, .24, 1);
}

.map-banner::before,
.map-banner::after {
  content: '';
  position: absolute;
  left: 50%;
  width: 38%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201, 164, 94, 0.72), transparent);
  transform: translateX(-50%);
}

.map-banner::before { top: 0; }

.map-banner::after { bottom: 0; opacity: 0.34; }

.map-banner.show { transform: translate(-50%, -50%); }

/* The name is the title; the line under it says what the place is for. The line
   is body text at reading width, never a second title — a subtitle set at title
   weight reads as two competing names. */

.map-banner__name {
  line-height: 1.12;
  text-align: center;
}

.map-banner__line {
  max-width: min(calc(560px * var(--ui-scale)), calc(100vw - 64px));
  margin-top: calc(7px * var(--ui-scale));
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: calc(14px * var(--ui-scale));
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.42;
  text-align: center;
  text-shadow: 0 1px 5px rgba(0, 0, 0, 0.7);
  text-wrap: balance;
}

/* An empty line element must not hold open the gap above it. */

.map-banner:not(.has-line) .map-banner__line { display: none; }

/* Broadcasts use a compact local rail. The message is the state; no decorative
   emoji or unexplained badge competes with it. */

.announce-bar {
  top: max(calc(10px * var(--ui-scale)), var(--safe-top));
  right: auto;
  left: 50%;
  width: min(calc(620px * var(--ui-scale)), calc(100vw - 32px));
  padding: calc(8px * var(--ui-scale)) calc(26px * var(--ui-scale));
  color: color-mix(in srgb, var(--accent) 56%, var(--text));
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.018em;
  background:
    linear-gradient(90deg, transparent, rgba(7, 12, 14, 0.76) 16%, rgba(7, 12, 14, 0.76) 84%, transparent);
  box-shadow: inset 0 1px rgba(201, 164, 94, 0.44);
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.3));
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.78);
  transform: translate(-50%, calc(-100% - var(--safe-top) - 12px));
  transition: transform 180ms cubic-bezier(.2, .78, .24, 1);
}

.announce-bar.show { transform: translate(-50%, 0); }

@keyframes screen-modal-scrim-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes screen-modal-panel-in {
  from { opacity: 0; transform: translateY(calc(8px * var(--ui-scale))) scale(.985); }
  to { opacity: 1; transform: none; }
}

@keyframes screen-modal-scan {
  from { transform: translateX(0); }
  to { transform: translateX(108%); }
}

@media (max-width: 560px) {
  .screen-modal__panel {
    padding:
      calc(26px * var(--ui-scale))
      calc(18px * var(--ui-scale))
      calc(24px * var(--ui-scale));
  }

  .screen-modal__title { font-size: calc(27px * var(--ui-scale)); }
  .screen-modal__actions { width: 100%; flex-direction: column; }
  .screen-modal__action,
  button.screen-modal__action { width: 100%; }

  .map-banner {
    min-width: calc(100vw - 20px);
    font-size: calc(27px * var(--ui-scale));
  }

  .screen-help-card__controls {
    grid-template-columns: minmax(0, 1fr);
    gap: calc(2px * var(--ui-scale));
  }

  .screen-help-card__controls dd {
    margin-bottom: calc(5px * var(--ui-scale));
  }
}

@media (prefers-reduced-motion: reduce) {
  .screen-modal,
  .screen-modal__panel,
  .screen-help-card,
  .screen-modal[data-state='reconnecting'] .screen-modal__signal::after,
  .screen-modal[data-state='restarting'] .screen-modal__signal::after {
    animation: none;
  }

  .map-banner,
  .announce-bar { transition: none; }
}

body.reduce-motion .screen-modal,
body.reduce-motion .screen-modal__panel,
body.reduce-motion .screen-help-card,
body.reduce-motion .screen-modal[data-state='reconnecting'] .screen-modal__signal::after,
body.reduce-motion .screen-modal[data-state='restarting'] .screen-modal__signal::after {
  animation: none;
}

body.reduce-motion .map-banner,
body.reduce-motion .announce-bar { transition: none; }

body.high-contrast .screen-modal { background: rgba(3, 5, 6, 0.92); }

body.high-contrast .screen-modal__panel { border-block-color: #c6d1ce; }

body.high-contrast .screen-modal__message { color: var(--text); }

/* The profile and desktop-glass sheets are deliberately NOT imported here. This
   bundle is loaded before the standalone HUD sheets, so a copy taken from inside
   it would land ahead of the rules it has to outrank. Each entry point loads
   them itself, after everything else. */
/* Adventurer active catalog and four comparable passive role lanes.
   The window states its own height instead of taking it from whatever the list
   currently holds: typing in the search box must not make the frame, its close
   button and every row under the cursor jump as results narrow to one or none. */
.win-body.skill-mode-adventurer {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  min-width: 0;
  height: min(72vh, calc(640px * var(--ui-scale, 1)));
  /* Beat .win-body's 68vh cap, and keep the title plus hotbar clear. 72px is
     the skills window's default top. */
  max-height: calc(
    100vh
    - 72px
    - var(--hotbar-h, calc(96px * var(--ui-scale, 1)))
    - calc(40px * var(--ui-scale, 1) + 8px)
  );
  padding: 8px;
  overflow: hidden;
}

/* Points + reset docked top-right; the window frame already titles this "Skills". */
.adv-build-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

.adv-role-identity,
.adv-active-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.adv-role-identity small { color: var(--muted); font-size: calc(11px * var(--ui-scale)); }

.adv-build-point-row { display: flex; flex-wrap: wrap; gap: 5px; }
.adv-build-points {
  padding: 3px 8px;
  border: 1px solid #344052;
  border-radius: 999px;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
}
.adv-build-points b { color: var(--text); font-size: calc(13px * var(--ui-scale)); }
.adv-build-points--active { border-color: color-mix(in srgb, var(--gold) 52%, #344052); }
.adv-build-points--passive { border-color: color-mix(in srgb, #67b7d1 52%, #344052); }
/* The next level that pays a point rides the badge itself, so the answer is on
   screen without a rail of its own; the badge tooltip carries the ladder. It
   takes the hue of the badge it sits in, so the two currencies stay apart. */
.adv-build-points-next { font-size: calc(9px * var(--ui-scale)); font-weight: 800; font-variant-numeric: tabular-nums; }
.adv-build-points--active .adv-build-points-next { color: color-mix(in srgb, var(--gold) 78%, var(--muted)); }
.adv-build-points--passive .adv-build-points-next { color: color-mix(in srgb, #67b7d1 78%, var(--muted)); }

.adv-build-attack-mix {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 6px 9px 8px;
  border: 1px solid #344052;
  border-radius: 9px;
  background: rgba(255,255,255,.025);
}
.adv-build-attack-mix__heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}
.adv-build-attack-mix__heading strong { color: var(--text); font-size: calc(11px * var(--ui-scale)); }
.adv-build-attack-mix__preview {
  position: relative;
  width: 35px;
  height: 22px;
  padding: 0;
  border: 1px solid #3c495c;
  border-radius: 999px;
  background: rgba(7,11,17,.5);
  cursor: var(--cursor-action);
}
.adv-build-attack-mix__preview img {
  position: absolute;
  top: 3px;
  width: 15px;
  height: 15px;
  object-fit: contain;
  filter: saturate(.82) brightness(.92);
}
.adv-build-attack-mix__preview img:first-child { left: 3px; }
.adv-build-attack-mix__preview img:last-child { right: 3px; }
.adv-build-attack-mix__preview:hover,
.adv-build-attack-mix__preview[aria-expanded='true'] { border-color: var(--accent); background: color-mix(in srgb, var(--gold) 9%, transparent); }
.adv-build-attack-mix__preview:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.adv-build-attack-mix__preview:hover img,
.adv-build-attack-mix__preview[aria-expanded='true'] img { filter: saturate(1.08) brightness(1.08); }

.adv-build-attack-mix__control {
  display: grid;
  grid-template-columns: minmax(58px, auto) minmax(82px, 1fr) minmax(58px, auto);
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.adv-build-attack-mix__side {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}
.adv-build-attack-mix__side.is-magical { flex-direction: row-reverse; }
.adv-build-attack-mix__side img {
  flex: 0 0 auto;
  width: 23px;
  height: 23px;
  object-fit: contain;
}
.adv-build-attack-mix__side-copy { display: flex; flex-direction: column; line-height: 1; }
.adv-build-attack-mix__side.is-magical .adv-build-attack-mix__side-copy { text-align: right; }
.adv-build-attack-mix__side b { color: var(--text); font-size: calc(11px * var(--ui-scale)); }
.adv-build-attack-mix__side small { margin-top: 2px; color: var(--muted); font-size: calc(8px * var(--ui-scale)); }
.adv-build-attack-mix__side.is-physical b { color: color-mix(in srgb, var(--attack-physical) 74%, white); }
.adv-build-attack-mix__side.is-magical b { color: color-mix(in srgb, var(--attack-magical) 74%, white); }

.adv-build-attack-mix input[type='range'] {
  width: 100%;
  min-width: 0;
  height: 26px;
  margin: 0;
  appearance: none;
  background: transparent;
  cursor: var(--cursor-action);
}
.adv-build-attack-mix input[type='range']::-webkit-slider-runnable-track {
  height: 8px;
  border: 1px solid rgba(216,226,239,.24);
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--attack-physical) 0 var(--physical-share),
    var(--attack-magical) var(--physical-share) 100%
  );
  box-shadow: inset 0 1px 2px rgba(0,0,0,.5), 0 0 7px rgba(87,124,166,.12);
}
.adv-build-attack-mix input[type='range']::-moz-range-track {
  height: 8px;
  border: 1px solid rgba(216,226,239,.24);
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--attack-physical) 0 var(--physical-share),
    var(--attack-magical) var(--physical-share) 100%
  );
  box-shadow: inset 0 1px 2px rgba(0,0,0,.5), 0 0 7px rgba(87,124,166,.12);
}
.adv-build-attack-mix input[type='range']::-moz-range-progress { background: transparent; }
.adv-build-attack-mix input[type='range']::-webkit-slider-thumb {
  width: 14px;
  height: 22px;
  margin-top: -8px;
  appearance: none;
  border: 2px solid #e7edf5;
  border-radius: 5px;
  background: linear-gradient(to right, var(--attack-physical) 0 50%, var(--attack-magical) 50% 100%);
  box-shadow: 0 2px 7px rgba(0,0,0,.65);
}
.adv-build-attack-mix input[type='range']::-moz-range-thumb {
  width: 11px;
  height: 19px;
  border: 2px solid #e7edf5;
  border-radius: 5px;
  background: linear-gradient(to right, var(--attack-physical) 0 50%, var(--attack-magical) 50% 100%);
  box-shadow: 0 2px 7px rgba(0,0,0,.65);
}
.adv-build-attack-mix input[type='range']:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.adv-build-attack-mix input[type='range'][aria-disabled='true'] { cursor: wait; opacity: .58; }
.adv-build-attack-mix__projection {
  position: absolute;
  z-index: 12;
  top: calc(100% + 5px);
  right: 7px;
  display: flex;
  visibility: hidden;
  flex-direction: column;
  gap: 6px;
  width: min(268px, calc(100% - 14px));
  padding: 9px 11px;
  border: 1px solid #506078;
  border-radius: 8px;
  background: linear-gradient(145deg, rgba(28,35,47,.98), rgba(12,17,25,.98));
  box-shadow: 0 10px 24px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.05);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-3px);
  transition: opacity .1s ease, transform .1s ease, visibility .1s;
}
.adv-build-attack-mix:not(.is-preview-suppressed):hover .adv-build-attack-mix__projection,
.adv-build-attack-mix:not(.is-preview-suppressed) .adv-build-attack-mix__control:focus-within + .adv-build-attack-mix__projection,
.adv-build-attack-mix:has(.adv-build-attack-mix__preview[aria-expanded='true']) .adv-build-attack-mix__projection {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}
.adv-build-attack-mix__projection-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.adv-build-attack-mix__projection-label > strong { color: var(--text); font-size: calc(10px * var(--ui-scale)); }
.adv-build-attack-mix__total { display: flex; align-items: baseline; gap: 3px; }
.adv-build-attack-mix__total b { color: var(--gold-label); font-size: calc(15px * var(--ui-scale)); }
.adv-build-attack-mix__total small,
.adv-build-attack-mix__projection-note { color: var(--muted); font-size: calc(8px * var(--ui-scale)); }
.adv-build-attack-mix__components { display: flex; align-items: center; gap: 7px; }
.adv-build-attack-mix__component {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.adv-build-attack-mix__component img { width: 20px; height: 20px; object-fit: contain; }
.adv-build-attack-mix__component b { font-size: calc(12px * var(--ui-scale)); line-height: 1; }
.adv-build-attack-mix__component small { color: var(--muted); font-size: calc(8px * var(--ui-scale)); }
.adv-build-attack-mix__component.is-physical b { color: color-mix(in srgb, var(--attack-physical) 74%, white); }
.adv-build-attack-mix__component.is-magical b { color: color-mix(in srgb, var(--attack-magical) 74%, white); }
.adv-build-attack-mix__operator { color: var(--muted); font-size: calc(10px * var(--ui-scale)); font-weight: 900; }
.adv-build-attack-mix__projection-note { padding-top: 5px; border-top: 1px solid rgba(108,123,145,.28); }

@media (prefers-reduced-motion: reduce) {
  .adv-build-attack-mix__projection { transition: none; }
}

.reduce-motion .adv-build-attack-mix__projection { transition: none; }

/* The reset button itself is `.refund-button`, shared with the character sheet. */
.adv-build-respec-wrap { display: inline-flex; justify-self: start; }
.adv-build-tab {
  border: 1px solid #344052;
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--text);
  cursor: var(--cursor-action);
}
.adv-build-choice:focus-visible,
.adv-build-tab:focus-visible,
.adv-active-card:focus-visible,
.atn-card:focus-visible,
.adv-build-search input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.adv-build-notice { padding: 7px 9px; border: 1px solid #8c554d; border-radius: 8px; background: rgba(140,85,77,.12); color: #efb2a9; font-size: calc(11px * var(--ui-scale)); }
.adv-build-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.adv-build-tab { min-height: 25px; padding: 3px 10px; color: var(--muted); font-size: calc(12px * var(--ui-scale)); font-weight: 850; }
.adv-build-tab:hover,
.adv-build-tab.is-active { color: var(--text); border-color: var(--accent); }
.adv-build-tab.is-active { background: color-mix(in srgb, var(--gold) 10%, transparent); box-shadow: inset 0 -2px 0 var(--accent); }

.adv-build-active-pane,
.adv-role-pane { min-height: 0; }
.adv-build-active-pane { display: flex; flex: 1; flex-direction: column; gap: 10px; overflow: hidden; }
.adv-role-pane { display: flex; flex: 1; flex-direction: column; gap: 9px; overflow: hidden; }
.adv-role-primer {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(220px, 1fr);
  align-items: center;
  gap: 9px;
  padding: 6px 9px;
  border: 1px solid color-mix(in srgb, var(--accent) 58%, #344052);
  border-radius: 10px;
  background:
    linear-gradient(120deg, color-mix(in srgb, var(--accent) 11%, transparent), transparent 62%),
    var(--panel-2);
}
.adv-role-primer-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.adv-role-primer-kicker {
  color: var(--gold-label);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 900;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.adv-role-primer-copy > strong { color: var(--text); font-size: calc(15px * var(--ui-scale)); }
.adv-role-primer-copy p {
  margin: 0;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  line-height: 1.35;
}
.adv-role-primer-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 5px;
}
.adv-role-primer-facts span {
  display: grid;
  place-items: center;
  min-height: 30px;
  padding: 3px 5px;
  border: 1px solid #344052;
  border-radius: 8px;
  color: #dfe7ef;
  font-size: calc(9px * var(--ui-scale));
  font-weight: 750;
  line-height: 1.25;
  text-align: center;
}
/* Filter bar as one row of HUD pills — search, learned-only, and the one
   function tag — not a grid of labeled form fields. Pills light gold when an
   active filter is set. */
.adv-build-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.adv-build-toolbar ron-select { flex: 0 1 auto; max-width: 132px; }
.adv-build-search { position: relative; display: flex; align-items: center; flex: 1 1 150px; min-width: 130px; }
.adv-build-search::before {
  content: ''; position: absolute; left: 7px; width: 15px; height: 15px; pointer-events: none;
  background: url('/play/assets/hud-search-CdtrNLXb.webp') center / contain no-repeat;
  filter: saturate(.8) brightness(.9);
}
.adv-build-search input {
  width: 100%; min-width: 0; height: 27px; padding: 4px 9px 4px 26px;
  border: 1px solid #344052; border-radius: 999px; background: rgba(10,14,20,.72);
  color: var(--text); font-size: calc(11px * var(--ui-scale)); outline: none;
}
.adv-build-search input:focus { border-color: var(--accent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 28%, transparent); }
.adv-build-search:focus-within::before { filter: saturate(1.1) brightness(1.12); }
.adv-build-pill {
  position: relative; display: inline-flex; align-items: center; min-width: 0; height: 27px;
  border: 1px solid #344052; border-radius: 999px; background: rgba(255,255,255,.03);
  cursor: var(--cursor-action); transition: border-color .12s, background-color .12s;
}
.adv-build-toggle {
  padding: 0 11px; color: var(--muted);
  font-family: inherit; font-size: calc(10px * var(--ui-scale)); font-weight: 750; white-space: nowrap;
}
.adv-build-pill:hover { border-color: #59687c; }
.adv-build-pill.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--gold) 10%, transparent); }
.adv-build-toggle.is-active { color: var(--gold-label); }
.adv-build-pill:focus-within { border-color: var(--accent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--gold) 28%, transparent); }

/* Single-column condensed catalog; the rich detail floats in as a hover flyout. */
.adv-active-catalog { display: flex; flex-direction: column; gap: 4px; flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; }
.adv-active-card {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  flex: 0 0 auto;
  min-height: 0;
  padding: 4px 8px;
  border: 1px solid #344052;
  border-left: 3px solid #667080;
  border-radius: 8px;
  background: linear-gradient(180deg, rgba(255,255,255,.025), rgba(0,0,0,.1)), var(--panel);
  cursor: var(--cursor-action);
  transition: border-color .12s, background-color .12s, transform .12s;
}
.adv-active-card:hover { border-color: #59687c; }
.adv-active-card.is-selected { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, var(--panel)); }
.adv-active-card.is-learned { border-left-color: var(--accent); }
.adv-active-card.is-available { border-left-color: #6fc06a; }
.adv-active-card.is-granted { border-left-color: #7fa8d8; }
.adv-active-card.is-planned { opacity: .68; }
.adv-active-card[draggable="true"] { cursor: grab; }
.adv-active-card.is-dragging { opacity: .55; cursor: grabbing; }
.adv-active-icon { width: 28px; height: 28px; border-radius: 7px; box-shadow: 0 0 0 1px rgba(255,255,255,.08), 0 3px 8px rgba(0,0,0,.3); }
.adv-active-copy { gap: 1px; }
.adv-active-copy > strong { overflow: hidden; text-overflow: ellipsis; color: var(--text); font-size: calc(12px * var(--ui-scale)); white-space: nowrap; }
.adv-active-copy > span { overflow: hidden; color: var(--muted); font-size: calc(10px * var(--ui-scale)); text-overflow: ellipsis; white-space: nowrap; }
/* The row says what the skill does; reach, footprint, function tags and the
   numbers live in the hover popover. Two lines is the budget — past that the
   list stops being scannable — so a long description trails off and the popover
   finishes it. */
.adv-active-description {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  margin: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  line-height: 1.32;
}
.adv-active-actions { display: flex; flex-direction: column; align-items: stretch; gap: 5px; min-width: 68px; }

.adv-build-choice {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 26px;
  padding: 4px 7px;
  border: 1px solid #566274;
  border-radius: 7px;
  background: linear-gradient(180deg, rgba(255,255,255,.07), transparent), #18202b;
  color: var(--text);
  font: 850 calc(10px * var(--ui-scale))/1 system-ui;
  letter-spacing: .03em;
  text-transform: uppercase;
  cursor: var(--cursor-action);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 2px 7px rgba(0,0,0,.34);
  transition: transform .12s, border-color .12s, background-color .12s;
}
.adv-build-choice-icon { width: 15px; height: 15px; flex: none; object-fit: contain; }
.adv-build-choice:hover:not(:disabled) { transform: translateY(-1px); }
.adv-build-choice:active:not(:disabled) { transform: translateY(0); }
.adv-build-choice:disabled { cursor: not-allowed; filter: grayscale(1); opacity: .42; }
.adv-build-choice.is-learn { border-color: #659f5b; background-color: #172719; color: #d6ffd1; }
.adv-build-choice.is-place { border-color: #5a7ea8; background-color: #17202a; color: #cfe4ff; }
.adv-build-choice.is-place.is-coach-place {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  animation: advcoachplace 1.35s ease-in-out infinite;
}
@keyframes advcoachplace {
  0%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 0 0 0 transparent; }
  50% { box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 0 14px 5px color-mix(in srgb, var(--accent) 65%, transparent); }
}
body.reduce-flashing .adv-build-choice.is-place.is-coach-place,
body.reduce-motion .adv-build-choice.is-place.is-coach-place {
  animation: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 0 12px 4px color-mix(in srgb, var(--accent) 50%, transparent);
}
.adv-build-choice.is-refund { border-color: #8f7046; background-color: #271f17; color: #f1d59a; }
.adv-build-choice:hover:not(:disabled):not(.is-learn):not(.is-refund) {
  background: linear-gradient(180deg, rgba(255,255,255,.14), transparent), #222b38;
  border-color: #6d7d92;
}
.adv-build-choice.is-learn:hover:not(:disabled) {
  border-color: #7ab56f;
  background-color: #1e3222;
}
.adv-build-choice.is-refund:hover:not(:disabled) {
  border-color: #a88855;
  background-color: #32271a;
}

/* Hover/focus detail: a body-level portal so it escapes the window's clipped scroll
   body and floats beside the row (flips side / clamps in the positioning code). */
.adv-skill-flyout {
  position: fixed;
  left: 0;
  top: 0;
  width: min(340px, calc(100vw - 16px));
  display: none;
  /* Popover band: above windows and the system menu, below kit select lists
     and the loading cover. */
  z-index: 190;
  pointer-events: none;
  box-shadow: 0 16px 38px rgba(0,0,0,.62);
}
.adv-skill-flyout.is-open { display: block; }
.adv-skill-flyout .adv-active-detail { max-height: min(84vh, 720px); }
/* The same card in the shared tooltip layer (hotbar hover): the layer is sized
   for terse item tooltips, so restate the flyout's width and height cap. */
.item-tooltip:has(> .adv-active-detail) {
  width: min(340px, calc(100vw - 16px));
  max-width: none;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: 0 16px 38px rgba(0,0,0,.62);
}
.item-tooltip > .adv-active-detail { max-height: min(84vh, 720px); }
.adv-active-detail { min-height: 0; max-height: 100%; overflow-y: auto; padding: 11px; border: 1px solid #45526580; border-radius: 12px; background: linear-gradient(180deg, rgba(24,30,40,.98), rgba(14,19,27,.99)); box-shadow: inset 0 1px 0 rgba(255,255,255,.05); }
.adv-active-detail-header { display: flex; align-items: center; gap: 8px; }
.adv-active-detail-header > div { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.adv-active-detail-header h3 { margin: 1px 0 0; color: var(--text); font-size: calc(15px * var(--ui-scale)); line-height: 1.2; }
.adv-active-detail-icon { width: 34px; height: 34px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,.38); }
/* The summary answers "what is this?" and the effect list answers "what does it
   do to the fight?" — the two lines a player actually reads. They lead the card
   and outweigh the reference chips below them. The summary is a <p>: pin its
   margins so the UA's 1em top and bottom don't open a gap wider than the text
   block itself. */
.adv-active-detail-summary { margin: 7px 0 8px; color: var(--text); font-size: calc(13.5px * var(--ui-scale)); line-height: 1.35; }
.adv-active-effects { margin-bottom: 8px; padding: 6px 8px 7px; border: 1px solid #4a5a48; border-left: 3px solid #8fbf7a; border-radius: 8px; background: rgba(143,191,122,.07); }
.adv-active-effects > strong { display: block; color: #b7d6a6; font-size: calc(9.5px * var(--ui-scale)); font-weight: 900; text-transform: uppercase; letter-spacing: .07em; }
.adv-active-facts { display: flex; flex-wrap: wrap; gap: 3px; }
.adv-active-facts > span { display: inline-flex; flex: 0 1 auto; align-items: center; gap: 4px; min-width: 0; padding: 2px 6px 2px 3px; border: 1px solid #303b49; border-radius: 6px; background: rgba(8,12,18,.38); color: #dce5ee; font-size: calc(9.5px * var(--ui-scale)); line-height: 1.25; }
.adv-active-fact-icon { flex: 0 0 auto; width: calc(17px * var(--ui-scale)); height: calc(17px * var(--ui-scale)); object-fit: contain; filter: drop-shadow(0 1px 1px rgba(0,0,0,.5)); }
.adv-active-footprint { margin-top: 7px; overflow: hidden; border: 1px solid #303b49; border-radius: 9px; background: rgba(8,12,18,.38); }
.adv-footprint-layer + .adv-footprint-layer { border-top: 1px solid #465467; }
.adv-footprint-layer > header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 8px; border-bottom: 1px solid #303b49; }
.adv-footprint-layer > header strong { color: var(--text); font-size: calc(10px * var(--ui-scale)); text-transform: uppercase; letter-spacing: .06em; }
.adv-footprint-layer > header span { color: var(--gold-label); font-size: calc(11px * var(--ui-scale)); font-weight: 850; }
.adv-footprint-content { display: grid; grid-template-columns: minmax(88px, .72fr) minmax(126px, 1fr); align-items: center; gap: 8px; padding: 6px; }
.adv-footprint-canvas { display: block; width: 100%; min-height: 84px; border: 1px solid rgba(255,255,255,.04); border-radius: 7px; background: #0d131c; }
.adv-footprint-content > div { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.adv-footprint-content > div strong { color: var(--text); font-size: calc(11px * var(--ui-scale)); line-height: 1.2; }
.adv-footprint-content > div span { color: var(--muted); font-size: calc(10px * var(--ui-scale)); line-height: 1.25; }
.adv-active-mechanics { margin: 4px 0 0; padding-left: 15px; color: #e4ecf3; font-size: calc(12.5px * var(--ui-scale)); line-height: 1.35; }
.adv-active-mechanics li + li { margin-top: 3px; }
/* Attribution for the numbers above, not a fourth effect line: it sits quieter
   than the effects and closes the block. */
.adv-active-scales-from { margin: 5px 0 0; color: #93a4b3; font-size: calc(10.5px * var(--ui-scale)); letter-spacing: 0.02em; text-transform: uppercase; }
.adv-active-mechanics::marker, .adv-active-mechanics li::marker { color: #8fbf7a; }
.adv-active-affinity { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 4px; padding-top: 6px; border-top: 1px solid #303b49; }
.adv-active-affinity strong { flex-basis: auto; margin-right: 2px; color: var(--muted); font-size: calc(10px * var(--ui-scale)); text-transform: uppercase; letter-spacing: .06em; }
.adv-active-affinity span { padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,.055); color: var(--text); font-size: calc(10px * var(--ui-scale)); line-height: 1.3; }
/* Both notes are <p>: zero the UA bottom margin so the panel ends at the note. */
.adv-active-edit-note { margin: 6px 0 0; padding: 6px; border-radius: 8px; background: color-mix(in srgb, var(--gold) 8%, transparent); color: #ead9ad; font-size: calc(11px * var(--ui-scale)); line-height: 1.3; }
.adv-active-edit-note { background: rgba(255,255,255,.035); color: var(--muted); }
.adv-build-state { display: inline-flex; justify-content: center; padding: 2px 6px; border: 1px solid #344052; border-radius: 999px; color: var(--muted); font-size: calc(9px * var(--ui-scale)); font-weight: 900; text-transform: uppercase; letter-spacing: .06em; }
.adv-build-state.is-available { border-color: #4f7d3d; color: #bdf3b9; }
.adv-build-state.is-granted { border-color: #46617f; color: #bcd6f2; }
.adv-build-state.is-locked { border-color: #5d6068; color: #a3a7ae; }

/* Masteries: a vertical rail of role tabs (points at a glance) + the selected
   role's linear tree, so it fits the same narrow window as the Abilities list. */
.adv-role-layout { display: grid; grid-template-columns: minmax(128px, 148px) minmax(0, 1fr); gap: 8px; flex: 1; min-height: 0; }
.adv-role-rail { display: flex; flex-direction: column; gap: 5px; min-height: 0; overflow-y: auto; padding: 1px 4px 1px 1px; }
.adv-role-tab {
  --role-accent: var(--gold);
  display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 7px;
  padding: 6px 8px; border: 1px solid #344052; border-left: 3px solid var(--role-accent);
  border-radius: 9px; background: var(--panel-2); color: var(--text); cursor: var(--cursor-action); text-align: left;
  transition: border-color .12s, background-color .12s;
}
.adv-role-tab:hover { border-color: #59687c; }
.adv-role-tab.is-active { border-color: var(--role-accent); background: color-mix(in srgb, var(--role-accent) 13%, var(--panel)); }
.adv-role-tab-crest { display: grid; place-items: center; width: 24px; height: 24px; border: 1px solid color-mix(in srgb, var(--role-accent) 70%, #364151); border-radius: 50%; background: #111822; overflow: hidden; }
.adv-role-tab-crest img { width: 21px; height: 21px; }
.adv-role-tab-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.adv-role-tab-body strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: calc(11px * var(--ui-scale)); font-weight: 800; }
.adv-role-tab-pips { display: flex; gap: 2px; }
.adv-role-tab-pips i { width: 10px; height: 4px; border-radius: 3px; background: #2e3948; }
.adv-role-tab-pips i.is-on { background: var(--role-accent); box-shadow: 0 0 6px color-mix(in srgb, var(--role-accent) 45%, transparent); }
.adv-role-tab-count { color: var(--muted); font-size: calc(10px * var(--ui-scale)); font-weight: 800; font-variant-numeric: tabular-nums; }
.adv-role-tab.is-active .adv-role-tab-count { color: color-mix(in srgb, var(--role-accent) 55%, white); }

.adv-role-tree-view { --role-accent: var(--gold); display: flex; flex-direction: column; min-height: 0; overflow: hidden; border: 1px solid #344052; border-top: 3px solid var(--role-accent); border-radius: 11px; background: linear-gradient(180deg, color-mix(in srgb, var(--role-accent) 9%, transparent), transparent 150px), var(--panel); box-shadow: 0 8px 20px rgba(0,0,0,.2); }
.adv-role-tree-head { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 8px; min-height: 34px; padding: 4px 9px; border-bottom: 1px solid var(--ui-control-line); }
.adv-role-tree-count { flex: none; color: var(--muted); font-size: calc(12px * var(--ui-scale)); font-weight: 800; font-variant-numeric: tabular-nums; }
/* max-content rows: an `auto` row collapses to the node's min-height once the path
   is taller than the pane, which laps a two-line node over the next one. */
.adv-role-nodes { display: grid; grid-auto-rows: max-content; margin: 0; padding: 3px; list-style: none; min-height: 0; overflow-y: auto; }

/* Per-role accent applies to both the rail tab and the selected tree view. */
.adv-role-tree--tank { --role-accent: #6aa8e0; }
.adv-role-tree--physical_dps { --role-accent: #dc765f; }
.adv-role-tree--magical_dps { --role-accent: #a382df; }
.adv-role-tree--healer { --role-accent: #6fc98a; }
.adv-role-crest { display: grid; place-items: center; width: 28px; height: 28px; border: 1px solid color-mix(in srgb, var(--role-accent) 70%, #364151); border-radius: 50%; background: #111822; box-shadow: 0 0 12px color-mix(in srgb, var(--role-accent) 22%, transparent); overflow: hidden; }
.adv-role-crest img { width: 24px; height: 24px; }
.adv-role-identity { gap: 2px; }
.adv-role-identity small { line-height: 1.25; }
/* The learn/refund control sits inline on the right, so a node is one row tall and
   the whole six-node path reads without scrolling. */
.adv-role-node { position: relative; display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; align-items: center; gap: 8px; min-height: 34px; padding: 2px 6px; border-radius: 8px; transition: background-color .12s, transform .12s; }
.adv-role-node + .adv-role-node::before { content: ''; position: absolute; left: 20px; top: -3px; width: 2px; height: 5px; background: #3a4656; }
.adv-role-node.is-learned { background: color-mix(in srgb, var(--role-accent) 8%, transparent); }
.adv-role-node.is-available:hover { background: color-mix(in srgb, var(--role-accent) 8%, transparent); transform: translateY(-1px); }
.adv-role-node.is-locked { opacity: .48; filter: saturate(.4); }
.adv-role-icon { position: relative; display: grid; place-items: center; width: 30px; height: 30px; border: 2px solid #465263; border-radius: 50%; background: #10161f; box-shadow: inset 0 0 0 2px rgba(0,0,0,.45); }
.adv-role-icon > img { width: 24px; height: 24px; border-radius: 50%; }
.adv-role-node.is-learned .adv-role-icon,
.adv-role-node.is-available .adv-role-icon { border-color: var(--role-accent); box-shadow: 0 0 13px color-mix(in srgb, var(--role-accent) 24%, transparent), inset 0 0 0 2px rgba(0,0,0,.4); }
.adv-role-copy { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.adv-role-copy > strong { font-size: calc(12px * var(--ui-scale)); }
.adv-role-copy > span { color: var(--muted); font-size: calc(10px * var(--ui-scale)); line-height: 1.25; }
.adv-role-copy > .adv-role-trigger { color: color-mix(in srgb, var(--role-accent) 72%, white); font-size: calc(9px * var(--ui-scale)); line-height: 1.25; }
.adv-role-action { display: flex; flex-direction: column; align-items: stretch; gap: 4px; min-width: 68px; }
.adv-role-action .adv-build-choice { min-height: 24px; padding: 3px 6px; }

/* Attunement loadout: the slot rail states what the character is carrying, and
   the catalog below states every trade on offer, grouped by family. A family
   is headed the way a mastery path is — crest, name, what it is for, and how
   much of its cap this loadout has spent — and a card reads the way a mastery
   node does: emblem, copy, one learn/refund control. */
.atn-pane { display: flex; flex: 1; flex-direction: column; gap: 8px; min-height: 0; overflow: hidden; }
/* The rail is the whole ladder, so it spans the row: every slot takes an equal
   share of the width whatever the season's slot count is. */
.atn-slots {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  flex: 0 0 auto;
  gap: 6px;
}
.atn-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 36px;
  padding: 3px 8px;
  border: 1px dashed #344052;
  border-radius: 9px;
  background: var(--panel-2);
}
.atn-slot.is-filled {
  border-style: solid;
  border-color: color-mix(in srgb, var(--accent) 62%, #344052);
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold) 11%, transparent), transparent), var(--panel-2);
}
.atn-slot-empty { color: var(--muted); font-size: calc(10px * var(--ui-scale)); }
/* A slot the level curve has not opened. It stays on the rail so the ladder is
   visible from the first slot, and names the level it is waiting on. */
.atn-slot.is-locked { border-style: dashed; border-color: #2a323e; background: rgba(8,12,18,.3); }
.atn-slot-level {
  color: #6f7c8c;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: calc(10px * var(--ui-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
}
/* The rung the character is next in line for — every slot of it, since a rung
   can open more than one — lit the way a point badge lights its own next level. */
.atn-slot-level.is-next { color: color-mix(in srgb, var(--accent) 78%, var(--muted)); }
.atn-slot-card {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--text);
  cursor: var(--cursor-action);
  font-size: calc(11px * var(--ui-scale));
}
/* A long card name trims rather than widening its share of the rail. */
.atn-slot-card > strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.atn-slot-crest {
  display: grid;
  place-items: center;
  flex: none;
  width: 22px;
  height: 22px;
  border: 1px solid #465263;
  border-radius: 50%;
  background: #111822;
  overflow: hidden;
}
.atn-slot-crest img { width: 18px; height: 18px; }

/* One flat catalog of choices, the way the Abilities list is. Attunements are
   not a path the character walks, so nothing here is grouped into sections: a
   card's family is one chip it wears, and the toolbar filters by it. */
.atn-catalog { display: flex; flex: 1; flex-direction: column; gap: 3px; margin: 0; padding: 0 4px 0 0; list-style: none; min-height: 0; overflow-y: auto; }
.atn-card {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center;
  flex: 0 0 auto;
  gap: 8px;
  padding: 5px 7px;
  border: 1px solid #344052;
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(255,255,255,.025), rgba(0,0,0,.1)), var(--panel);
  transition: border-color .12s, background-color .12s, transform .12s;
}
.atn-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 6px; min-width: 0; }
.atn-card-family {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px 1px 2px;
  border: 1px solid #303b49;
  border-radius: 999px;
  background: rgba(8,12,18,.42);
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 750;
  letter-spacing: .04em;
  text-transform: uppercase;
}
/* A family whose cap this loadout has spent says so on every card that wears
   it, because it is the reason the rest of them refuse to go in. */
.atn-card-family.is-capped { border-color: color-mix(in srgb, var(--accent) 52%, #303b49); color: var(--gold-label); }
.atn-card-family-crest {
  display: grid;
  place-items: center;
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #111822;
  overflow: hidden;
}
.atn-card-family-crest img { width: 14px; height: 14px; }
/* Colour says what the Abilities catalog uses it for: gold is held, and a card
   the loadout has no room for dims. */
.atn-card:hover { border-color: #59687c; }
.atn-card.is-held {
  border-color: color-mix(in srgb, var(--accent) 58%, #344052);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}
.atn-card.is-blocked { opacity: .5; filter: saturate(.45); }
/* A card the character has not acquired. It stays listed and readable so a
   player can see what they are working toward, but it never looks available. */
.atn-card.is-locked { filter: grayscale(.7); }
.atn-card.is-locked.is-blocked { opacity: .5; filter: grayscale(.7) saturate(.45); }
.atn-card-icon {
  position: relative;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 2px solid #465263;
  border-radius: 50%;
  background: #10161f;
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.45);
}
.atn-card-icon img { width: 24px; height: 24px; }
.atn-card.is-held .atn-card-icon {
  border-color: var(--accent);
  box-shadow: 0 0 13px color-mix(in srgb, var(--gold) 24%, transparent), inset 0 0 0 2px rgba(0,0,0,.4);
}
.atn-card:hover:not(.is-held):not(.is-blocked) .atn-card-icon { border-color: #59687c; }
.atn-card-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.atn-card-copy > strong { font-size: calc(12px * var(--ui-scale)); }
/* Every move of every lane as one chip: emblem, signed amount, lane. The row
   wraps so a four-lane card stays as scannable as a two-lane one. */
.atn-card-trade { display: flex; flex-wrap: wrap; gap: 2px 3px; margin: 0; padding: 0; list-style: none; }
.atn-mod {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-width: 0;
  padding: 1px 5px 1px 2px;
  border: 1px solid #303b49;
  border-radius: 6px;
  background: rgba(8,12,18,.42);
  font-size: calc(9.5px * var(--ui-scale));
  line-height: 1.3;
  white-space: nowrap;
}
.atn-mod-icon { flex: none; width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale)); object-fit: contain; filter: drop-shadow(0 1px 1px rgba(0,0,0,.5)); }
.atn-mod > b { font-variant-numeric: tabular-nums; font-weight: 850; }
.atn-mod > span { overflow: hidden; color: #cad4df; text-overflow: ellipsis; }
.atn-mod.is-gain { border-color: #3f5c46; background: rgba(111,201,138,.1); }
.atn-mod.is-gain > b { color: #8fd6a4; }
.atn-mod.is-cost { border-color: #5f4340; background: rgba(220,118,95,.1); }
.atn-mod.is-cost > b { color: #efb2a9; }
.atn-mod.is-conditional { border-style: dashed; }
.atn-card .adv-build-choice { min-height: 24px; padding: 3px 6px; min-width: 74px; }

/* The attunement explanation card, in the same hover portal the ability detail
   uses. A slot on the rail carries a name and nothing else, so this is where a
   player reads what they are actually holding: the trade, both halves, in full
   sentences rather than the catalog row's packed chips. */
.adv-skill-flyout .atn-detail { max-height: min(84vh, 720px); }
.atn-detail {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  padding: 11px;
  border: 1px solid #45526580;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(24,30,40,.98), rgba(14,19,27,.99));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
}
.atn-detail-header { display: flex; align-items: center; gap: 8px; }
.atn-detail-identity { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.atn-detail-identity h3 { margin: 0; color: var(--text); font-size: calc(15px * var(--ui-scale)); line-height: 1.2; }
.atn-detail-family { color: var(--accent); font-size: calc(10px * var(--ui-scale)); font-weight: 800; letter-spacing: .05em; text-transform: uppercase; }
.atn-detail-crest { display: grid; place-items: center; flex: none; width: 34px; height: 34px; border: 1px solid #465263; border-radius: 50%; background: #111822; overflow: hidden; }
.atn-detail-crest img { width: 28px; height: 28px; }
.atn-detail-summary { margin: 0; color: #c6d0dc; font-size: calc(11px * var(--ui-scale)); line-height: 1.4; }
.atn-detail-trade { display: flex; flex-direction: column; gap: 3px; }
.atn-detail-trade > strong { color: var(--muted); font-size: calc(9.5px * var(--ui-scale)); font-weight: 850; letter-spacing: .07em; text-transform: uppercase; }
.atn-detail-moves { display: flex; flex-direction: column; gap: 3px; margin: 0; padding: 0; list-style: none; }
.atn-detail-moves > li {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 7px;
  border: 1px solid #303b49;
  border-radius: 7px;
  background: rgba(8,12,18,.42);
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.3;
}
.atn-detail-moves > li.is-gain { border-color: #3f5c46; background: rgba(111,201,138,.1); color: #a9dfb9; }
.atn-detail-moves > li.is-cost { border-color: #5f4340; background: rgba(220,118,95,.1); color: #f0c0b8; }
.atn-detail-move-icon { flex: none; width: calc(16px * var(--ui-scale)); height: calc(16px * var(--ui-scale)); object-fit: contain; filter: drop-shadow(0 1px 1px rgba(0,0,0,.5)); }
.atn-detail-note { margin: 0; color: var(--muted); font-size: calc(9.5px * var(--ui-scale)); line-height: 1.35; }
.atn-detail-blocked { margin: 0; color: #efb2a9; font-size: calc(10px * var(--ui-scale)); line-height: 1.35; }

/* Four plates across stop holding a card name well before the frame does, so
   the rail folds to two columns and keeps filling the row. */
@media (max-width: 480px) {
  .atn-slots { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .win-body.skill-mode-adventurer {
    height: min(68vh, calc(560px * var(--ui-scale, 1)));
    padding: 7px;
  }
  .adv-build-tabs { position: sticky; top: 0; z-index: 2; }
  .adv-role-layout { grid-template-columns: minmax(108px, 130px) minmax(0, 1fr); }
  .adv-role-primer { grid-template-columns: 1fr; }
}

/* The condensed row is a name plus one activation line, so icon | copy | action
   stays legible far narrower than it did when the row also carried tag chips. */
@media (max-width: 380px) {
  .adv-active-card { grid-template-columns: 28px minmax(0, 1fr); }
  .adv-active-actions { grid-column: 1 / -1; flex-direction: row; justify-content: flex-end; }
  .adv-role-node { grid-template-columns: 30px minmax(0, 1fr); }
  .adv-role-action { grid-column: 1 / -1; flex-direction: row; justify-content: flex-end; }
  .atn-card { grid-template-columns: 30px minmax(0, 1fr); }
  .atn-card .adv-build-choice { grid-column: 1 / -1; }
}

/* The desktop cockpit dissolves the rail box and keeps its members as items of
   the window's own column. */
.adv-build-rail { display: contents; }

/* A handheld glass is wide and short, so the build flows left to right instead
   of restacking the desktop column: tabs one row across the top, the points /
   refund / attack-mix chrome in a left rail, and the catalog pane at full
   height beside it. The rail scrolls when squeezed, so no glass can clip a
   control out of reach. Row gaps ride margins, not the grid, so the empty
   notice row costs nothing while no notice is up. */
[data-ui-profile="handheld"] .win-skills .win-body.skill-mode-adventurer {
  display: grid;
  grid-template-columns: clamp(176px, 30%, 220px) minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
  align-content: stretch;
  gap: 0 10px;
  overflow: hidden;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-tabs {
  grid-row: 1;
  grid-column: 1 / -1;
  position: static;
  margin-bottom: 6px;
}
/* 34px, not the 44px chrome floor: three floor-height tabs would spend a fifth
   of the shortest glass on one row, and at a third of the window wide each the
   press area is already generous. */
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-tab { min-height: 34px; }
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-notice {
  grid-row: 2;
  grid-column: 1 / -1;
  margin-bottom: 6px;
  padding: 4px 8px;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-rail {
  display: flex;
  flex-direction: column;
  gap: 6px;
  grid-row: 3;
  grid-column: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-header {
  flex: 0 0 auto;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 5px;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-point-row { flex-direction: column; }
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix {
  flex: 0 0 auto;
  padding: 5px 8px 6px;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-active-pane,
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-role-pane,
[data-ui-profile="handheld"] .skill-mode-adventurer .atn-pane {
  grid-row: 3;
  grid-column: 2;
}

/* The mix control is three columns wide on the desktop row; in the rail the
   two sides share one row and the slider takes the next, full width. */
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix__control {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: 'physical magical' 'slider slider';
  gap: 2px 7px;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix__side.is-physical { grid-area: physical; }
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix__side.is-magical {
  grid-area: magical;
  justify-self: end;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix input[type='range'] { grid-area: slider; }
/* Inside a scrolling rail an overlay would open the rail sideways instead of
   escaping it, so the projection is a disclosure here: the preview chip folds
   it open in the rail's own flow, and the rail scrolls it into reach. */
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix__projection {
  display: none;
  position: static;
  width: 100%;
  transform: none;
  transition: none;
}
[data-ui-profile="handheld"] .skill-mode-adventurer .adv-build-attack-mix:has(.adv-build-attack-mix__preview[aria-expanded='true']) .adv-build-attack-mix__projection {
  display: flex;
  visibility: visible;
  opacity: 1;
}

[data-ui-profile="handheld"] .adv-role-layout {
  grid-template-columns: minmax(96px, 120px) minmax(0, 1fr);
}

[data-ui-profile="handheld"] .adv-role-primer {
  grid-template-columns: 1fr;
}

/* A touch-selected skill keeps its detail in the narrow chrome rail instead of
   covering the catalog actions. The portal lives outside `.win-body`, so it
   must explicitly take vertical panning back from the pinned play shell. */
[data-ui-profile="handheld"] .adv-skill-flyout {
  width: min(220px, calc(30vw - 12px));
  pointer-events: auto;
}

[data-ui-profile="handheld"] .adv-skill-flyout .adv-active-detail,
[data-ui-profile="handheld"] .adv-skill-flyout .atn-detail {
  max-height: min(64dvh, calc(var(--hud-window-max-h) - 44px));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
}
/* ============================================================================
   Character sheet (game Alt+A) — redesigned.

   Goal: an MMORPG "character cockpit", not a settings dashboard. Depth, framed
   emblem tiles, engraved dividers, and per-attribute identity colour — all built
   from the shared tokens in styles.css. Inner sizes track --ui-scale; the window
   WIDTH stays fixed (like win-inv / win-skills). Motion is gated behind
   reduced-motion; borders thicken under high-contrast.
   ============================================================================ */

/* Gear beside the readout, one arrangement at every width. The portrait is the
   elastic part: it stands as tall as the ten stat lines, which is what makes the
   two columns the same height and leaves no band for either to pad out. */
.window.win-stats {
  width: calc(822px * var(--ui-scale));
  max-width: 96vw;
}

/* The stats element mounts DIRECTLY under .window (no generic .win-body wrapper,
   unlike the inventory), so .stats-grid IS the window body: it owns the inset
   padding and the single-flow equipment + stats layout. */
.stats-grid {
  display: flex; flex-direction: column; gap: calc(7px * var(--ui-scale));
  padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale)) calc(8px * var(--ui-scale));
  box-sizing: border-box;
  overflow: visible;
  container-type: inline-size;
  /* The stat row's metrics, stated once. The readout builds its rows from them
     and the portrait plate takes its height from the column they add up to. */
  --stat-row-emblem: calc(22px * var(--ui-scale));
  --stat-row-pad-top: calc(3px * var(--ui-scale));
  --stat-row-pad-bottom: calc(7px * var(--ui-scale));
  --stat-row-gap: calc(3px * var(--ui-scale));
  --stat-row-height: calc(
    var(--stat-row-emblem) + var(--stat-row-pad-top) + var(--stat-row-pad-bottom) + 2px);
  /* Ten rows and the nine gaps between them, less the paperdoll's own padding. */
  --stat-column-height: calc(
    10 * var(--stat-row-height) + 9 * var(--stat-row-gap) - calc(8px * var(--ui-scale)));
  background:
    radial-gradient(120% 80% at 50% -10%, color-mix(in srgb, var(--gold) 6%, transparent), transparent 60%),
    linear-gradient(180deg, #131a25 0%, #10151d 100%);
}

/* One arrangement at every width: the two columns scale, they never reflow. The
   readout carries the floor because a stat nobody can read is worse than a
   smaller portrait, so the gear column is what gives ground as the sheet
   narrows. Both tracks state a min of their own; a track left at auto is sized
   by its content and takes the space out of its neighbour. */
.stats-sheet-content {
  display: grid;
  grid-template-columns:
    minmax(0, calc(444px * var(--ui-scale)))
    minmax(min(calc(300px * var(--ui-scale)), 100%), 1fr);
  align-items: start;
  gap: calc(10px * var(--ui-scale));
  min-width: 0;
}

.stats-grid[data-viewport-scroll] {
  max-height: var(--stats-available-height);
  overflow-y: auto;
}

.stats-gear { min-width: 0; }
.stats-readout { min-width: 0; }

/* The slot columns run down both edges of a portrait plate as tall as the ten
   stat lines beside it, and the figure is drawn to the plate's full height. The
   plate is never wider than the figure's own proportions: a wider one is a
   letterbox with a character adrift in the middle of it.

   The plate's height is the stat column's height, derived from the row metrics
   rather than restated: change a row's padding or the emblem and both sides of
   the sheet move together. */
.stats-gear .paperdoll {
  align-items: center;
  max-width: 100%;
  gap: calc(6px * var(--ui-scale));
  margin-bottom: 0;
  padding: calc(4px * var(--ui-scale));
}
.stats-gear .paperdoll .pd-col { gap: calc(6px * var(--ui-scale)); }
.stats-gear .paperdoll .pd-slot {
  --slot-size: clamp(calc(28px * var(--ui-scale)), 7cqw, calc(52px * var(--ui-scale)));
}
.stats-gear .paperdoll .pd-mannequin {
  flex: 1 1 auto;
  /* Ten stat lines and the gaps between them, less this block's own padding.
     Stated so the image below has a definite height to fill; without one it
     falls back to its intrinsic size. */
  height: var(--stat-column-height);
  min-width: 0;
  max-width: calc(320px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale));
}
.stats-gear .paperdoll .pd-mannequin-img { max-height: 100%; }

/* The heading wraps rather than squeezing its label: the stat names are
   translated and a longer one must not push the row out of line. */
.attr-head { position: relative; flex-wrap: wrap; }
.attr-head > span { min-width: 0; }
/* ---- Equipment panel (paperdoll host) -------------------------------------- */
.equipment-panel { display: flex; flex-direction: column; gap: calc(8px * var(--ui-scale)); }
.equip-empty { text-align: center; color: var(--muted); padding: calc(8px * var(--ui-scale)); }

/* ---- Identity header -------------------------------------------------------
   Character name as the hero over its affiliations, level read-out as a
   struck-metal chip, on a gold-edged plate underlined by a thin gold rule like
   an engraved nameplate.

   Two zones on one band, mirroring the self-status HUD: the name leads with
   faction + guild affiliations stacked beneath it, and the level read-out sits
   at the right edge so the header fills its width. */
.stats-identity {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; column-gap: calc(8px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(8px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border-radius: 9px;
  border: 1px solid #2f3a4c; border-top-color: #3b475b;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--gold) 16%, transparent), color-mix(in srgb, var(--gold) 2%, transparent) 55%, transparent),
    linear-gradient(180deg, #1b2331, #141b26);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Engraved gold underline. */
.stats-identity::after {
  content: ''; position: absolute; left: calc(8px * var(--ui-scale)); right: calc(8px * var(--ui-scale)); bottom: 3px;
  height: 1px; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--gold) 55%, transparent), transparent);
}
.si-hero {
  grid-column: 1; min-width: 0;
  display: flex; flex-flow: row wrap; align-items: center; gap: calc(6px * var(--ui-scale));
}
.si-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 0 1 auto;
  color: var(--text); font-size: calc(14px * var(--ui-scale)); font-weight: 800; line-height: 1.15;
}
/* Faction chip leads, the smaller guild affiliation follows it — same order and
   divider treatment as the HUD self-status panel. */
.si-affiliations {
  display: flex; align-items: center; gap: calc(6px * var(--ui-scale));
  flex: 1 1 calc(220px * var(--ui-scale)); min-width: 0; flex-wrap: wrap;
  padding-left: calc(7px * var(--ui-scale));
  border-left: 1px solid #2c3646;
}
.si-faction { min-width: 0; }
/* Right zone: the level read-out, right-aligned so the number sits at the frame
   edge and the block fills the header's height. */
.si-side {
  grid-column: 2; display: flex; flex-direction: column; align-items: flex-end;
  gap: calc(2px * var(--ui-scale)); min-width: 0;
}

/* HUD identity chips (faction + player/guild), shared across self-status + stats sheet. */
.identity-chip {
  display: inline-flex;
  align-items: center;
  gap: calc(7px * var(--ui-scale));
  min-width: 0;
  max-width: 100%;
}
.identity-icon {
  position: relative;
  flex: 0 0 auto;
  width: calc(24px * var(--ui-scale));
  height: calc(24px * var(--ui-scale));
  border-radius: 50%;
  border: 1px solid var(--identity-ring, var(--accent));
  background: color-mix(in srgb, var(--identity-ring, var(--accent)) 14%, rgba(8, 12, 18, 0.55));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 2px 8px rgba(0, 0, 0, 0.35);
  object-fit: contain;
  padding: calc(4px * var(--ui-scale));
  box-sizing: border-box;
  overflow: hidden;
}
.identity-chip--md .identity-icon {
  width: calc(32px * var(--ui-scale));
  height: calc(32px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale));
}
.identity-icon--guild { display: grid; place-items: center; padding: 0; }
.identity-icon__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}
.identity-body {
  display: flex;
  flex-direction: column;
  gap: calc(1px * var(--ui-scale));
  min-width: 0;
}
.identity-body--single { justify-content: center; }
.identity-primary {
  font-weight: 800;
  font-size: calc(13px * var(--ui-scale));
  line-height: 1.15;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.identity-chip--md .identity-primary { font-size: calc(15px * var(--ui-scale)); }
.si-faction-chip .identity-primary {
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: .35px;
  color: color-mix(in srgb, var(--text) 88%, var(--muted));
}
.identity-secondary {
  font-size: calc(10px * var(--ui-scale));
  font-weight: 600;
  line-height: 1.15;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.identity-chip--md .identity-secondary { font-size: calc(11px * var(--ui-scale)); }

/* Pending header has no name, so the sync message carries the header at full size. */
.stats-pending-title {
  font-size: calc(15px * var(--ui-scale)); font-weight: 700; letter-spacing: .3px; color: var(--accent);
}
/* Level read-out: clean stat callouts (big number + micro label), split by a
   hairline — no boxy "tag" chrome. */
.si-levels { display: flex; align-items: center; }
.si-lv {
  display: inline-flex; flex-direction: column; align-items: center; gap: 0;
  padding: 0 calc(8px * var(--ui-scale)); line-height: 1;
}
.si-lv + .si-lv { border-left: 1px solid #2c3646; }
.si-lv-k { font-size: calc(9px * var(--ui-scale)); text-transform: uppercase; letter-spacing: .7px; color: var(--muted); margin-top: 2px; }
.si-lv-v { font-size: calc(17px * var(--ui-scale)); font-weight: 800; color: #f0dcac; font-variant-numeric: tabular-nums; }

.stats-pending-head { grid-template-columns: 1fr; }
.stats-pending-note { color: var(--muted); font-size: calc(11px * var(--ui-scale)); line-height: 1.35; }

/* ---- Section heads ---------------------------------------------------------
   Colour-ticked, small-caps, with a section glyph badge. */
.col-head {
  display: flex; align-items: center; gap: calc(7px * var(--ui-scale));
  font-size: calc(10px * var(--ui-scale)); text-transform: uppercase; letter-spacing: .7px;
  font-weight: 700; color: #aeb8c8;
  padding-bottom: calc(3px * var(--ui-scale)); margin-bottom: calc(3px * var(--ui-scale)); border-bottom: 1px solid #232c3a;
}
.col-head::before {
  content: ''; flex: 0 0 auto; width: 3px; height: calc(12px * var(--ui-scale));
  border-radius: 2px; background: var(--grp, var(--accent)); box-shadow: 0 0 8px var(--grp-glow, transparent);
}

/* ---- Stat-group colour coding ---------------------------------------------- */
.grp-attr { --grp: var(--accent); --grp-soft: color-mix(in srgb, var(--gold) 14%, transparent); --grp-glow: color-mix(in srgb, var(--gold) 50%, transparent); }

/* ---- Icon system ----------------------------------------------------------- */
.stat-ico { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; color: var(--grp, var(--accent)); }
.stat-ico-img { width: 82%; height: 82%; object-fit: contain; image-rendering: auto; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); pointer-events: none; user-select: none; -webkit-user-drag: none; }

/* Premium framed tile — a faceted metal plate. Attribute tiles glow with their
   identity colour; the emblem sits on a dark inset so it reads at a glance. */
.stat-ico--tile {
  position: relative;
  width: calc(42px * var(--ui-scale)); height: calc(42px * var(--ui-scale));
  border-radius: 9px;
  background: linear-gradient(160deg, #232d3c 0%, #161d29 100%);
  border: 1px solid #313c4e; border-top-color: #3e4b60; border-bottom-color: #232c3a;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), inset 0 -3px 6px rgba(0, 0, 0, 0.35), 0 2px 4px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}
.stat-ico--tile .stat-ico-img { width: 96%; height: 96%; }
/* Corner spark on the tile for a struck-metal highlight. */
.stat-ico--tile::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(60% 50% at 30% 20%, rgba(255, 255, 255, 0.1), transparent 70%);
  pointer-events: none;
}
/* Per-stat identity glow behind the emblem, driven by data-stat. Keys match
   `ASSIGNABLE_STAT_KEYS` in ui-types (vcast, aspd, def, mdef, pdmg, mdmg,
   dodge, maxHpStat, maxSpStat, crit). */
.stat-ico--attr[data-stat] { --emblem: var(--accent); }
.stat-ico--attr[data-stat="vcast"]    { --emblem: #b483e0; } /* arcane purple */
.stat-ico--attr[data-stat="aspd"]     { --emblem: #5fc76a; } /* bright green */
.stat-ico--attr[data-stat="def"]      { --emblem: #5a9bff; } /* steel blue */
.stat-ico--attr[data-stat="mdef"]     { --emblem: #37bda8; } /* teal */
.stat-ico--attr[data-stat="pdmg"]     { --emblem: #e0574a; } /* ember red */
.stat-ico--attr[data-stat="mdmg"]     { --emblem: #5aa0e0; } /* arcane blue */
.stat-ico--attr[data-stat="dodge"]    { --emblem: #e0c34a; } /* swift gold */
.stat-ico--attr[data-stat="maxHpStat"]{ --emblem: #e0578a; } /* vital pink */
.stat-ico--attr[data-stat="maxSpStat"]{ --emblem: #5a7ae0; } /* azure */
.stat-ico--attr[data-stat="crit"]     { --emblem: #e0a53a; } /* amber */
.stat-ico--attr {
  border-color: color-mix(in srgb, var(--emblem) 42%, #313c4e);
  background:
    radial-gradient(75% 65% at 50% 80%, color-mix(in srgb, var(--emblem) 34%, transparent), transparent 72%),
    linear-gradient(160deg, #232d3c 0%, #161d29 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 -3px 6px rgba(0, 0, 0, 0.35),
    0 0 10px color-mix(in srgb, var(--emblem) 30%, transparent), 0 2px 4px rgba(0, 0, 0, 0.3);
}

/* ---- Stat readout ----------------------------------------------------------
   One flat list of ten lines. Each is an engraved plate carrying the stat's
   emblem, its name, the number the server resolved, and a bar underneath
   splitting that number by where it came from. The bar is always the whole of
   its own value, so a capped line and an uncapped one are read the same way. */
/* One column, one row per stat. Everything a line says stands on a single
   baseline and the bar is the line's own underline rather than a second row, so
   eleven stats cost eleven rows of height instead of twenty-two. */
.stat-list {
  display: flex;
  flex-direction: column;
  gap: var(--stat-row-gap);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.stat-line {
  position: relative;
  display: grid;
  grid-template-columns:
    auto minmax(0, 1fr) calc(64px * var(--ui-scale)) calc(54px * var(--ui-scale));
  align-items: center;
  column-gap: calc(7px * var(--ui-scale));
  padding: var(--stat-row-pad-top) calc(8px * var(--ui-scale)) var(--stat-row-pad-bottom);
  border-radius: 6px;
  border: 1px solid #29323f;
  border-left: calc(2px * var(--ui-scale)) solid var(--stat-accent, var(--accent));
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--stat-accent, var(--accent)) 9%, transparent), transparent 34%),
    linear-gradient(180deg, #191f2a, #141922);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
  cursor: help;
  transition: border-color .12s ease, background .12s ease;
}

/* The line borrows its stat's identity colour from the emblem tile beside it,
   so a glance down the list separates offense from survival without a heading
   over either. */
.stat-line[data-stat-key="vcast"]     { --stat-accent: #b483e0; }
.stat-line[data-stat-key="aspd"]      { --stat-accent: #5fc76a; }
.stat-line[data-stat-key="def"]       { --stat-accent: #5a9bff; }
.stat-line[data-stat-key="mdef"]      { --stat-accent: #37bda8; }
.stat-line[data-stat-key="pdmg"]      { --stat-accent: #e0574a; }
.stat-line[data-stat-key="mdmg"]      { --stat-accent: #5aa0e0; }
.stat-line[data-stat-key="dodge"]     { --stat-accent: #e0c34a; }
.stat-line[data-stat-key="maxHpStat"] { --stat-accent: #e0578a; }
.stat-line[data-stat-key="maxSpStat"] { --stat-accent: #5a7ae0; }
.stat-line[data-stat-key="crit"]      { --stat-accent: #e0a53a; }

.stat-line:is(:hover, :focus-visible) {
  border-color: color-mix(in srgb, var(--stat-accent, var(--accent)) 62%, #29323f);
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--stat-accent, var(--accent)) 16%, transparent), transparent 40%),
    linear-gradient(180deg, #1d2431, #161c26);
}
/* Keyboard focus is a ring, never the hover tint: ten rows are ten tab stops and
   a pointer user and a keyboard user must not be shown the same thing. */
.stat-line:focus-visible {
  outline: calc(2px * var(--ui-scale)) solid var(--accent);
  outline-offset: calc(1px * var(--ui-scale));
}

.stat-line .stat-ico--tile {
  width: var(--stat-row-emblem);
  height: var(--stat-row-emblem);
}

.stat-line-name {
  grid-column: 2;
  min-width: 0;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: .1px;
  color: color-mix(in srgb, var(--text) 88%, var(--muted));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stat-line-value {
  grid-column: 3;
  text-align: right;
  font-size: calc(15px * var(--ui-scale));
  font-weight: 800;
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* What the bonuses add, in the stat's own unit, on the lines they moved. It is
   the same gold as the bar's bonus segment on purpose: the number and the
   length are one fact shown twice, which is what saves the bar from needing a
   legend to decode it. */
.stat-line-gain {
  grid-column: 4;
  text-align: right;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.stat-bar {
  position: absolute;
  left: calc(8px * var(--ui-scale));
  right: calc(8px * var(--ui-scale));
  bottom: calc(3px * var(--ui-scale));
  height: calc(3px * var(--ui-scale));
  border-radius: 999px;
  background: rgba(137, 151, 173, .16);
  overflow: hidden;
}
.stat-bar > span {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--stat-from, 0%);
  width: var(--stat-fill, 0%);
}
.stat-bar-gear { background: linear-gradient(90deg, #4c637f, #6d87a8); }
.stat-bar-build {
  background: linear-gradient(90deg, var(--gold), #f0dcac);
  box-shadow: 0 0 6px color-mix(in srgb, var(--gold) 55%, transparent);
}

/* A stat standing on its ceiling: the bar runs into a wall. Combat refuses to
   resolve it any further, and a stop at the end of the track is that refusal
   without a sentence about it. The number itself is in the tooltip. */
.stat-line[data-at-ceiling] .stat-bar {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}
/* The stop sits on top of the bonus segment, which is itself pale gold, so it
   carries its own dark edge. A bright mark on a bright bar is no mark. */
.stat-line[data-at-ceiling] .stat-bar::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: calc(5px * var(--ui-scale));
  background: #ffffff;
  border-left: calc(2px * var(--ui-scale)) solid #171c25;
  box-sizing: border-box;
}

.stat-line--pending { opacity: .82; cursor: default; }

/* ---- Section wrapper -------------------------------------------------------
   A label + body block. Used for both Stats and equipment. */
.stats-section { display: flex; flex-direction: column; gap: calc(4px * var(--ui-scale)); }

/* ---- Accessibility ----------------------------------------------------------
   High contrast thickens the plate edges and drops the identity wash; the bar's
   two tones separate by lightness, not by hue alone. */
body.high-contrast .stats-identity,
body.high-contrast .stat-ico--tile { border-width: 2px; }
body.high-contrast .stat-line {
  border-width: 2px;
  background: linear-gradient(180deg, #1b2230, #12171f);
}
body.high-contrast .stat-line:is(:hover, :focus-visible) { outline: 2px solid currentColor; }
body.high-contrast .stat-bar { background: rgba(132, 147, 168, .34); }
body.high-contrast .stat-bar-gear { background: #8fa3bd; }
body.high-contrast .stat-bar-build { background: #f4e6c2; box-shadow: none; }
body.high-contrast .stat-line[data-at-ceiling] .stat-bar::after {
  background: #ffffff;
  box-shadow: none;
}

body.reduce-motion .stat-line { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .stat-line { transition: none; }
}
/* The refund control. One definition, worn by every surface that hands a spend
   back: the Skills window's build reset and the character sheet's stat reset.
   Refund icon, gold-on-bronze pill, one press to arm and a second to send —
   `is-confirming` is the armed state. Matches the per-node Refund buttons in
   the skill tree so the whole vocabulary reads as one action. */
.refund-button {
  display: inline-flex; align-items: center; gap: calc(5px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: 1px solid #8f7046; border-radius: 999px;
  background-color: #271f17; color: #f1d59a;
  font-size: calc(9px * var(--ui-scale)); font-weight: 800;
  text-transform: uppercase; letter-spacing: .4px; line-height: 1;
  cursor: var(--cursor-action);
}
.refund-button img {
  width: calc(14px * var(--ui-scale)); height: calc(14px * var(--ui-scale));
  flex: none; object-fit: contain;
}
.refund-button:hover:not(:disabled) {
  border-color: #c19a5b;
  background-color: #33281c;
  transform: translateY(-1px);
}
.refund-button:active:not(:disabled) { transform: translateY(0); }
.refund-button.is-confirming {
  border-color: color-mix(in srgb, var(--gold) 72%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold) 22%, transparent), color-mix(in srgb, var(--gold) 6%, transparent));
}
/* Hover falls through to the wrapper so its title tooltip still shows while the
   button is disabled (browsers suppress a disabled control's own tooltip). */
.refund-button:disabled {
  pointer-events: none; cursor: not-allowed;
  filter: grayscale(1); opacity: .42;
}
.refund-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ============================================================================
   Inventory (Bag) — redesigned to match the Character window: a bag header,
   category filter chips, a beveled slot grid (filled + empty cells), and the
   labelled weight/gold footer. Loads after styles.css so it refines the base
   inv-* rules. All sizes track --ui-scale; motion honours reduced-motion.
   ============================================================================ */

.inv-window { display: flex; flex-direction: column; gap: calc(7px * var(--ui-scale)); }

/* ---- Header: bag emblem · title · count ------------------------------------ */
.inv-header {
  display: flex; align-items: center; gap: calc(7px * var(--ui-scale));
  padding-bottom: calc(5px * var(--ui-scale)); border-bottom: 1px solid var(--ui-line-soft);
}
.inv-hd-ico { display: inline-flex; color: var(--accent); filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4)); }
.inv-hd-ico .inv-ico-img { width: calc(20px * var(--ui-scale)); height: calc(20px * var(--ui-scale)); object-fit: contain; display: block; }
.inv-hd-title { font-size: calc(12px * var(--ui-scale)); font-weight: 800; letter-spacing: .6px; text-transform: uppercase; color: var(--accent); }
.inv-hd-count {
  margin-left: auto; font-size: calc(12px * var(--ui-scale)); font-weight: 800; color: #c7ceda;
  font-variant-numeric: tabular-nums;
}

/* ---- Category filter chips (icon · label · count) -------------------------- */
.inv-filters { display: flex; gap: calc(4px * var(--ui-scale)); }
.inv-filter {
  flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center;
  gap: calc(4px * var(--ui-scale)); padding: calc(4px * var(--ui-scale)) calc(5px * var(--ui-scale));
  border: 1px solid var(--ui-line); border-radius: 5px; background: rgba(25, 35, 38, 0.86);
  color: var(--muted); cursor: var(--cursor-action); transition: color .12s ease, border-color .12s ease, background .12s ease;
}
.inv-filter-ico { display: inline-flex; flex: 0 0 auto; }
.inv-filter-ico .inv-ico-img { width: calc(17px * var(--ui-scale)); height: calc(17px * var(--ui-scale)); object-fit: contain; display: block; }
.inv-filter-label {
  font-size: calc(11px * var(--ui-scale)); font-weight: 700; letter-spacing: .2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.inv-filter-count {
  flex: 0 0 auto; font-size: calc(9.5px * var(--ui-scale)); font-weight: 800; color: #8a93a3;
  background: rgba(0, 0, 0, 0.32); border-radius: 999px; padding: 0 calc(5px * var(--ui-scale));
  min-width: calc(16px * var(--ui-scale)); text-align: center; font-variant-numeric: tabular-nums;
}
.inv-filter:hover { border-color: #3a4658; color: #c7ceda; }
.inv-filter.active {
  color: var(--accent); border-color: color-mix(in srgb, var(--gold) 50%, transparent);
  background: rgba(201, 164, 94, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.inv-filter.active .inv-filter-count { color: var(--accent); background: color-mix(in srgb, var(--gold) 16%, transparent); }
.inv-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.high-contrast .inv-filter { border-width: 2px; }
@media (max-width: 460px) { .inv-filter-label { display: none; } }

/* ---- Toolbar: search + sort ------------------------------------------------ */
.inv-toolbar { display: flex; gap: calc(6px * var(--ui-scale)); align-items: stretch; }

/* ---- Slot grid ------------------------------------------------------------- */
.inv-grid {
  display: grid; grid-template-columns: repeat(auto-fill, calc(44px * var(--ui-scale)));
  gap: calc(3px * var(--ui-scale)); justify-content: start; box-sizing: border-box;
  align-content: start;
}
/* Beveled slot cell — same frame language as the equipment paperdoll. */
.inv-cell {
  position: relative; aspect-ratio: 1; min-height: calc(42px * var(--ui-scale)); width: 100%; min-width: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: calc(5px * var(--ui-scale));
  border: 1px solid var(--inv-rarity-frame, var(--ui-line));
  background: linear-gradient(145deg, #263438 0%, #11191c 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), inset 0 -2px 5px rgba(0, 0, 0, 0.28);
  cursor: var(--cursor-action); overflow: hidden;
  transition: border-color .12s ease, transform .08s ease, box-shadow .12s ease;
}
.inv-cell::before { display: none; }
.inv-cell:hover {
  border-color: var(--inv-rarity-frame, var(--ui-line));
  outline: 1px solid var(--accent); outline-offset: 1px;
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 4px 12px rgba(0, 0, 0, 0.42);
}
.inv-cell:active { transform: translateY(0); }
.inv-cell[aria-selected="true"],
.inv-cell--selected {
  border-color: var(--inv-rarity-frame, var(--ui-line));
  outline: 1px solid var(--accent);
  outline-offset: 1px;
}
.inv-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.inv-cell .inv-icon { width: 78%; height: 78%; object-fit: contain; position: relative; z-index: 1; pointer-events: none; }

/* Empty slot — subtle, non-interactive; gives a light bag real structure. */
.inv-cell-empty {
  cursor: var(--cursor-default); opacity: 0.5;
  background: #11191c;
  border-color: #2b383c;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.36);
}
.inv-cell-empty::before { display: none; }
.inv-cell-empty:hover { border-color: #2a3342; transform: none; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), inset 0 -2px 5px rgba(0, 0, 0, 0.3); }

/* Badges (refine ▲, quantity) ride above the icon. */
.inv-cell .inv-refine, .inv-cell .inv-amt { z-index: 2; }

/* Quest-item corner marker — a small gold wedge in the corner opposite the
   quantity badge. Colour is not the only cue: the slot's accessible label
   carries the same tag, and the item tooltip repeats it in words. */
.inv-cell .inv-quest {
  position: absolute; left: 0; top: 0; z-index: 2; width: 0; height: 0;
  border-top: calc(9px * var(--ui-scale)) solid var(--accent);
  border-right: calc(9px * var(--ui-scale)) solid transparent;
  pointer-events: none;
}

.inv-empty { grid-column: 1 / -1; text-align: center; color: var(--muted); padding: calc(18px * var(--ui-scale)); }

body.high-contrast .inv-cell { border-width: 2px; }

/* Keep the bag's two resource readouts on one compact baseline. */
.win-inv .inv-footer {
  gap: calc(8px * var(--ui-scale));
  padding-top: calc(6px * var(--ui-scale));
  margin-top: 0;
}
.win-inv .inv-weight { gap: calc(5px * var(--ui-scale)); }
.win-inv .inv-weight-ico .inv-ico-img,
.win-inv .inv-gold-ico .inv-ico-img {
  width: calc(17px * var(--ui-scale));
  height: calc(17px * var(--ui-scale));
}
.win-inv .inv-weight-text { font-size: calc(10px * var(--ui-scale)); }
.win-inv .inv-gold-val { font-size: calc(11px * var(--ui-scale)); }

/* ---- Item tooltip: typed capabilities -------------------------------------
   Each rolled option is a single human-readable effect phrase (from
   formatRandOpt) rendered on its own line. Long phrases (skill procs, elemental
   resistances) wrap instead of truncating, with a hanging indent so wrapped
   lines align under the text rather than the marker. */
.tt-opts { display: grid; gap: calc(3px * var(--ui-scale)); margin: calc(3px * var(--ui-scale)) 0; }
/* Band qualifier: the concentration profile or the filled trait slots, sitting
   beside its heading so the band answers "is this item finished?" at a glance. */
.tt-rowqual { float: right; text-transform: none; letter-spacing: 0; opacity: .85; }
/* Roll quality rides the right edge of its own line, dim and tabular so a column
   of them reads as one scale rather than as part of each stat's value. */
.tt-optq {
  float: right; margin-left: calc(6px * var(--ui-scale));
  color: var(--muted); font-weight: 500; font-variant-numeric: tabular-nums;
  font-size: calc(10px * var(--ui-scale));
}
/* Base is fixed on every copy of the definition, so it reads quieter than the
   rolled bands rather than competing with them. */
.tt-opts--base .tt-opt { color: var(--muted); border-left-color: color-mix(in srgb, var(--muted) 50%, transparent); }
/* An unfilled trait seat is potential, not content: a dashed edge and italic
   label keep it legible without letting it read as something the item has. */
.tt-opt--empty {
  color: var(--muted); font-weight: 500; font-style: italic;
  border-left-style: dashed;
  border-left-color: color-mix(in srgb, var(--muted) 45%, transparent);
}
.tt-opt {
  position: relative; padding-left: calc(8px * var(--ui-scale));
  border-left: calc(2px * var(--ui-scale)) solid color-mix(in srgb, var(--opt, var(--accent)) 60%, transparent);
  color: var(--opt, var(--accent)); font-size: calc(11.5px * var(--ui-scale));
  font-weight: 600; line-height: 1.32; overflow-wrap: break-word;
}
/* Empty option slots — a dim, italic hint so it never reads as a live effect. */
.tt-opt-hint { color: var(--muted); font-style: italic; font-size: calc(10.5px * var(--ui-scale)); opacity: .9; }

[data-ui-profile="handheld"] .inv-filter {
  min-height: var(--hud-touch-min);
}

[data-ui-profile="handheld"] .inv-filter-label {
  display: none;
}

[data-ui-profile="handheld"] .armory-onboarding-facts {
  grid-template-columns: 1fr;
}
/* Pre-game experience — the frame every screen is drawn in, and the atoms every
   screen is drawn from. The screens themselves are in the pre-game screens
   sheet, the session surfaces in the pre-game overlays sheet; all three are one
   design language, in-family with the in-game HUD. The frame and the session
   surfaces are scoped under `.menu-root`, `.menu-overlay` and `.menu-toast`; the
   atoms carry pre-game-only names (`pg-`, `cs-`, `login-`, `create-`, `delete-`)
   and are what keeps the rest off the HUD. See the Pre-Game Experience — Design
   Spec.

   The layout law, and the reason there are almost no breakpoints below:

   Every screen is one frame — a head, a middle, and a foot — that occupies
   exactly the glass and nothing more; the page itself never scrolls. The
   sign-in card lives in a scrolling middle: content that outgrows the device is
   reachable at any size without a rule written for that size, the card centres
   with `margin: auto` (auto margins collapse to zero the moment the card
   outgrows its box, where centring alignment would push its head above the
   scroll origin), and its decisive action sticks to the bottom of the middle.
   The character screens are drawn to the glass instead: the featured character
   is sized by the height the name and actions leave, and only a long roster and
   a tall form scroll, each inside its own box — see the pre-game screens sheet.

   Sizes come from `clamp()` against the viewport's own height, not from a
   breakpoint, so a short landscape phone condenses the same layout instead of
   being handed a different one. The width breakpoint says one thing only:
   whether the character roster stands beside the hero or under it. */

.menu-root,
.menu-overlay,
.menu-toast {
  /* Neutral ramp */
  --line: var(--ui-line);
  --line-strong: #4c5d60;
  --panel-3: var(--panel-raised);
  --ink: #1a1206;
  --text-2: #b9c0cc;

  /* Quiet working planes over world artwork. The frost itself is declared only
     by the desktop profile — iOS keeps a leftover frost layer across the whole
     compositor once any element declares one — so this is only its strength. */
  --glass: rgba(13, 20, 23, 0.82);
  --glass-2: rgba(27, 38, 41, 0.72);
  --glass-hi: rgba(36, 49, 52, 0.88);
  --blur: 7px;

  /* Accent support */
  --accent-soft: rgba(201, 164, 94, 0.14);
  --accent-line: rgba(201, 164, 94, 0.52);
  --danger-soft: rgba(224, 83, 61, 0.14);

  /* Radii */
  --r-sm: 8px;
  --r-md: 12px;
  --r-pill: 999px;

  /* Spacing — 4pt grid */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;

  /* Motion */
  --dur-fast: 120ms;
  --dur: 170ms;
  --dur-slow: 240ms;
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);

  /* Focus ring — one source of truth, reads on dark AND on gold */
  --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);

  /* Critical controls stay clear of notches, rounded corners and gesture bars.
     Decorative background art deliberately continues beneath those insets. */
  --menu-safe-top: max(var(--s3), var(--safe-top));
  --menu-safe-right: max(var(--s3), var(--safe-right));
  --menu-safe-bottom: max(var(--s3), var(--safe-bottom));
  --menu-safe-left: max(var(--s3), var(--safe-left));

  /* The two figures the whole flow breathes with. Both shrink with the glass's
     own height, which is what a phone held sideways is short of: on a 390px-tall
     landscape phone this is 8px, on a desk it is 20px. Chrome is what gives way
     there — the controls below hold their touch size. */
  --pg-gap: clamp(6px, 2.2dvh, 20px);

  /* A control has to be pressable by a thumb, so every button, field and row in
     the pre-game flow is at least this tall — on every device, because a mouse
     is no worse off for a comfortable target. This is the one size that does not
     condense: a control too small to hit is not a smaller control. */
  --pg-touch: 44px;

  /* How hard everything that is not a control shrinks. Every derived size in the
     flow multiplies by it, so the whole screen steps down together instead of
     one part at a time, and there is one number to change rather than forty. */
  --pg-condense: 1;
}

/* A phone held sideways has about 390px of height, and under the browser's own
   chrome nearer 320. Chrome, type and copy step down there; the touch size does
   not. */

@media (max-height: 34rem) {
  .menu-root,
  .menu-overlay,
  .menu-toast { --pg-condense: 0.88; }
}

@media (max-height: 26rem) {
  .menu-root,
  .menu-overlay,
  .menu-toast { --pg-condense: 0.8; }
}

/* ========================================================== Screen frame === */

/* The stage fills the glass and clips: the vista behind it is an absolute layer
   scaled past its own box so it can drift, and a scrollable stage would count
   that bleed as page overflow — a scrollbar down the side that scrolls nothing. */

.menu-root {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 100dvh;
  overflow: clip;
  color: var(--text);
}

.pg-shell {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--pg-gap);
  height: 100dvh;
  padding: var(--menu-safe-top) var(--menu-safe-right) var(--menu-safe-bottom) var(--menu-safe-left);
}

.pg-head,
.pg-foot {
  flex: 0 0 auto;
  min-width: 0;
}

/* The one scroll container on the page. `safe` centring is the whole trick: it
   centres what fits and falls back to top-aligned the moment the content is
   taller than the middle, so an overflowing screen never pushes its own head
   above the scroll origin where nothing can reach it. */

.pg-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: safe center;
  gap: var(--pg-gap);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.pg-main > * {
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
}

.pg-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s4);
  font-weight: 600;
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  line-height: 1;
  letter-spacing: 0.4px;
  color: var(--muted);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
}

/* Nothing but the build line lives down here, and on a short glass even that is
   worth the row it costs — it hides rather than pushing the frame. */

@media (max-height: 26rem) {
  .pg-foot { display: none; }
  /* A phone in landscape inside a browser is the shortest glass the game is
     played on, and the sign-in head is only a wordmark: it leaves the flow and
     hides, and the card gets the row back. The character screens deal with
     their own head in the pre-game screens sheet. */
  .login-screen > .pg-head {
    position: absolute;
    z-index: 3;
    inset-block-start: var(--menu-safe-top);
    inset-inline: var(--menu-safe-left) var(--menu-safe-right);
    pointer-events: none;
  }
  .login-screen > .pg-head .ront-brand { display: none; }
}

.screen-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center 32%;
  background-repeat: no-repeat;
  transform: scale(1.04);
  animation: screen-bg-drift 40s ease-in-out infinite alternate;
}

@keyframes screen-bg-drift {
  from { transform: scale(1.04) translate3d(-0.6%, 0, 0); }
  to { transform: scale(1.08) translate3d(0.6%, -1%, 0); }
}

.screen-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(125% 95% at 50% 42%, transparent 26%, rgba(6, 9, 14, 0.7) 100%),
    linear-gradient(180deg, rgba(6, 9, 14, 0.55) 0%, transparent 34%, rgba(6, 9, 14, 0.66) 100%);
}

/* The ambient plane behind the whole pre-game flow. It is mounted once and
   persists across login → character select, so it is what the player sees in the
   moment between two screens, when neither one's vista is on the page. */

.menu-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: radial-gradient(120% 80% at 50% -10%, #16203a 0%, #0c1018 45%, var(--bg) 75%);
}

.menu-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(0, 0, 0, 0.55) 100%);
}

.menu-aurora {
  position: absolute;
  inset: -20%;
  opacity: 0.5;
  will-change: transform;
  background:
    radial-gradient(40% 30% at 30% 25%, rgba(64, 120, 150, 0.45), transparent 70%),
    radial-gradient(45% 35% at 70% 20%, rgba(120, 100, 160, 0.35), transparent 70%),
    radial-gradient(35% 25% at 55% 15%, color-mix(in srgb, var(--gold) 10%, transparent), transparent 70%);
  animation: menu-aurora-drift 48s ease-in-out infinite alternate;
}

.menu-aurora.menu-aurora--b {
  opacity: 0.32;
  animation-duration: 67s;
  animation-direction: alternate-reverse;
}

@keyframes menu-aurora-drift {
  from { transform: translate3d(-3%, 0, 0) scale(1.05); }
  to { transform: translate3d(3%, 2%, 0) scale(1.12); }
}

.menu-particles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* =============================================================== Panels ==== */

.menu-panel {
  --pg-panel-py: clamp(10px, 2.6dvh, 24px);
  --pg-panel-px: clamp(12px, 2vw, 26px);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--pg-gap);
  padding: var(--pg-panel-py) var(--pg-panel-px);
  background: var(--glass);
  border: 1px solid var(--ui-line-soft);
  border-radius: var(--r-sm);
  color: var(--text);
  /* box-shadow, not filter: drop-shadow — a filter on this node rasterizes the
     Enter / Create / Discord labels into a soft bitmap. */
  box-shadow: var(--ui-shadow);
  animation: menu-surface-in var(--dur) var(--ease) both;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .menu-panel,
  .menu-overlay__card,
  .menu-toast {
    background: var(--panel);
  }
}

/* The accent hairline starts inside the corner radius, so it never hangs off the
   rounded silhouette it is drawn on. */

.menu-panel::before {
  content: '';
  position: absolute;
  top: 0;
  left: var(--r-sm);
  width: calc(74% - var(--r-sm));
  height: 1px;
  background: linear-gradient(90deg, var(--accent), transparent);
  opacity: 0.6;
}

.menu-panel.is-danger::before {
  background: linear-gradient(90deg, var(--danger), transparent);
}

.menu-panel__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(16px, calc(20px * var(--ui-scale) * var(--pg-condense)), 22px);
  line-height: 1.2;
  letter-spacing: 0.3px;
}

.menu-panel__title--icon {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.menu-panel__title-icon {
  flex: none;
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.menu-panel__sub {
  margin: calc(-1 * var(--pg-gap) + var(--s1)) 0 0;
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.45;
}

.menu-panel__body {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

/* The decisive actions ride the bottom of the scroller: on a glass too short for
   the card they stay on screen, and on one tall enough the sticky offset never
   engages, so there is no separate short-screen layout to keep in step. */

.menu-panel__actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-block-start: auto;
}

/* Inside a card the pinned row bleeds to the card's own edges, so what scrolls
   under it passes behind the card's ground rather than behind nothing. */

.menu-panel > .menu-panel__actions {
  bottom: calc(-1 * var(--pg-panel-py));
  margin-inline: calc(-1 * var(--pg-panel-px));
  margin-block-end: calc(-1 * var(--pg-panel-py));
  padding: var(--s2) var(--pg-panel-px) var(--pg-panel-py);
  background: linear-gradient(180deg, rgba(9, 14, 17, 0), rgba(9, 14, 17, 0.94) 42%);
}

.menu-panel__actions > .menu-button {
  flex: 1 1 auto;
}

@keyframes menu-surface-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* =============================================================== Buttons === */

.menu-button {
  position: relative;
  display: inline-flex;
  white-space: nowrap;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--pg-touch);
  padding: 0 var(--s4);
  border-radius: 6px;
  border: 1px solid var(--line);
  font-weight: 600;
  font-size: calc(14px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.1;
  cursor: var(--cursor-action);
  color: var(--text);
  background: var(--glass-2);
  transition:
    background var(--dur),
    box-shadow var(--dur),
    border-color var(--dur),
    transform var(--dur-fast);
}

.menu-button:active { transform: translateY(1px) scale(0.99); }

.menu-button:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.menu-button[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* An aria-disabled button is inert but still reachable: it keeps taking clicks
   and keyboard activation so its handler can say what would unlock it. Only a
   real `disabled` control swallows input outright. */

.menu-button[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Primary actions use matte brass: clear hierarchy without mobile-game gloss. */

.menu-button--primary {
  color: var(--ink);
  font-weight: 800;
  letter-spacing: 0.24px;
  border-color: #c5a35d;
  background: #c5a35d;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
}

.menu-button--primary:hover {
  background: #d6b970;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.34);
}

.menu-button--primary:active { box-shadow: inset 0 2px 4px rgba(80, 49, 15, 0.38); }

.menu-button--ghost {
  background: var(--glass-2);
  border-color: var(--line-strong);
  color: var(--text);
}

.menu-button--ghost:hover {
  background: var(--panel-3);
  border-color: var(--accent-line);
  color: #ffe7a8;
}

.menu-button--danger {
  background: rgba(224, 83, 61, 0.12);
  border-color: rgba(224, 83, 61, 0.5);
  color: var(--danger);
}

.menu-button--danger:hover {
  background: var(--danger-soft);
  border-color: var(--danger);
}

.menu-button--block { width: 100%; }

/* Loading: hide the label, centre a spinner, preserve width. */

.menu-button.is-loading {
  color: transparent;
  pointer-events: none;
}

.menu-button.is-loading::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: calc(16px * var(--ui-scale) * var(--pg-condense));
  height: calc(16px * var(--ui-scale) * var(--pg-condense));
  border-radius: 50%;
  background: conic-gradient(from 0deg, transparent, currentColor);
  -webkit-mask: radial-gradient(circle, transparent 52%, #000 54%);
  mask: radial-gradient(circle, transparent 52%, #000 54%);
  color: var(--ink);
  animation: ui-spin 0.8s linear infinite;
}

.menu-button--ghost.is-loading::after,
.menu-button--danger.is-loading::after { color: var(--accent); }

/* ================================================================ Fields === */

.menu-field {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}

.menu-field__label {
  font-weight: 600;
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.3;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--muted);
}

.menu-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.menu-input {
  width: 100%;
  min-height: var(--pg-touch);
  padding: 0 var(--s3);
  color: var(--text);
  /* 16px is the threshold under which mobile Safari zooms the page on focus and
     never zooms back — a field that reads smaller has to say so through the
     scale token, not through a size that hijacks the viewport. */
  font-size: max(16px, calc(14px * var(--ui-scale) * var(--pg-condense)));
  background: var(--glass-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  transition: border-color var(--dur), box-shadow var(--dur), background var(--dur);
}

.menu-input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  pointer-events: none;
}

.menu-input::placeholder { color: #5f6877; }

.menu-input:hover { border-color: var(--line-strong); }

.menu-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  background: var(--glass-hi);
}

.menu-input[aria-invalid='true'] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
  padding-right: 34px;
}

/* Non-colour invalid cue. */

.menu-field.is-invalid .menu-input-wrap::after {
  content: '';
  position: absolute;
  right: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  background: url('/play/assets/hud-warning-BTZpYkHQ.webp') center / contain no-repeat;
  pointer-events: none;
}

.menu-field__hint {
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  color: var(--muted);
}

/* Password reveal toggle sits inside the field. */

.menu-reveal {
  position: absolute;
  right: var(--s1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: none;
  border-radius: var(--r-sm);
  background: none;
  color: var(--muted);
  cursor: var(--cursor-action);
  transition: color var(--dur), background var(--dur);
}

.menu-reveal:hover:not(:disabled) { color: var(--text); background: var(--glass-2); }

/* Held during a sign-in: dimmed, inert, and no hover lift, so it reads as
   unavailable rather than unresponsive. */

.menu-reveal:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.menu-reveal:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.menu-reveal__icon {
  width: 22px;
  height: 22px;
  object-fit: contain;
  filter: saturate(0.75) brightness(0.9);
}

.menu-reveal:hover:not(:disabled) .menu-reveal__icon { filter: saturate(1) brightness(1.08); }

/* ================================================================ Status === */

.menu-status {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: calc(16px * var(--ui-scale) * var(--pg-condense));
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.35;
  color: var(--muted);
}

.menu-status::before { content: ''; flex: 0 0 auto; }

.menu-status:empty { visibility: hidden; }

.menu-status.is-info { color: var(--muted); }

.menu-status.is-error { color: var(--danger); }

.menu-status.is-error::before {
  width: 16px;
  height: 16px;
  background: url('/play/assets/hud-warning-BTZpYkHQ.webp') center / contain no-repeat;
}

.menu-spinner {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, transparent, var(--accent));
  -webkit-mask: radial-gradient(circle, transparent 52%, #000 54%);
  mask: radial-gradient(circle, transparent 52%, #000 54%);
  animation: ui-spin 0.8s linear infinite;
}

/* ========================================================= Crest / faction = */

.menu-crest {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  color: var(--accent);
  background: radial-gradient(circle at 50% 35%, var(--crest-tint, color-mix(in srgb, var(--gold) 22%, transparent)), rgba(8, 12, 18, 0.6) 70%);
  border: 2px solid var(--crest-ring, var(--accent-line));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 4px 14px rgba(0, 0, 0, 0.4);
}

.menu-crest--sm { width: 40px; height: 40px; }

.menu-crest__bitmap { width: 72%; height: 72%; object-fit: contain; display: block; }

/* Missing art is explicit readable text, never a blank disc. */

.faction-icon {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--faction-ring, var(--accent-line));
  background: color-mix(in srgb, var(--faction-ring, var(--accent)) 14%, rgba(8, 12, 18, 0.55));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 2px 8px rgba(0, 0, 0, 0.35);
  object-fit: contain;
  padding: 5px;
  box-sizing: border-box;
}

.faction-icon--sm { width: 32px; height: 32px; }

.faction-icon--md { width: 38px; height: 38px; padding: 6px; }

.faction-icon--lg {
  width: clamp(52px, 11dvh, 84px);
  height: clamp(52px, 11dvh, 84px);
  padding: 8px;
  border-width: 2px;
}

.faction-icon-missing {
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale) * var(--pg-condense));
  font-weight: 700;
  line-height: 1.2;
}

.faction-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
}

.faction-chip__name {
  font-weight: 600;
  font-size: calc(13px * var(--ui-scale) * var(--pg-condense));
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.faction-chip--unknown .faction-chip__name { color: var(--muted); font-weight: 500; }

/* ============================================================= Brand mark == */

.ront-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  color: #e9b94e;
}

.ront-brand__mark {
  flex: none;
  object-fit: contain;
  filter: drop-shadow(0 2px 8px rgba(233, 185, 78, 0.4));
}

.ront-brand__text {
  display: flex;
  min-width: 0;
  flex-direction: column;
  line-height: 1.02;
}

.ront-brand__name {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.05em;
  background: linear-gradient(180deg, #f4cd6e 0%, #e9b94e 48%, #b78f2e 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* The lockup lands on the vista's brightest band, where gold on sunrise loses
     the wordmark; the type carries its own darkness so the art stays undimmed. */
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.8)) drop-shadow(0 0 12px rgba(0, 0, 0, 0.8));
}

.ront-brand__tag {
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: #d3cbb8;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.92);
}

.ront-brand__initial { color: #f6dc96; }

.ront-brand--hero .ront-brand__mark {
  width: clamp(40px, 8dvh, 68px);
  height: clamp(40px, 8dvh, 68px);
}

.ront-brand--hero .ront-brand__name { font-size: clamp(28px, 6dvh, 46px); }

.ront-brand--hero .ront-brand__tag { margin-top: 3px; font-size: clamp(8px, 1.4dvh, 11px); }

.ront-brand--compact .ront-brand__mark {
  width: clamp(26px, 5dvh, 38px);
  height: clamp(26px, 5dvh, 38px);
}

.ront-brand--compact .ront-brand__name { font-size: clamp(17px, 3.4dvh, 23px); }

.ront-brand--compact .ront-brand__tag { margin-top: 2px; font-size: clamp(7px, 1.1dvh, 9px); }

/* The signature line is the first thing to go when the head has no room for it. */

@media (max-height: 26rem) {
  .ront-brand__tag { display: none; }
}

/* ======================================================== Player settings == */

@media (prefers-reduced-motion: reduce) {
  .screen-bg { animation: none; transform: scale(1.04); }
  .menu-aurora { animation: none; }
  .menu-panel,
  .cs-hero.is-promoting,
  .menu-overlay__card,
  .menu-overlay__scrim,
  .menu-toast { animation: none; }
  .create-faction__check { transition: none; }
}

body.reduce-motion .screen-bg { animation: none; transform: scale(1.04); }

body.reduce-motion .menu-aurora { animation: none; }

body.reduce-motion .menu-panel,
body.reduce-motion .cs-hero.is-promoting,
body.reduce-motion .menu-overlay__card,
body.reduce-motion .menu-toast { animation: none; }

body.high-contrast .menu-root,
body.high-contrast .menu-overlay,
body.high-contrast .menu-toast {
  --glass: rgba(6, 9, 12, 0.96);
  --glass-2: rgba(10, 15, 18, 0.96);
  --glass-hi: rgba(16, 23, 27, 0.98);
}

body.high-contrast .menu-panel,
body.high-contrast .cs-rail,
body.high-contrast .menu-input,
body.high-contrast .menu-overlay__card,
body.high-contrast .menu-toast {
  border-color: var(--text);
}

body.high-contrast .screen-bg::after { background: rgba(4, 6, 9, 0.88); }

/* The four pre-game screens — login, character select, character create and
   character delete — drawn on the frame and the atoms the pre-game sheet
   defines. Each one is a head, a middle and a foot sized to the glass; the
   sign-in card scrolls its middle, the character screens scroll only a long
   roster or a tall form inside its own box. What differs here is only what
   stands in that middle. */

/* ================================================================= Login === */

.login-consent {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.55rem;
  align-items: start;
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.45;
}

.login-consent__check {
  width: 1rem;
  height: 1rem;
  margin-top: 0.1rem;
  accent-color: var(--accent);
}

.login-consent a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.login-brand {
  display: flex;
  justify-content: center;
}

.login-panel {
  width: min(24rem, 100%);
  margin-inline: auto;
}

.login-panel .menu-panel__body {
  gap: var(--s3);
}

/* Official provider marks keep their published colors and proportions. Login
   buttons reserve the same left rail for each mark while the label stays truly
   centered, matching the OAuth buttons players already recognize elsewhere. */

.auth-provider-mark {
  display: block;
  flex: 0 0 auto;
  object-fit: contain;
}

.auth-provider-mark--discord { width: 24px; height: 18px; }

.auth-provider-mark--google { width: 20px; height: 20px; }

.login-provider { padding-inline: 52px; }

.login-provider__mark {
  position: absolute;
  inset-inline-start: 16px;
  inset-block-start: 50%;
  transform: translateY(-50%);
}

.login-provider__label { line-height: 1; }

.login-provider.is-loading .login-provider__mark { opacity: 0; }

/* Discord Blurple (#5865F2) with Discord's unmodified white symbol. Corners,
   height and focus ring still come from the shared Ront control family. */

.login-discord,
.menu-button--discord {
  color: #fff;
  font-weight: 700;
  letter-spacing: 0.2px;
  background: #5865f2;
  border-color: #4b56d2;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 4px 14px rgba(88, 101, 242, 0.38),
    0 1px 2px rgba(0, 0, 0, 0.3);
}

.login-discord:hover,
.menu-button--discord:hover {
  color: #fff;
  background: #4752c4;
  border-color: #3d47b0;
}

.login-discord:active,
.menu-button--discord:active { background: #3c449e; border-color: #333c8c; }

/* Google's neutral button treatment with the official multicolor G. */

.login-google,
.menu-button--google {
  color: #1f1f1f;
  font-family: Roboto, Arial, sans-serif;
  font-weight: 500;
  letter-spacing: 0.15px;
  background: #fff;
  border-color: #747775;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 3px 12px rgba(0, 0, 0, 0.24);
}

.login-google:hover,
.menu-button--google:hover {
  color: #111;
  background: #f7faff;
  border-color: #747775;
}

.login-google:active,
.menu-button--google:active { background: #edf1f7; }

/* "or" separator between the provider CTAs and the local user/pass form. */

.login-or {
  display: flex;
  align-items: center;
  gap: var(--s3);
  color: var(--muted);
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}

.login-or::before,
.login-or::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
}

/* Text button that flips the local form between sign-in and create-account. */

.login-switch {
  display: block;
  width: 100%;
  min-height: var(--pg-touch);
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  text-align: center;
  cursor: var(--cursor-action);
}

.login-switch:hover:not(:disabled) { color: var(--text); text-decoration: underline; }

.login-switch:disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }

.login-switch:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-sm); }

/* A phone held sideways is wide and short, and a single column of eight rows is
   the one shape that glass cannot hold. The card spends the width it does have
   instead: the provider on one side, the account fields on the other. Only a
   card that actually offers both is dealt this way. */

@media (min-width: 40rem) and (max-height: 34rem) {
  /* Two columns, not two half-screens: past this the fields are far wider than
     anything anyone types into them, and the card reads as a page rather than as
     a card standing in the world. */
  .login-panel:has(#login-user) { width: min(34rem, 100%); }
  .login-panel .menu-panel__body:has(#login-user) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      'discord email'
      'google  pass'
      'or      submit'
      'status  switch';
    column-gap: var(--s5);
    align-items: start;
  }
  .login-panel:has(#login-user) .login-discord { grid-area: discord; }
  .login-panel:has(#login-user) .login-google { grid-area: google; }
  .login-panel .login-or { grid-area: or; }
  .login-panel .menu-field:has(#login-user) { grid-area: email; }
  .login-panel .menu-field:has(#login-pass) { grid-area: pass; }
  .login-panel:has(#login-user) button[type='submit'] { grid-area: submit; align-self: end; }
  .login-panel:has(#login-user) .menu-status { grid-area: status; align-self: end; }
  .login-panel .login-switch { grid-area: switch; }
  .login-panel:has(.login-google):has(#login-user) .menu-panel__body {
    grid-template-areas:
      'google  email'
      'discord pass'
      'or      submit'
      'status  status'
      'switch  switch';
  }
  .login-panel:has(.login-google):has(#login-user) .login-google { grid-area: google; }
}

/* ============================================== OAuth account choice === */

.account-choice {
  width: min(30rem, 100%);
  margin-inline: auto;
}

.account-choice__body { gap: var(--s4); }

.account-choice__email,
.account-choice__intro,
.account-choice__warning p {
  margin: 0;
  color: var(--muted);
  line-height: 1.5;
}

.account-choice__email { color: var(--text); font-weight: 700; }

.account-choice__actions { display: grid; gap: var(--s3); }

.account-choice__warning {
  display: grid;
  gap: var(--s3);
  padding: var(--s4);
  border: 1px solid color-mix(in srgb, var(--danger) 62%, transparent);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--danger) 9%, transparent);
}

.account-choice__confirm-title {
  margin: 0;
  color: var(--text);
  font-family: var(--font-display);
  font-size: calc(19px * var(--ui-scale) * var(--pg-condense));
}

.account-choice__confirm-title:focus { outline: none; }

.account-choice__irreversible { color: var(--danger) !important; font-weight: 700; }

.account-choice__status:empty { display: none; }

/* ====================================================== Character select === */

/* The character screens never scroll the page: the frame is the glass, the hero
   takes the height the chrome leaves, and only two things ever scroll — a long
   roster inside its own list, and a form inside its own card. */

.cs-screen > .pg-main {
  overflow: hidden;
}

.cs-header {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
  min-height: var(--pg-touch);
}

.cs-header__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding-left: var(--s3);
  border-left: 1px solid rgba(234, 217, 172, 0.42);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.3;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(238, 233, 222, 0.8);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.8);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cs-header__spacer { flex: 1 1 auto; }

@media (max-width: 40rem) {
  .cs-header__title { display: none; }
}

.cs-header > .menu-button { flex: 0 0 auto; padding-inline: var(--s4); }

/* ---- Sign-in methods dialog ---- */

.account-methods {
  position: fixed;
  inset: 0;
  z-index: 90;
  padding:
    max(var(--s4), var(--safe-top))
    max(var(--s4), var(--safe-right))
    max(var(--s4), var(--safe-bottom))
    max(var(--s4), var(--safe-left));
  background: rgba(7, 9, 13, 0.72);
}

.account-methods[hidden] { display: none; }

.account-methods__panel {
  width: min(31rem, 100%);
  max-height: min(36rem, 100%);
  margin: auto;
  overflow: auto;
}

.account-methods__body {
  display: grid;
  gap: var(--s3);
}

.account-methods__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: var(--pg-touch);
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: rgba(7, 10, 15, 0.34);
}

.account-methods__identity {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}

.account-methods__identity-copy { display: grid; gap: 2px; min-width: 0; }

.account-methods__provider-mark { justify-self: center; }

.account-methods__button-mark { width: 18px; height: 18px; }

.account-methods__state {
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
}

.account-methods__state.is-linked { color: var(--accent-2); }

.account-methods__link { flex: 0 0 auto; }

.account-methods__remove { flex: 0 0 auto; }

.account-methods__confirmation { display: grid; gap: var(--s3); }

.account-methods__confirmation p { margin: 0; color: var(--muted); line-height: 1.5; }

.account-methods__confirm-actions { display: flex; justify-content: flex-end; gap: var(--s2); }

.account-methods__hint {
  margin: 0;
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  line-height: 1.5;
}

.account-methods__actions {
  display: flex;
  justify-content: flex-end;
}

@media (max-width: 34rem) {
  .cs-header > .menu-button { padding-inline: var(--s2); font-size: calc(11px * var(--ui-scale)); }
  .account-methods__row { align-items: stretch; flex-direction: column; }
  .account-methods__link { width: 100%; }
  .account-methods__remove { width: 100%; }
  .account-methods__confirm-actions { align-items: stretch; flex-direction: column; }
}

/* The body fills the middle. Hero above, roster strip below on a narrow glass;
   hero beside a roster column on a wide one. */

.cs-body {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  gap: var(--pg-gap);
  width: min(72rem, 100%);
  margin-inline: auto;
}

.cs-herohost {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.cs-herohost > * {
  flex: 1 1 auto;
  min-height: 0;
}

.cs-body--solo { grid-template-rows: minmax(0, 1fr); }

.cs-body--solo .cs-roster { display: none; }

/* ---- Featured hero ---- */

.cs-hero {
  padding: 0;
  gap: var(--s3);
  background: transparent;
  border: 0;
  box-shadow: none;
  justify-content: center;
  text-align: center;
}

.cs-hero::before { display: none; }

/* The figure is drawn inside whatever height the name and actions leave, never
   at the bitmap's own size, and is capped so a tall desk does not hand it the
   whole wall. */

.cs-hero__stage {
  position: relative;
  flex: 1 1 0;
  min-height: 64px;
  max-height: 620px;
  width: 100%;
  overflow: hidden;
}

.cs-hero__stage::after {
  content: '';
  position: absolute;
  left: 22%;
  right: 22%;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(233, 224, 198, 0.3), transparent);
}

.cs-character-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  filter: drop-shadow(0 12px 14px rgba(0, 0, 0, 0.5));
}

.cs-hero__stage .menu-crest {
  position: absolute;
  inset: auto 0 0;
  margin: auto;
  width: 96px;
  height: 96px;
}

.cs-hero__id {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s2) var(--s3);
  min-width: 0;
}

.cs-hero__name {
  flex: 1 0 100%;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: calc(26px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.1;
  letter-spacing: 0.025em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85);
}

.cs-hero__class {
  padding: 4px var(--s2);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  background: rgba(6, 9, 14, 0.55);
  color: #ffe7a8;
  font-weight: 700;
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1;
  white-space: nowrap;
}

.cs-hero__faction { min-width: 0; }

.cs-hero__faction .faction-chip__name { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85); }

.cs-hero__where {
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  color: rgba(238, 233, 222, 0.86);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cs-hero__actions {
  display: flex;
  justify-content: center;
  gap: var(--s3);
}

.cs-hero__actions > .menu-button--primary { flex: 0 1 20rem; }

.cs-hero__actions > .cs-hero__delete { flex: 0 0 auto; }

.cs-hero.is-promoting { animation: menu-fade-in var(--dur) var(--ease) both; }

@keyframes menu-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* First hero: a short invitation card in the middle of the world. */

.cs-hero.is-empty {
  flex: 0 0 auto;
  align-self: center;
  margin: auto;
  align-items: center;
  justify-content: center;
  width: min(26rem, 100%);
  padding: clamp(18px, 3.4dvh, 32px) clamp(18px, 3vw, 28px);
  border: 1px solid var(--ui-line-soft);
  background: linear-gradient(150deg, rgba(9, 14, 17, 0.9), rgba(7, 12, 14, 0.76));
  box-shadow: var(--ui-shadow);
}

.cs-hero.is-empty::before { display: block; }

.cs-hero.is-empty .menu-panel__sub { max-width: 34ch; margin: 0; }

.cs-hero.is-empty .menu-button { min-width: min(14rem, 100%); }

/* ---- Roster ---- */

.cs-roster {
  display: flex;
  gap: var(--s2);
  min-width: 0;
  min-height: 0;
}

.cs-rail {
  display: flex;
  min-width: 0;
  min-height: 0;
  border: 1px solid rgba(133, 151, 153, 0.24);
  border-radius: var(--r-sm);
  background: rgba(7, 11, 13, 0.62);
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.cs-slot {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 0;
  min-height: var(--pg-touch);
  padding: var(--s2) var(--s3);
  text-align: left;
  border: 0;
  background: transparent;
  color: var(--text);
  cursor: var(--cursor-action);
  transition: background var(--dur), box-shadow var(--dur);
}

.cs-slot:hover { background: rgba(26, 32, 40, 0.92); }

.cs-slot:focus-visible { outline: none; box-shadow: var(--focus-ring); z-index: 1; }

.cs-slot[aria-current='true'] { background: rgba(201, 164, 94, 0.12); }

.cs-slot__body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 6px;
  min-width: 0;
}

.cs-slot__name {
  flex: 1 0 100%;
  font-weight: 600;
  font-size: calc(13px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cs-slot__meta,
.cs-slot__faction {
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
}

.cs-slot__faction {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  border-left: 1px solid rgba(221, 207, 175, 0.32);
  padding-left: 6px;
}

.cs-create {
  flex: 0 0 auto;
  background: rgba(14, 19, 26, 0.7);
  border-color: rgba(201, 164, 94, 0.38);
  color: #ddcfaf;
}

.cs-create:hover {
  background: rgba(201, 164, 94, 0.1);
  border-color: rgba(201, 164, 94, 0.65);
  color: #f1dfb5;
}

/* An author `display` beats the user-agent `[hidden]` rule. */

.cs-create[hidden] { display: none; }

/* Locked while a form's request is in flight: inert must read as unavailable. */

.cs-slot:disabled,
.cs-create:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Narrow glass: the roster is a strip of chips under the hero, scrolling
   sideways inside itself, with Create standing at its end. */

.cs-roster {
  flex-direction: row;
  align-items: stretch;
}

.cs-rail {
  flex: 1 1 auto;
  flex-direction: row;
  overflow-x: auto;
  overflow-y: hidden;
}

.cs-slot {
  flex: 0 0 auto;
  width: 8.5rem;
  border-right: 1px solid rgba(133, 151, 153, 0.18);
}

.cs-slot:last-child { border-right: 0; }

.cs-slot[aria-current='true'] { box-shadow: inset 0 -3px 0 var(--accent); }

.cs-slot__faction { display: none; }

.cs-create {
  width: 6.5rem;
  padding-inline: var(--s2);
  white-space: normal;
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.15;
  text-align: center;
}

/* Wide glass — or a phone held sideways, which is short before it is narrow:
   the roster stands beside the hero as a column, scrolling inside itself when
   the account holds more than the glass shows. */

@media (min-width: 52rem), (min-width: 40rem) and (max-height: 28rem) {
  .cs-body {
    grid-template-columns: minmax(0, 1fr) clamp(15rem, 22vw, 18rem);
    grid-template-rows: minmax(0, 1fr);
  }
  .cs-body--solo { grid-template-columns: minmax(0, 1fr); }
  .cs-roster {
    flex-direction: column;
    align-items: stretch;
  }
  .cs-rail {
    flex: 0 1 auto;
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: auto;
  }
  .cs-slot {
    flex: 0 0 auto;
    width: 100%;
    gap: var(--s3);
    border-right: 0;
    border-bottom: 1px solid rgba(133, 151, 153, 0.18);
  }
  .cs-slot:last-child { border-bottom: 0; }
  .cs-slot[aria-current='true'] { box-shadow: inset 3px 0 0 var(--accent); }
  .cs-slot__faction { display: block; }
  .cs-create {
    width: 100%;
    padding-inline: var(--s4);
    white-space: nowrap;
    font-size: calc(14px * var(--ui-scale) * var(--pg-condense));
  }
}

/* Screen-level failure line: where a server error lands when no form owns it.
   Empty it costs nothing; the live region stays in the tree either way. */

.cs-status {
  flex: 0 0 auto;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  padding: var(--s2) var(--s3);
  border: 1px solid rgba(224, 83, 61, 0.5);
  border-radius: var(--r-sm);
  background: rgba(14, 19, 26, 0.92);
  text-align: center;
}

.cs-status:empty {
  visibility: visible;
  margin-block-start: calc(-1 * var(--pg-gap));
  min-height: 0;
  padding: 0;
  border-width: 0;
  background: none;
}

.cs-status:empty::before { content: none; }

/* A phone held sideways: the head leaves the flow and keeps only the way out in
   its corner, so the middle gets the whole height. The roster column starts
   under that button; the hero was never under it. */

@media (max-height: 28rem) {
  .cs-screen > .cs-header {
    position: absolute;
    z-index: 3;
    top: var(--menu-safe-top);
    right: var(--menu-safe-right);
    min-height: 0;
  }
  .cs-screen > .cs-header > .ront-brand,
  .cs-screen > .cs-header > .cs-header__title,
  .cs-screen > .cs-header > .cs-header__spacer { display: none; }
  .cs-screen > .cs-header > .menu-button { min-height: 36px; }
  .cs-screen .cs-body:not(.cs-body--solo) .cs-roster { margin-top: calc(36px + var(--s2)); }
  /* Name, level and Crown share one row, so the figure keeps the height. */
  .cs-hero { gap: var(--s2); }
  .cs-hero__name { flex: 0 1 auto; }
  .cs-hero__where { display: none; }
  .cs-hero__actions { padding-top: var(--s1); }
}

/* ======================================================= Create / delete === */

/* Both forms are bounded cards standing where the hero stood. A form taller
   than the glass scrolls inside its own card; its decisive row stays pinned to
   the card's foot. */

.cs-root .create-panel,
.cs-root .delete-panel {
  flex: 0 1 auto;
  align-self: center;
  margin: auto;
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  gap: var(--s3);
  padding-bottom: 0;
  border-left: 2px solid color-mix(in srgb, var(--accent) 66%, transparent);
  background: linear-gradient(150deg, rgba(9, 14, 17, 0.92), rgba(7, 12, 14, 0.82));
}

.cs-root .create-panel { width: min(44rem, 100%); }

.cs-root .delete-panel {
  width: min(30rem, 100%);
  border-left-color: color-mix(in srgb, var(--danger) 72%, transparent);
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--danger) 8%, rgba(9, 14, 17, 0.92)),
    rgba(7, 12, 14, 0.82)
  );
}

.cs-root .menu-panel > .menu-panel__actions {
  bottom: 0;
  margin-block-end: 0;
  padding: var(--s3) var(--pg-panel-px) var(--pg-panel-py);
  background: linear-gradient(180deg, rgba(9, 14, 17, 0), rgba(9, 14, 17, 0.96) 40%);
}

.cs-root .menu-panel > .menu-status:empty { display: none; }

/* ---- Create ---- */

.create-fields {
  display: flex;
  flex-direction: column;
  max-width: 22rem;
}

.create-count {
  align-self: flex-end;
  margin-top: 2px;
  font-size: calc(11px * var(--ui-scale) * var(--pg-condense));
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.create-faction-select {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.create-faction-select__head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.create-faction-select__title {
  margin: 0;
  font-size: calc(15px * var(--ui-scale) * var(--pg-condense));
  font-weight: 700;
}

.create-faction-select__sub,
.create-faction-select__stake,
.create-faction-select__permanent {
  margin: 0;
  max-width: 64ch;
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.45;
  color: var(--muted);
}

.create-faction-select__stake { color: var(--text-2); }

.create-factions {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s3);
}

/* A Crown card: the crest on its own ground at the left, the name, epithet and
   description beside it. The description is never cut. */

.create-faction {
  --faction-panel: rgba(11, 17, 21, 0.76);
  position: relative;
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  align-items: stretch;
  padding: 0;
  overflow: hidden;
  text-align: left;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--faction-panel);
  cursor: var(--cursor-action);
  transition: border-color var(--dur), box-shadow var(--dur);
}

.create-faction:hover {
  border-color: color-mix(in srgb, var(--faction-ring, var(--accent)) 55%, var(--line));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--faction-ring, var(--accent)) 22%, transparent);
}

.create-faction:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.create-faction:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.create-faction[aria-checked='true'] {
  border-color: var(--faction-ring, var(--accent));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--faction-ring, var(--accent)) 35%, transparent);
}

.create-faction[aria-checked='true']:focus-visible { box-shadow: var(--focus-ring); }

.create-faction:not([aria-checked='true']) .create-faction__art { filter: saturate(0.55) brightness(0.7); }

.create-faction:not([aria-checked='true']):hover .create-faction__art { filter: saturate(0.85) brightness(0.9); }

.create-faction__check {
  position: absolute;
  z-index: 2;
  top: var(--s2);
  right: var(--s2);
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--faction-ring, var(--accent)) 70%, transparent);
  background: rgba(6, 10, 13, 0.86);
  opacity: 0;
  transform: scale(0.82);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.create-faction__check::after {
  content: '';
  position: absolute;
  left: 7px;
  top: 3px;
  width: 6px;
  height: 11px;
  border: solid var(--faction-ring, var(--accent));
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}

.create-faction[aria-checked='true'] .create-faction__check { opacity: 1; transform: none; }

.create-faction__art {
  position: relative;
  display: grid;
  place-items: center;
  padding: var(--s2);
  transition: filter var(--dur) var(--ease);
}

.create-faction__art > .faction-icon--lg {
  width: 56px;
  height: 56px;
  padding: 7px;
}

.create-faction--has-bg .create-faction__art {
  background:
    linear-gradient(90deg, rgba(8, 11, 17, 0.2) 0%, rgba(8, 11, 17, 0.55) 70%, var(--faction-panel) 100%),
    var(--faction-bg, none) center / cover no-repeat;
}

.create-faction__art::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(
    120% 90% at 40% 20%,
    color-mix(in srgb, var(--faction-ring, var(--accent)) 22%, transparent),
    transparent 68%
  );
  opacity: 0.5;
  pointer-events: none;
  transition: opacity var(--dur);
}

.create-faction:hover .create-faction__art::after { opacity: 0.85; }

.create-faction[aria-checked='true'] .create-faction__art::after { opacity: 1; }

.create-faction__art > * { position: relative; z-index: 1; }

.create-faction__body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-content: center;
  align-items: baseline;
  column-gap: var(--s2);
  padding: var(--s3) calc(var(--s4) + 22px) var(--s3) var(--s3);
}

.create-faction__name {
  font-weight: 700;
  font-size: calc(14px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.2;
  color: var(--text);
}

.create-faction__tagline {
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  font-weight: 700;
  line-height: 1.2;
  color: var(--faction-ring, var(--accent));
}

.create-faction__blurb {
  grid-column: 1 / -1;
  margin: 4px 0 0;
  font-size: calc(12px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.4;
  color: var(--text-2, var(--muted));
}

/* A narrow glass: a slimmer crest column, and the epithet under the name
   rather than beside it. */

@media (max-width: 40rem) {
  .create-faction { grid-template-columns: 4.5rem minmax(0, 1fr); }
  .create-faction__body { grid-template-columns: minmax(0, 1fr); }
  .create-faction__art > .faction-icon--lg { width: 48px; height: 48px; padding: 6px; }
}

/* Wide and short (a phone held sideways): the copy takes one column, the two
   Crowns the other, so both cards stand whole on the glass above the row that
   commits. The select's children are dealt to the grid directly. */

@media (min-width: 40rem) and (max-height: 34rem) {
  .cs-root .create-panel {
    width: min(52rem, 100%);
    display: grid;
    grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
    grid-template-areas:
      'title     crowns'
      'name      crowns'
      'crownhead crowns'
      'status    permanent'
      'actions   actions';
    grid-template-rows: auto auto 1fr auto auto;
    column-gap: var(--s5);
    row-gap: var(--s3);
    align-items: start;
  }
  .create-panel > .menu-panel__title { grid-area: title; }
  .create-panel > .create-fields { grid-area: name; max-width: none; }
  .create-panel > .create-faction-select { display: contents; }
  .create-panel .create-faction-select__head { grid-area: crownhead; }
  .create-panel .create-faction-select__sub { display: none; }
  .create-panel .create-faction-select__permanent { grid-area: permanent; }
  .create-panel .create-factions { grid-area: crowns; grid-template-columns: minmax(0, 1fr); }
  .create-panel .create-factions { align-self: start; }
  .create-panel > .menu-status { grid-area: status; align-self: end; }
  .create-panel > .menu-status:empty { display: block; visibility: hidden; }
  .create-panel > .menu-panel__actions { grid-area: actions; }
  .create-faction { grid-template-columns: 4rem minmax(0, 1fr); }
  .create-faction__art > .faction-icon--lg { width: 44px; height: 44px; padding: 6px; }
  .create-faction__body { padding: var(--s2) calc(var(--s4) + 22px) var(--s2) var(--s2); }
  .create-faction__name { font-size: calc(13px * var(--ui-scale) * var(--pg-condense)); }
  .create-faction__tagline,
  .create-faction__blurb { font-size: calc(11px * var(--ui-scale) * var(--pg-condense)); }
  .create-faction__blurb { margin-top: 2px; line-height: 1.35; }
}

/* On the shortest glass the way out sits in the top-right corner, so the form
   keeps to the left of it. */

@media (min-width: 40rem) and (max-height: 28rem) {
  .cs-root .create-panel {
    width: min(52rem, calc(100% - 4.75rem));
    margin-inline: 0 auto;
    grid-template-columns: minmax(0, clamp(11rem, 27%, 14rem)) minmax(0, 1fr);
  }
}

/* ---- Delete ---- */

.delete-echo {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-md);
  background: var(--glass-2);
  border: 1px solid var(--line);
}

.delete-echo__body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.delete-echo__name {
  font-weight: 700;
  font-size: calc(15px * var(--ui-scale) * var(--pg-condense));
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.delete-echo__meta { font-size: calc(12px * var(--ui-scale) * var(--pg-condense)); color: var(--muted); }

.delete-panel .menu-panel__sub { margin: 0; }

/* Session overlays and the reconnect pill. These are body-level surfaces rather
   than screens: they wrap whatever is on the page, in the pre-game flow and in
   the world alike, and so they carry their own frame instead of the screen one. */

/* ====================================================== Overlays + toast === */

/* The overlay, not the card, is the scroll container. A card is a short stack of
   flex children whose heights land on fractions of a pixel, so a card that owns
   `overflow-y: auto` rounds its own content one pixel past its own box and shows
   a scrollbar over nothing. Scrolling from out here only ever engages when the
   card genuinely outgrows the viewport. */

.menu-overlay {
  position: fixed;
  inset: 0;
  /* Session band: above the loading cover too — a drop mid-warp must not hide
     behind it. */
  z-index: 400;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  overflow: auto;
  overscroll-behavior: contain;
  padding: var(--menu-safe-top) var(--menu-safe-right) var(--menu-safe-bottom) var(--menu-safe-left);
}

.menu-overlay--fatal { z-index: 401; }

/* The handheld portrait cover sits above every session card: nothing behind it
   can be read or pressed while the device is upright. */

.menu-overlay--landscape-gate { z-index: 402; }

/* Opaque map-enter loading cover. Stacks above the whole HUD (which reaches
   z-index ~220) so the DOM HUD and the not-yet-revealed black canvas stay hidden
   until the first frame composites. */

.menu-overlay--cover { z-index: 300; background: #070a0f; }

.menu-overlay__scrim {
  position: fixed;
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 48%, rgba(13, 20, 23, 0.38), transparent 60%),
    rgba(5, 8, 10, 0.78);
  animation: menu-fade 180ms ease-out both;
}

.menu-overlay__card {
  position: relative;
  z-index: 1;
  /* `100%` and not `100vw - 32px`: the overlay already holds the safe insets, and
     a percentage of its content box keeps giving way when a classic scrollbar
     claims its 15px, instead of forcing a second bar along the bottom. */
  width: min(calc(34rem * var(--ui-scale) * var(--pg-condense)), 100%);
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: clamp(18px, 3.4dvh, 30px) clamp(20px, 4vw, 34px);
  overflow: hidden;
  text-align: center;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(13, 20, 23, 0.9) 12%,
    rgba(18, 27, 29, 0.96) 50%,
    rgba(13, 20, 23, 0.9) 88%,
    transparent
  );
  border-block: 1px solid var(--ui-line-soft);
  border-inline: 0;
  border-radius: 0;
  filter: drop-shadow(0 18px 30px rgba(0, 0, 0, 0.42));
  color: var(--text);
  animation: menu-pop 180ms cubic-bezier(0.2, 0.78, 0.24, 1) both;
}

.menu-overlay__card::before,
.menu-overlay__card::after {
  content: '';
  position: absolute;
  left: 50%;
  width: 42%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  transform: translateX(-50%);
  opacity: 0.62;
}

.menu-overlay__card::before { top: -1px; }

.menu-overlay__card::after { bottom: -1px; opacity: 0.22; }

.menu-overlay__card[data-tone='danger']::before,
.menu-overlay__card[data-tone='danger']::after {
  background: linear-gradient(90deg, transparent, var(--danger), transparent);
}

.menu-overlay__icon {
  width: clamp(28px, 5dvh, 38px);
  height: clamp(28px, 5dvh, 38px);
  object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.36));
}

.menu-overlay__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(20px, 4.4dvh, 28px);
  line-height: 1.08;
}

.menu-overlay__msg {
  max-width: 40ch;
  font-size: calc(13px * var(--ui-scale) * var(--pg-condense));
  color: color-mix(in srgb, var(--text) 70%, var(--muted));
  line-height: 1.48;
}

/* The entering cover's live phase line — the join's real phases landing one after
   another are the liveness signal (the cover has no spinner). */

.menu-overlay__phase {
  font-size: calc(14px * var(--ui-scale) * var(--pg-condense));
  font-weight: 600;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--text) 86%, var(--muted));
}

.menu-overlay__card[data-tone='danger'] .menu-overlay__msg {
  color: color-mix(in srgb, var(--danger) 42%, var(--text));
}

/* A card that is already centred and already has its own ground needs neither
   the pin nor the bleed. */

.menu-overlay__actions {
  position: static;
  margin: var(--s2) 0 0;
  padding: 0;
  background: none;
  justify-content: center;
  margin-top: var(--s2);
}

.menu-overlay__actions .menu-button { min-width: min(9rem, 100%); }

@keyframes menu-fade { from { opacity: 0; } to { opacity: 1; } }

@keyframes menu-pop {
  from { opacity: 0; transform: scale(0.98); }
  to { opacity: 1; transform: none; }
}

/* Non-blocking reconnect pill (bottom-centre). */

.menu-toast {
  position: fixed;
  left: 50%;
  bottom: max(var(--s5), var(--safe-bottom));
  transform: translateX(-50%);
  /* Session band: the one line telling the player a recovery is running must not
     sit under a window, the ESC menu or the help sheet. */
  z-index: 402;
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 7px var(--s4);
  border-radius: 4px;
  background: rgba(13, 20, 23, 0.88);
  border: 1px solid var(--ui-line-soft);
  border-inline-start: 2px solid var(--accent);
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.34);
  color: color-mix(in srgb, var(--accent) 68%, var(--text));
  font-weight: 600;
  font-size: calc(13px * var(--ui-scale) * var(--pg-condense));
  line-height: 1;
  animation: menu-toast-in var(--dur-slow) var(--ease) both;
  width: max-content;
  max-width: calc(100% - var(--menu-safe-left) - var(--menu-safe-right));
}

.menu-toast .menu-spinner { width: 14px; height: 14px; }

@keyframes menu-toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translateX(-50%); }
}
/* Stacked WoW-style action bars (layout + stacking only). The per-slot visuals
 * (.hot-slot / .hot-cd / .hot-key / .hot-name / .hot-icon, plus the .filled /
 * .insufficient / .flash / .ready states and the cooldown sweep) are reused
 * verbatim from styles.css — this file owns only the cluster layout, the bar
 * stacking, the slot-size/keybind polish, and the in-place config affordance.
 *
 * Stack order top -> bottom: Extra Bar 2, Extra Bar 1, Main. The rows are emitted
 * main-first in the DOM (so the always-on bar is the layout anchor) and rendered
 * bottom-up via flex column-reverse. */

.action-bars {
  --action-bars-slot: calc(40px * var(--ui-scale, 1));
  /* Recall rides on the left end of the cluster, held off the main bar by a gap
   * wide enough to read as its own thing rather than a ninth binding. The
   * cluster is shifted back by half that column so the main bar itself stays
   * screen-centred — the bar the player aims at does not move because an innate
   * slot sits beside it. */
  --action-bars-recall-gap: calc(12px * var(--ui-scale, 1));
  --action-bars-recall-column: calc(var(--action-bars-slot) + var(--action-bars-recall-gap));
  position: fixed;
  bottom: var(--hud-safe-bottom);
  left: 50%;
  transform: translateX(calc(-50% - var(--action-bars-recall-column) / 2));
  pointer-events: none; /* gaps pass clicks through; slots opt back in below */
  z-index: 30;
}

/* Recall column first, then the stack of real bars: a row, so the innate slot
 * sits beside the bars instead of stacking above them. */
.action-bars__content {
  display: flex;
  flex-direction: row;
  align-items: flex-end; /* Recall rests on the main bar's bottom edge */
  gap: var(--action-bars-recall-gap);
}

.action-bars__stack {
  display: flex;
  flex-direction: column-reverse; /* main (first child) sits at the bottom */
  align-items: center;
  gap: 4px;
}

.action-bar {
  display: flex;
  flex-direction: row;
  gap: 3px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* A hidden extra bar leaves the layout so the remaining bars reflow flush. */
.action-bar.is-hidden { display: none; }

/* The combat anchor is a run of individual faceted controls, never a dark panel
 * laid across the world. Slot size stays compact enough for three optional rows. */
.action-bars .hot-slot {
  width: var(--action-bars-slot);
  height: var(--action-bars-slot);
  pointer-events: auto;
  padding: calc(2px * var(--ui-scale));
  background: rgba(7, 12, 14, 0.82);
  border-color: rgba(184, 203, 202, 0.38);
  border-radius: 2px;
  clip-path: polygon(
    0 0,
    calc(100% - 7px) 0,
    100% 7px,
    100% 100%,
    7px 100%,
    0 calc(100% - 7px)
  );
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.025);
}
.action-bars .hot-slot:hover {
  border-color: color-mix(in srgb, var(--accent) 72%, #fff);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    0 3px 12px rgba(0, 0, 0, 0.32);
}
/* Empty slots remain recessed without disappearing against bright terrain. */
.action-bars .hot-slot:not(.filled) { opacity: 0.5; }
/* A bound slot gets a touch more presence (the loadout is what matters). */
.action-bars .hot-slot.filled {
  background: rgba(7, 12, 14, 0.78);
  border-color: color-mix(in srgb, var(--gold) 64%, transparent);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.09),
    0 3px 10px rgba(0, 0, 0, 0.4);
}
.action-bars .hot-slot.filled .hot-icon {
  clip-path: polygon(
    0 0,
    calc(100% - 6px) 0,
    100% 6px,
    100% 100%,
    6px 100%,
    0 calc(100% - 6px)
  );
}

/* Slot-to-slot drag: the picked-up slot fades and the valid drop target lights
 * up (the shared .pd-drop class from styles.css already tints the border). */
.action-bars .hot-slot.hot-dragging { opacity: 0.35; cursor: grabbing; }
.action-bars .hot-slot.hot-dragging.hot-discarding {
  opacity: 0.12;
  filter: grayscale(0.9);
  transform: scale(0.82);
}
.action-bars .hot-slot.pd-drop {
  box-shadow: inset 0 0 0 2px var(--accent), 0 0 8px rgba(201, 164, 94, 0.4);
}

/* Native skill-window drags use a staged occupied slot instead of snapshotting
 * the whole catalog row. Keep it off-canvas but fully rendered until the browser
 * captures the drag image; the .action-bars ancestor supplies exact slot polish. */
.action-bars.hotbar-drag-image {
  top: -1000px;
  right: auto;
  bottom: auto;
  left: -1000px;
  transform: none;
  z-index: -1;
}
.action-bars.hotbar-drag-image .hot-slot {
  pointer-events: none;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .action-bars .hot-slot.hot-dragging.hot-discarding {
    transform: none;
    transition: none;
  }
}
.reduce-motion .action-bars .hot-slot.hot-dragging.hot-discarding {
  transform: none;
  transition: none;
}

/* Keybind label: top-left, compact pill (inherits the prominent .hot-key look
 * from styles.css). Shows the short form (1–8, F1–F8, S-F1–S-F8). */
.action-bars .hot-key {
  top: 2px;
  left: 3px;
  font-size: calc(10px * var(--ui-scale));
  padding: 1px 3px;
  border-radius: 2px;
  background: rgba(5, 9, 11, 0.68);
}

/* In-place config: a small gear handle to the right of the cluster opening a
 * compact popover with the two extra-bar toggles + the editing lock. */
.action-bars__config {
  position: absolute;
  right: -34px;
  bottom: 0;
  pointer-events: auto;
}
.action-bar__handle {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid rgba(184, 203, 202, 0.26);
  border-radius: 2px;
  background: rgba(7, 12, 14, 0.6);
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: var(--cursor-action);
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}
.action-bar__handle img { width: 20px; height: 20px; object-fit: contain; display: block; margin: auto; }
.action-bar__handle:hover { color: var(--accent); border-color: var(--accent); }

.action-bars__popover {
  position: absolute;
  right: 0;
  bottom: 32px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  white-space: nowrap;
  background: rgba(10, 14, 20, 0.94);
  border: 1px solid var(--ui-control-line);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
.action-bars__opt {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  color: #d6dde6;
  cursor: var(--cursor-action);
}
.action-bars__opt input { cursor: var(--cursor-action); }

.action-bars__cycle,
.action-bars__rotate,
.action-bars__attack { display: none; }

/* Editing-lock cue: the whole cluster reads as non-editable (the slots keep
 * firing on click — only drag/clear are gated, in hud.ts). */
.action-bars.is-locked .hot-slot { cursor: var(--cursor-action); }
.action-bars.is-locked .action-bar__handle { color: var(--accent-2, #5fd38a); }

/* Coach marks need two-tone separation from both halves of the world palette:
   the near-black halo survives pale sand and sunlight, while gold survives dark
   ground. `filter` is used for the outside halo because the faceted slot's
   clip-path clips an ordinary outward box-shadow. Empty slots normally sit at
   half opacity; a directed destination must be fully present. */
.action-bars .hot-slot.coach,
.action-bars .hot-slot.coach-hold,
.action-bars .hot-slot.coach-place {
  opacity: 1;
  filter:
    drop-shadow(0 0 1px rgba(2, 5, 7, 1))
    drop-shadow(0 0 5px rgba(2, 5, 7, 0.98))
    drop-shadow(0 0 9px color-mix(in srgb, var(--accent) 82%, transparent));
}

/* Onboarding coach mark: a slow gold breath on the slot that just received the
   first skill. Fires once per character, so it stays long enough to be noticed
   (docs/PLAYER_ONBOARDING.md). */
.hot-slot.coach { animation: hotcoach 1.1s ease-in-out 3; }
@keyframes hotcoach {
  0%, 100% { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 76%, transparent); }
  50% { box-shadow: inset 0 0 0 4px var(--accent); }
}
body.reduce-flashing .hot-slot.coach { animation: none; box-shadow: inset 0 0 0 3px var(--accent); }

/* Sustained coach highlight (doc "Coach Directives") — held until cleared, unlike
   the one-shot .coach breath that self-removes after three pulses. */
.hot-slot.coach-hold { animation: hotcoach 1.4s ease-in-out infinite; }
body.reduce-flashing .hot-slot.coach-hold { animation: none; box-shadow: 0 0 0 3px var(--accent); }

/* The same directive aimed at a skill the character owns but has not placed: the
   free slot reads as a destination rather than as a thing to press. Dashed rather
   than solid so it says "drop here", and inset so it never mimics a filled slot.

   It is the loudest state a slot has. Nothing else on the bar is asking for an
   action the player has not learned yet, so it carries an outward glow as well as
   the fill: a tint alone reads as decoration on a bar that is already lit. */
.hot-slot.coach-place {
  animation: hotcoachplace 1.35s ease-in-out infinite;
  outline: 3px dashed var(--accent);
  outline-offset: -3px;
  z-index: 1;
}
@keyframes hotcoachplace {
  0%, 100% {
    background-color: rgba(4, 8, 11, 0.96);
    box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 72%, transparent);
    outline-color: color-mix(in srgb, var(--accent) 82%, #fff);
    transform: scale(1);
  }
  50% {
    background-color: color-mix(in srgb, var(--accent) 58%, #10161a);
    box-shadow: inset 0 0 0 4px color-mix(in srgb, var(--accent) 90%, #fff);
    outline-color: #fff3c4;
    transform: scale(1.12);
  }
}
body.reduce-flashing .hot-slot.coach-place,
body.reduce-motion .hot-slot.coach-place {
  animation: none;
  background-color: rgba(4, 8, 11, 0.96);
  box-shadow: inset 0 0 0 4px var(--accent);
  outline-color: #fff3c4;
}

/* Tap-to-place portals the bar above a window only after Place is pressed. */
.action-bars.is-placing {
  z-index: 130;
}

.action-bars.is-placing:not(:has(.coach-place)) .action-bar--main .hot-slot {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}

.action-bars__place-prompt {
  position: absolute;
  right: 0;
  bottom: calc(100% + 8px);
  width: max-content;
  max-width: min(310px, calc(100dvw - 24px));
  padding: 7px 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 72%, transparent);
  border-radius: 7px;
  background: rgba(8, 12, 17, 0.96);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.48);
  color: var(--text);
  font-size: calc(12px * var(--ui-scale, 1));
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  pointer-events: none;
}
/* ESC root menu + tabbed Settings window. Standalone from styles.css (which stays
   frozen). Reuses .panel from styles.css and the button kit's classes read-only. */

/* --- ESC root menu (WoW-style centered button column) --------------------- */
.esc-root,
.settings-win {
  position: fixed;
  inset: 0;
  /* System-menu band: above every window, or a window covers the menu and stays clickable through it. */
  z-index: 150;
  display: none;
  align-items: center;
  justify-content: center;
  /* Centred inside the safe edges, not the glass: a column taller than a phone's
     safe box scrolls rather than resting on the home bar. */
  padding: var(--hud-safe-top) var(--hud-safe-right) var(--hud-safe-bottom) var(--hud-safe-left);
  background: rgba(8, 10, 16, 0.6);
}
.esc-root__panel {
  width: 240px;
  max-height: 100%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.esc-root__panel h1 {
  margin: 0 0 6px;
}
/* The menu's buttons are the kit's variants, stacked full-width; the surface
   only seats the taller menu rhythm and hangs the key cap off the edge. */
.esc-root__btn {
  padding: 10px 14px;
  position: relative;
}
/* Key cap worn by whichever control Escape currently performs (Resume at the menu
   root, Back inside Settings), so the key's next effect is readable at a glance. */
.esc-key-cap {
  flex: none;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid currentColor;
  font: 600 10px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .04em;
  opacity: .55;
}
/* Taken out of flow so the label stays on the same centre line as its siblings. */
.esc-root__btn .esc-key-cap {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
}

/* Themed volume sliders (replace the native blue OS range control). */
.settings-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 20px; background: transparent; cursor: var(--cursor-action);
  --fill: 50%;
}
.settings-range::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent) 0%, #ffd98a var(--fill), rgba(255, 255, 255, 0.12) var(--fill), rgba(255, 255, 255, 0.12) 100%);
}
.settings-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 15px; height: 15px; margin-top: -4.5px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ffe6a6, var(--accent) 68%);
  border: 1px solid #b9862c; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.settings-range:focus-visible { outline: none; }
.settings-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 40%, transparent); }
.settings-range::-moz-range-track { height: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); }
.settings-range::-moz-range-progress { height: 6px; border-radius: 999px; background: var(--accent); }
.settings-range::-moz-range-thumb { width: 15px; height: 15px; border: 1px solid #b9862c; border-radius: 50%; background: var(--accent); }

/* Sound on/off: a themed toggle instead of a dominating gold bar. */
.settings-toggle {
  width: 100%; padding: 9px 12px; border-radius: 8px; cursor: var(--cursor-action);
  font-weight: 700; font-size: 12px;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, rgba(14, 19, 26, 0.6));
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  transition: background .12s, color .12s, border-color .12s;
}
.settings-toggle:hover { background: color-mix(in srgb, var(--accent) 22%, rgba(14, 19, 26, 0.6)); }
.settings-toggle.is-off {
  color: var(--muted); background: rgba(14, 19, 26, 0.6); border-color: #3a4453;
}

/* --- Settings window (left rail + scrolling content pane) ------------------ */
.settings-dialog {
  width: 600px;
  max-width: 94vw;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow: hidden;
}
.settings-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.settings-dialog__title { margin: 0; }
/* Back is the kit's ghost at the dialog's own padding; the key cap beside it
   is the surface's to hang. */
.settings-back {
  padding: 8px 12px;
  font-size: 12px;
}
.settings-body {
  display: flex;
  gap: 14px;
  min-height: 0;
  flex: 1;
}

/* Left rail of tabs */
.settings-rail {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 140px;
  flex: 0 0 140px;
  border-right: 1px solid var(--ui-line-soft);
  padding-right: 10px;
}
.settings-rail__tab {
  text-align: left;
padding: 10px 12px;
  font-size: 13px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: var(--cursor-action);
}
.settings-rail__tab:hover {
  background: var(--panel-2);
  color: #e6e6e6;
}
.settings-rail__tab.is-active {
  background: var(--panel-2);
  border-color: var(--ui-line-soft);
  color: var(--accent);
  font-weight: 600;
}

/* Content pane */
.settings-pane-host {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
}
.settings-pane {
  display: none;
  flex-direction: column;
  gap: 10px;
}

/* One control row */
.settings-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.settings-row--check {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
  cursor: var(--cursor-action);
}
.settings-row__label {
  font-size: 12px;
  color: var(--muted);
}
.settings-row__note {
  font-size: 11px;
  color: var(--muted);
  opacity: 0.75;
}
.settings-row__head {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--muted);
}

/* Per-tab footer (reset row) */
.settings-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--ui-line-soft);
}

/* Controls (rebind grid) */
.settings-controls {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.settings-controls__grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 10px;
  align-items: center;
  font-size: 12px;
}
.settings-controls__label {
  color: var(--muted);
}
.settings-controls__key {
  min-width: 110px;
  font-size: 11px;
  padding: 3px 8px;
  background: var(--panel-2);
  border: 1px solid var(--ui-line-soft);
  border-radius: 6px;
  color: #e6e6e6;
  cursor: var(--cursor-action);
}
.settings-controls__key.is-reassigned {
  box-shadow: 0 0 0 2px var(--gold);
  transition: box-shadow 120ms ease-out;
}
.settings-controls__ref {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.6;
  margin-top: 4px;
}
.settings-controls__ref b {
  color: var(--accent);
}
/* hud-primitives.css — shared CSS for the Wave-1 HUD feedback primitives
   (AlertStack 2.a, ResultFlash 2.d, ResourcePips 2.b). Loaded once via index.html
   alongside styles.css. The section-4 design tokens these reference
   (--good/--info/--skillmsg/--warn/--hotbar-h/--pip-*) live in styles.css :root;
   the blocks below fall back to literals where a token may be absent. */

/* ── 2.a AlertStack — UIErrorsFrame-style system/error lines ───────────────── */

.alert-stack {
  position: fixed;
  left: 50%;
  bottom: calc(var(--hotbar-h, calc(96px * var(--ui-scale))) + 12px * var(--ui-scale));
  transform: translateX(-50%);
  display: flex;
  flex-direction: column-reverse; /* newest line nearest the action bars */
  align-items: center;
  gap: calc(4px * var(--ui-scale));
  max-width: 90vw;
  pointer-events: none;
  z-index: 22;
  --feedback-enter-x: 0px;
  --feedback-enter-y: calc(7px * var(--ui-scale));
}

.alert-stack[data-hud-anchor-x="left"] { align-items: flex-start; --feedback-enter-x: calc(-7px * var(--ui-scale)); }

.alert-stack[data-hud-anchor-x="right"] { align-items: flex-end; --feedback-enter-x: calc(7px * var(--ui-scale)); }

.alert-stack[data-hud-anchor-y="top"] {
  flex-direction: column;
  --feedback-enter-y: calc(-7px * var(--ui-scale));
}

.alert-line {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: calc(6px * var(--ui-scale));
  max-width: 100%;
  padding: calc(5px * var(--ui-scale)) calc(13px * var(--ui-scale));
  border: 0;
  border-right: calc(2px * var(--ui-scale)) solid transparent;
  border-left: calc(2px * var(--ui-scale)) solid currentColor;
  background: linear-gradient(90deg, rgba(7, 12, 14, .82), rgba(7, 12, 14, .48) 72%, transparent);
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 600;
  line-height: 1.3;
  text-shadow: 0 1px 2px #000;
  filter: drop-shadow(0 calc(4px * var(--ui-scale)) calc(8px * var(--ui-scale)) rgba(0, 0, 0, .26));
  animation: hud-feedback-in var(--hud-fold-duration, 180ms) var(--hud-fold-ease, cubic-bezier(.2, .78, .24, 1)) both;
}

.alert-line .alert-msg {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Tone bands — colour is always paired with the left gold/coloured tick + text,
   never hue-only (the message copy itself carries the meaning). */

.alert-line.error { color: var(--danger); }

.alert-line.warn  { color: var(--accent-2); }

.alert-line.info  { color: var(--muted); }

.alert-line.skill { color: var(--skillmsg, #b48ce0); }

.alert-stack[data-hud-anchor-x="right"] .alert-line {
  flex-direction: row-reverse;
  border-right-color: currentColor;
  border-left-color: transparent;
  background: linear-gradient(270deg, rgba(7, 12, 14, .82), rgba(7, 12, 14, .48) 72%, transparent);
  text-align: right;
}

.alert-stack[data-hud-anchor-x="center"] .alert-line {
  border-color: transparent;
  background: linear-gradient(90deg, transparent, rgba(7, 12, 14, .74) 18%, rgba(7, 12, 14, .74) 82%, transparent);
  box-shadow: inset 0 1px color-mix(in srgb, currentColor 58%, transparent);
  text-align: center;
}

/* ×N coalesce badge — tabular so the count doesn't jitter; hidden until N>1. */

.alert-line .alert-x {
  font-variant-numeric: tabular-nums;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  color: var(--accent);
  opacity: 0.9;
}

.alert-line .alert-x:empty { display: none; }

.alert-line.leaving { animation: alert-out 200ms ease-in both; }

@keyframes hud-feedback-in {
  from {
    opacity: 0;
    transform: translate(var(--feedback-enter-x), var(--feedback-enter-y)) scale(.985);
  }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}

@keyframes alert-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Motion opt-out: instant opacity, no slide. Gated by the OS query AND the two
   runtime body classes the HUD already toggles (readReduceFlashing /
   readReduceMotion). Removal stays timer-driven, so lines still clear on time. */

@media (prefers-reduced-motion: reduce) {
  .alert-line, .alert-line.leaving { animation: none; }
}

body.reduce-flashing .alert-line, body.reduce-flashing .alert-line.leaving,
body.reduce-motion   .alert-line, body.reduce-motion   .alert-line.leaving {
  animation: none;
}

/* ── 2.g Notice feed — loot / gear / quest / system event notices ──────────
   Same row grammar as the alert line above: tone tick on the left, copy in the
   middle, coalesce badge on the right. Docked in the top-right rail under the
   quest tracker, clear of the centre column's cast lane, and carrying no offset
   of its own. */

.notice-feed {
  display: flex;
  flex-direction: column;   /* newest at the bottom; live rows never shift */
  align-items: stretch;
  gap: calc(4px * var(--ui-scale));
  width: max-content;
  max-width: min(92vw, calc(320px * var(--ui-scale)));
  pointer-events: none;
  z-index: 36;
  --feedback-enter-x: calc(7px * var(--ui-scale));
  --feedback-enter-y: calc(-7px * var(--ui-scale));
}

.notice-feed[data-hud-anchor-x="left"] {
  --feedback-enter-x: calc(-7px * var(--ui-scale));
}

.notice-feed[data-hud-anchor-x="center"] {
  align-items: center;
  --feedback-enter-x: 0px;
}

.notice-feed[data-hud-anchor-y="bottom"] {
  flex-direction: column-reverse;
  --feedback-enter-y: calc(7px * var(--ui-scale));
}

.notice-row {
  display: flex;
  align-items: center;
  gap: calc(9px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(9px * var(--ui-scale)) calc(5px * var(--ui-scale)) calc(14px * var(--ui-scale));
  border: 0;
  border-right: calc(2px * var(--ui-scale)) solid var(--notice-tone, var(--muted));
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(7, 12, 14, 0.42), rgba(7, 12, 14, 0.8));
  clip-path: polygon(calc(9px * var(--ui-scale)) 0, 100% 0, 100% 100%, 0 100%, calc(9px * var(--ui-scale)) 50%);
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
  font-weight: 600;
  line-height: 1.3;
  text-shadow: 0 1px 2px #000;
  box-shadow: 0 calc(4px * var(--ui-scale)) calc(12px * var(--ui-scale)) rgba(0, 0, 0, 0.22);
  animation: hud-feedback-in var(--hud-fold-duration, 180ms) var(--hud-fold-ease, cubic-bezier(.2, .78, .24, 1)) both;
}

.notice-feed[data-hud-anchor-x="left"] .notice-row {
  padding-right: calc(14px * var(--ui-scale));
  padding-left: calc(9px * var(--ui-scale));
  border-right: 0;
  border-left: calc(2px * var(--ui-scale)) solid var(--notice-tone, var(--muted));
  background: linear-gradient(270deg, rgba(7, 12, 14, .42), rgba(7, 12, 14, .8));
  clip-path: polygon(0 0, calc(100% - calc(9px * var(--ui-scale))) 0, 100% 50%, calc(100% - calc(9px * var(--ui-scale))) 100%, 0 100%);
}

.notice-feed[data-hud-anchor-x="right"] .notice-row {
  flex-direction: row-reverse;
  text-align: right;
}

.notice-feed[data-hud-anchor-x="right"] .notice-row__copy { align-items: flex-end; }

.notice-feed[data-hud-anchor-x="center"] .notice-row {
  border: 0;
  background: linear-gradient(90deg, transparent, rgba(7, 12, 14, .78) 16%, rgba(7, 12, 14, .78) 84%, transparent);
  clip-path: none;
  box-shadow:
    inset 0 1px color-mix(in srgb, var(--notice-tone, var(--muted)) 52%, transparent),
    0 calc(4px * var(--ui-scale)) calc(12px * var(--ui-scale)) rgba(0, 0, 0, .22);
}

/* Tone bands, one per notice intent. The tick and the copy carry the tone
   together; the row's own words state what happened, so meaning never rests on
   hue — and a failure never reads as a neutral system line. */

.notice-row--notable { --notice-tone: var(--accent); }

.notice-row--positive { --notice-tone: var(--good); }

.notice-row--negative { --notice-tone: var(--danger); }

.notice-row--neutral { --notice-tone: var(--muted); }

.notice-row--notable .notice-row__title { color: var(--accent); }

.notice-row--negative .notice-row__title { color: var(--danger); }

.notice-row__icon {
  flex: 0 0 auto;
  width: calc(30px * var(--ui-scale));
  height: calc(30px * var(--ui-scale));
  border-radius: calc(5px * var(--ui-scale));
  image-rendering: auto;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.notice-row__copy {
  display: flex;
  flex-direction: column;
  gap: calc(1px * var(--ui-scale));
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.15;
}

.notice-row__eyebrow {
  color: var(--notice-tone, var(--muted));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* An item name is one line and ellipsises; a sentence wraps to two, because the
   clipped tail of "Quest complete — 500 Reputation" is the part that matters. */

.notice-row__title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.notice-row__title.is-wrapping {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Coalesce badge: the same tabular ×N the alert stack uses, so a repeat reads
   the same wherever feedback lands. */

.notice-row__badge {
  flex: 0 0 auto;
  align-self: flex-start;
  font-variant-numeric: tabular-nums;
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  color: var(--notice-tone, var(--accent));
}

.notice-row__badge.is-empty { display: none; }

.notice-row.is-leaving { animation: notice-out 200ms ease-in both; }

@keyframes notice-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Motion opt-out mirrors the alert stack: rows appear and clear instantly and
   removal stays timer-driven. */

@media (prefers-reduced-motion: reduce) {
  .notice-row, .notice-row.is-leaving { animation: none; }
}

body.reduce-flashing .notice-row, body.reduce-flashing .notice-row.is-leaving,
body.reduce-motion   .notice-row, body.reduce-motion   .notice-row.is-leaving {
  animation: none;
}

/* ===== ResultFlash (HUD spec 2.d) — proc/success/fail glow, floaters, bursts =====
   Uses tokens: --accent, --danger, --good, --info, --ui-scale. All sizes scale
   with --ui-scale; all motion is gated by readReduceFlashing()/
   readDisableDamageFlashes() in JS (which swaps to the .rf-static / .float-num
   .static / .big-burst.static classes) AND by the reduced-motion blocks below. */

/* --- Glow rings (flashEl) --- */

@keyframes rf-success {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 85%, transparent); }
  100% { box-shadow: 0 0 0 calc(16px * var(--ui-scale)) color-mix(in srgb, var(--gold) 0%, transparent); }
}

@keyframes rf-fail {
  0%, 100% { transform: translateX(0);                              box-shadow: 0 0 0 calc(2px * var(--ui-scale)) rgba(224, 83, 61, 0); }
  20%      { transform: translateX(calc(-4px * var(--ui-scale)));   box-shadow: 0 0 0 calc(2px * var(--ui-scale)) rgba(224, 83, 61, 0.95); }
  40%      { transform: translateX(calc(4px * var(--ui-scale))); }
  60%      { transform: translateX(calc(-3px * var(--ui-scale)));   box-shadow: 0 0 0 calc(2px * var(--ui-scale)) rgba(224, 83, 61, 0.7); }
  80%      { transform: translateX(calc(3px * var(--ui-scale))); }
}

@keyframes rf-proc {
  0%   { box-shadow: 0 0 0 0 var(--rf-tint, var(--accent)); }
  55%  { box-shadow: 0 0 calc(9px * var(--ui-scale)) calc(3px * var(--ui-scale)) var(--rf-tint, var(--accent)); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

@keyframes rf-levelup {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 90%, transparent);                    filter: brightness(1.35); }
  100% { box-shadow: 0 0 0 calc(22px * var(--ui-scale)) color-mix(in srgb, var(--gold) 0%, transparent); filter: brightness(1); }
}

.rf-glow          { border-radius: inherit; }

.rf-glow.success  { animation: rf-success 360ms ease-out; }

.rf-glow.fail     { animation: rf-fail    360ms ease-in-out; }

.rf-glow.proc     { animation: rf-proc    420ms ease-out; }

.rf-glow.levelup  { animation: rf-levelup 900ms ease-out; }

.rf-glow.heal     { --rf-tint: var(--good); animation: rf-proc 420ms ease-out; }

.rf-glow.mana     { --rf-tint: var(--info); animation: rf-proc 420ms ease-out; }

/* Reduced-motion glow: a held static border, no keyframe (JS clears after 600ms). */

.rf-static          { border-radius: inherit; box-shadow: 0 0 0 calc(2px * var(--ui-scale)) var(--rf-tint, var(--accent)); }

.rf-static.success,
.rf-static.levelup,
.rf-static.proc     { --rf-tint: var(--accent); }

.rf-static.fail     { --rf-tint: var(--danger); }

.rf-static.heal     { --rf-tint: var(--good); }

.rf-static.mana     { --rf-tint: var(--info); }

/* --- Floating numbers (floatNumber) — host must be position:relative --- */

@keyframes float-rise {
  0%   { opacity: 0; transform: translate(-50%, calc(2px * var(--ui-scale)))  translateX(var(--rf-drift, 0px)); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, calc(-28px * var(--ui-scale))) translateX(var(--rf-drift, 0px)); }
}

.float-num {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, 0);
  font: 700 calc(15px * var(--ui-scale)) / 1 "Segoe UI", system-ui, sans-serif;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
  pointer-events: none;
  white-space: nowrap;
  z-index: 30;
  animation: float-rise 900ms ease-out forwards;
}

.float-num.heal { color: var(--good); }

.float-num.mana { color: var(--info); }

.float-num.crit { color: var(--accent); font-size: calc(21px * var(--ui-scale)); font-weight: 800; }

/* 1.4x + bolder, not colour-only */

/* Reduced-motion floater: a static pill that JS fades (opacity) and removes. */

.float-num.static { animation: none; transform: translate(-50%, calc(-6px * var(--ui-scale))); transition: opacity 240ms linear; }

/* --- Centred bursts (bigBurst) --- */

.big-burst {
  position: fixed;
  left: 50%;
  top: 38%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 120;
  text-align: center;
  font: 800 calc(34px * var(--ui-scale)) / 1.05 "Cinzel", "Segoe UI", serif;
  letter-spacing: calc(2px * var(--ui-scale));
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.85);
  animation: rf-levelup 1200ms ease-out forwards;
}

.big-burst.levelup     { color: var(--accent); }

.big-burst.refine-ok   { color: var(--good); }

.big-burst.refine-fail { color: var(--danger); }

.big-burst.craft-ok    { color: var(--good); }

.big-burst.craft-fail  { color: var(--danger); }

.big-burst.gameover    { color: var(--danger); font-size: calc(40px * var(--ui-scale)); }

/* Reduced-motion burst: a static 1s toast that JS fades (opacity) and removes. */

.big-burst.static {
  top: 22%;
  box-shadow: none;
  animation: none;
  transition: opacity 260ms linear;
}

/* --- Reduced-motion fallback (belt-and-suspenders; JS already swaps classes) --- */

@media (prefers-reduced-motion: reduce) {
  .rf-glow, .float-num, .big-burst { animation-duration: 1ms !important; }
}

body.reduce-motion .rf-glow,
body.reduce-motion .float-num,
body.reduce-motion .big-burst { animation-duration: 1ms !important; }

/* ── Loot flight — a pickup travelling from the ground into the bag ─────────── */

/* The flyer is parked on the document body so no HUD window can clip it, and it
   is driven entirely by the Web Animations API: the rules here only describe what
   the travelling icon looks like, never where it is. */

.loot-fly {
  position: fixed;
  left: 0;
  top: 0;
  width: calc(40px * var(--ui-scale));
  height: calc(40px * var(--ui-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  /* Above every window and screen overlay — it crosses all of them, and a raised
     window would otherwise swallow the arrival. Tooltips and context menus stay
     higher: what the player asked to read outranks a passing icon. */
  z-index: 190;
  border-radius: calc(6px * var(--ui-scale));
  /* Rarity rides the trail: the tier is legible in peripheral vision, before the
     icon is close enough to recognise. */
  box-shadow:
    0 0 calc(10px * var(--ui-scale)) var(--loot-fly-tint, transparent),
    0 0 calc(22px * var(--ui-scale)) color-mix(in srgb, var(--loot-fly-tint, transparent) 45%, transparent);
}

.loot-fly[data-rarity="common"] { box-shadow: none; }

.loot-fly-icon { width: 100%; height: 100%; object-fit: contain; }

.loot-fly-count {
  position: absolute;
  right: calc(-2px * var(--ui-scale));
  bottom: calc(-3px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
}

.loot-fly-count:empty { display: none; }

/* ── Loot landing — the destination reacting to an arrival ──────────────────── */

/* Every rung of the bag ladder (cell, grid, menu button, menu launcher) takes the
   `loot-land` class while it is reacting. The glow and the border are animated in
   script, beside the crossing they follow: a bag cell carries its rarity colour
   as an inline style, and only an animation outranks that. The class is left as
   the state hook for anything that needs to know an arrival is landing. */

/* ===== ResourcePips (HUD spec 2.b) ===== */

/* Pip hue tokens (paired with per-kind glyphs, never hue-only). */

:root {
  --pip-sphere: #ffb84d;
  --pip-coin:   #e8c34a;
  --pip-soul:   #9bd0ff;
  --pip-charm:  #cdd6e0;
  --pip-shield: #9aa7b5;
  --pip-scar:   #e0533d;
}

/* Row: docks under the self unit-frame's SP bar. */

.res-pips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: calc(3px * var(--ui-scale));
  margin-top: calc(3px * var(--ui-scale));
  line-height: 1;
  pointer-events: none;
}

/* One pip. Default = dim socket; .filled = lit + glow. */

.res-pip {
  --pip-c: var(--muted);
  width: calc(13px * var(--ui-scale));
  height: calc(13px * var(--ui-scale));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--pip-c);
  opacity: 0.30;                         /* dim socket */
  transform: scale(0.92);
  transition: opacity 0.14s ease, transform 0.14s ease, filter 0.14s ease;
}

.res-pip img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}

.res-pip.filled {
  opacity: 1;
  transform: scale(1);
  filter: drop-shadow(0 0 calc(4px * var(--ui-scale)) var(--pip-c));
}

/* Per-kind hue supports the generated silhouette. */

.res-pip.sphere { --pip-c: var(--pip-sphere); }

.res-pip.coin   { --pip-c: var(--pip-coin); }

.res-pip.soul   { --pip-c: var(--pip-soul); }

.res-pip.charm  { --pip-c: var(--pip-charm); }

.res-pip.shield { --pip-c: var(--pip-shield); }

.res-pip.scar   { --pip-c: var(--pip-scar); }

/* Charm element tint (reuses the element palette; falls back to the charm hue). */

.res-pip.charm[data-elem="fire"]   { --pip-c: #ff6a3d; }

.res-pip.charm[data-elem="water"]  { --pip-c: #4db5ff; }

.res-pip.charm[data-elem="wind"]   { --pip-c: #6fe0a8; }

.res-pip.charm[data-elem="earth"]  { --pip-c: #d8a861; }

.res-pip.charm[data-elem="ghost"]  { --pip-c: #b48ce0; }

.res-pip.charm[data-elem="holy"]   { --pip-c: #ffe7a3; }

.res-pip.charm[data-elem="dark"], .res-pip.charm[data-elem="shadow"] { --pip-c: #8a7fb0; }

.res-pip.charm[data-elem="poison"] { --pip-c: #9bcf4a; }

/* Compact variant for nameplates / the target frame. */

.res-pips.compact { gap: calc(2px * var(--ui-scale)); margin-top: calc(1px * var(--ui-scale)); }

.res-pips.compact .res-pip {
  width: calc(12px * var(--ui-scale));
  height: calc(12px * var(--ui-scale));
}

/* Motion: gained pip spawns (scale .4->1 + glow pulse), lost pip pops. */

@keyframes pip-spawn {
  0%   { transform: scale(0.4); opacity: 0; text-shadow: 0 0 0 transparent; }
  60%  { transform: scale(1.25); opacity: 1; text-shadow: 0 0 calc(8px * var(--ui-scale)) var(--pip-c); }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes pip-pop {
  0%   { transform: scale(1); opacity: 1; }
  100% { transform: scale(0); opacity: 0; }
}

.res-pip.pip-spawn { animation: pip-spawn 180ms ease-out; }

.res-pip.pip-pop   { animation: pip-pop 140ms ease-in; }

/* High-contrast: thicken the socket so dim pips stay legible without glow. */

body.high-contrast .res-pip { opacity: 0.55; filter: none; }

body.high-contrast .res-pip.filled { filter: none; outline: calc(1px * var(--ui-scale)) solid var(--pip-c); border-radius: 50%; }

/* Reduced motion / flashing: instant fill/empty, no keyframes (TS also skips
   adding the classes when readReduceFlashing() is set). */

@media (prefers-reduced-motion: reduce) {
  .res-pip { transition: none; }
  .res-pip.pip-spawn, .res-pip.pip-pop { animation: none; }
}

body.reduce-motion .res-pip,
body.reduce-flashing .res-pip { transition: none; }

body.reduce-motion .res-pip.pip-spawn,
body.reduce-motion .res-pip.pip-pop,
body.reduce-flashing .res-pip.pip-spawn,
body.reduce-flashing .res-pip.pip-pop { animation: none; }

/* ===== ItemSlot + Paperdoll (HUD spec 2.e) =====
   The generic item cell + the equip mannequin built from it. Sizes scale with
   --ui-scale; each cell honours an optional per-slot --slot-size override. */

.item-slot {
  --slot-size: calc(44px * var(--ui-scale));
  position: relative;
  width: var(--slot-size);
  height: var(--slot-size);
  box-sizing: border-box;
  border: 1px solid #313c4e;
  border-top-color: #3e4b60;
  border-bottom-color: #232c3a;
  border-radius: calc(7px * var(--ui-scale));
  background: linear-gradient(160deg, #232d3c 0%, #141a24 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), inset 0 -3px 6px rgba(0, 0, 0, 0.32), 0 1px 3px rgba(0, 0, 0, 0.3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: var(--cursor-action);
  user-select: none;
}

/* Struck-metal corner highlight. */

.item-slot::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(60% 45% at 28% 16%, rgba(255, 255, 255, 0.09), transparent 70%);
  pointer-events: none;
}

.item-slot.empty { cursor: var(--cursor-default); }

.item-slot.locked { cursor: var(--cursor-default); opacity: 0.7; }

/* Hover/focus = gold outline (the "yours / actionable" cue). */

.item-slot.filled:hover,
.item-slot.filled:focus-visible {
  outline: calc(2px * var(--ui-scale)) solid var(--accent);
  outline-offset: -1px;
}

/* Icon — the 3D model render (or type-glyph fallback), fit to the cell. */

.item-slot .is-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

.item-slot .is-icon img {
  width: 76%;
  height: 76%;
  object-fit: contain;
  image-rendering: auto;
  display: block;
}

/* Painted steel slot emblem on an empty cell — a subtle "what goes here" hint,
   inset so it never touches the frame. Higher specificity than the item-icon
   img rule above so the smaller size wins. */

.item-slot .is-icon .is-empty-img {
  width: 52%;
  height: 52%;
  object-fit: contain;
  opacity: 0.32;
  filter: grayscale(0.15) brightness(1.05);
  pointer-events: none;
  transition: opacity 0.12s ease;
}

.item-slot.empty:hover .is-icon .is-empty-img { opacity: 0.5; }

/* Explicit rarity name, bottom-left; enemy-tier art is never reused for items. */

.item-slot .is-rarity-label {
  position: absolute;
  left: calc(2px * var(--ui-scale));
  bottom: calc(1px * var(--ui-scale));
  max-width: calc(42px * var(--ui-scale));
  overflow: hidden;
  color: var(--text);
  font-size: calc(7px * var(--ui-scale));
  font-weight: 800;
  line-height: 1;
  text-overflow: clip;
  pointer-events: none;
  text-shadow: 0 1px 2px #000;
}

/* Quantity badge, bottom-right (tabular so stacks don't jitter). */

.item-slot .is-qty {
  position: absolute;
  right: calc(2px * var(--ui-scale));
  bottom: calc(1px * var(--ui-scale));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-shadow: 0 1px 2px #000;
  pointer-events: none;
}

/* Refine ▲N badge, top-left, in --accent. */

.item-slot .is-badge.refine-up {
  position: absolute;
  left: calc(2px * var(--ui-scale));
  top: calc(1px * var(--ui-scale));
  font-size: calc(9px * var(--ui-scale));
  font-weight: 700;
  color: var(--accent);
  text-shadow: 0 1px 2px #000;
  pointer-events: none;
}

/* Corner state flags use promoted bitmap art so state is never hue-only. */

.item-slot .is-flag {
  position: absolute;
  top: calc(1px * var(--ui-scale));
  right: calc(2px * var(--ui-scale));
  width: calc(18px * var(--ui-scale));
  height: calc(18px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 1px 2px #000);
  pointer-events: none;
}

.item-slot.damaged.bound .is-flag.bound {
  right: calc(20px * var(--ui-scale));
}

/* Damaged piece — red border + desaturated, dimmed icon. */

.item-slot.damaged { border-color: var(--danger); }

.item-slot.damaged .is-icon img { filter: grayscale(0.7) brightness(0.85); }

/* Bound-on-equip — gold border tint. */

.item-slot.bound { border-color: var(--accent); }

/* Equip-switch queued — ghost (dashed) ring. */

.item-slot.switch {
  outline: calc(2px * var(--ui-scale)) dashed var(--accent);
  outline-offset: calc(-2px * var(--ui-scale));
  opacity: 0.92;
}

/* Equip mannequin: two slot columns flanking a centre figure. */

.paperdoll {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(8px * var(--ui-scale));
}

.paperdoll .pd-col {
  display: flex;
  flex-direction: column;
  gap: calc(4px * var(--ui-scale));
}

.paperdoll .pd-mannequin {
  position: relative;
  flex: 1 1 auto;
  min-width: calc(96px * var(--ui-scale));
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(6px * var(--ui-scale));
  border: 1px solid #2a3342;
  border-radius: calc(8px * var(--ui-scale));
  background:
    radial-gradient(80% 60% at 50% 12%, color-mix(in srgb, var(--gold) 6%, transparent), transparent 60%),
    linear-gradient(180deg, #1a2130 0%, #0f141d 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), inset 0 -12px 24px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}

/* Faint pedestal glow under the figure's feet. */

.paperdoll .pd-mannequin::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(6px * var(--ui-scale));
  width: 66%;
  height: calc(10px * var(--ui-scale));
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.5), transparent);
}

.paperdoll .pd-mannequin-img {
  position: relative;
  z-index: 1;
  max-width: 100%;
  max-height: calc(230px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.5));
}

.paperdoll .pd-slot[data-eqp] { flex: 0 0 auto; }

/* Shield slot taken over by a two-handed weapon (bow, 2H sword): show the
   weapon dimmed with a "both hands" marker so the empty shield reads as blocked,
   not available. */

.paperdoll .pd-slot.pd-blocked { opacity: 0.6; }

.paperdoll .pd-slot.pd-blocked .is-icon img { filter: grayscale(0.5) brightness(0.82); }

/* High-contrast: thicken slot borders, drop the hover glow. */

body.high-contrast .item-slot { border-width: 2px; }

body.high-contrast .item-slot.filled:hover,
body.high-contrast .item-slot.filled:focus-visible { outline-width: calc(3px * var(--ui-scale)); }

/* ===== DataGrid (HUD spec 2.c) — sortable/searchable list window =============
   The workhorse list behind bag, storage, vend, market, refine, mail,
   quest, roster. Lives inside a GameWindow body (the host element). Tokens:
   --grid-line (hairlines), --row-hover (hover wash), --sel-bar (selection bar,
   falls back to --accent), --accent (sort arrows). All sizes scale with
   --ui-scale; the only motion is the 120ms row enter/leave fade, gated by the
   reduced-motion blocks below (and skipped in JS via readReduceFlashing()). */

.dgrid {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  font-size: calc(13px * var(--ui-scale));
  color: var(--text);
}

/* Tools row: live search + a count chip. */

.dgrid-tools {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale)) calc(6px * var(--ui-scale));
  border-bottom: 1px solid var(--grid-line, var(--ui-line-soft));
}

.dgrid-search {
  flex: 1 1 auto;
  min-width: 0;
  padding: calc(4px * var(--ui-scale)) calc(8px * var(--ui-scale));
  background: var(--panel-2, #1c2533);
  border: 1px solid var(--grid-line, var(--ui-line-soft));
  border-radius: calc(5px * var(--ui-scale));
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
}

.dgrid-search:focus-visible { outline: calc(2px * var(--ui-scale)) solid var(--accent); outline-offset: 0; }

.dgrid-search:disabled { opacity: 0.5; }

.dgrid-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: calc(11px * var(--ui-scale));
  color: var(--muted);
}

/* Header + rows share a grid template (set inline from the column widths). */

.dgrid-head {
  display: grid;
  align-items: stretch;
  gap: calc(2px * var(--ui-scale));
  padding: 0 calc(6px * var(--ui-scale));
  border-bottom: 1px solid var(--grid-line, var(--ui-line-soft));
  background: var(--panel, #141a24);
  position: sticky;
  top: 0;
  z-index: 1;
}

.dgrid-th {
  display: flex;
  align-items: center;
  gap: calc(3px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(4px * var(--ui-scale));
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
  user-select: none;
}

.dgrid-th.align-r { justify-content: flex-end; }

.dgrid-th.align-c { justify-content: center; }

.dgrid-th.sortable { cursor: var(--cursor-action); }

.dgrid-th.sortable:hover { color: var(--text); }

.dgrid-th.sortable:focus-visible { outline: calc(2px * var(--ui-scale)) solid var(--accent); outline-offset: -2px; }

.dgrid-th.sorted { color: var(--text); }

/* Sort arrow in --accent; empty until the column is the active sort. */

.dgrid-sort {
  font-size: calc(9px * var(--ui-scale));
  line-height: 1;
  color: var(--accent);
}

.dgrid-sort:empty { display: none; }

/* Scrolling body. */

.dgrid-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0 calc(6px * var(--ui-scale)) calc(4px * var(--ui-scale));
}

/* One row — a grid mirroring the header columns. */

.dgrid-row {
  display: grid;
  align-items: center;
  gap: calc(2px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale)) 0;
  border-bottom: 1px solid var(--grid-line, var(--ui-line-soft));
  border-left: calc(3px * var(--ui-scale)) solid transparent; /* reserve the selection bar gutter */
  cursor: var(--cursor-default);
}

.dgrid-row:hover { outline: 1px solid var(--accent); outline-offset: calc(-1px * var(--ui-scale)); background: var(--row-hover, color-mix(in srgb, var(--gold) 10%, transparent)); }

.dgrid-row.selected { border-left-color: var(--sel-bar, var(--accent)); background: var(--row-hover, color-mix(in srgb, var(--gold) 10%, transparent)); }

.dgrid-cell {
  min-width: 0;
  padding: 0 calc(4px * var(--ui-scale));
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Numeric columns: right-aligned + tabular so digits line up for scanning. */

.dgrid-cell.align-r { text-align: right; font-variant-numeric: tabular-nums; }

.dgrid-cell.align-c { text-align: center; }

/* Footer (gold totals / pager). */

.dgrid-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(8px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border-top: 1px solid var(--grid-line, var(--ui-line-soft));
  font-variant-numeric: tabular-nums;
  font-size: calc(12px * var(--ui-scale));
  color: var(--muted);
}

/* Empty-state placeholder. */

.dgrid-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(24px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  font-style: italic;
}

/* Busy overlay + spinner. */

.dgrid-busy {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 14, 20, 0.5);
  z-index: 3;
}

.dgrid-spinner {
  width: calc(28px * var(--ui-scale));
  height: calc(28px * var(--ui-scale));
  border: calc(3px * var(--ui-scale)) solid var(--grid-line, var(--ui-line-soft));
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: ui-spin 720ms linear infinite;
}

/* Row enter/leave fade — the only row motion (delta packets mutate in place). */

@keyframes dgrid-row-in  { from { opacity: 0; } to { opacity: 1; } }

@keyframes dgrid-row-out { from { opacity: 1; } to { opacity: 0; } }

.dgrid-row.dgrid-entering { animation: dgrid-row-in  120ms ease-out both; }

.dgrid-row.dgrid-leaving  { animation: dgrid-row-out 120ms ease-in  both; pointer-events: none; }

/* High-contrast: thicker hairlines + selection bar, no hover wash. */

body.high-contrast .dgrid-row { border-bottom-width: calc(1px * var(--ui-scale)); }

body.high-contrast .dgrid-row.selected { border-left-width: calc(4px * var(--ui-scale)); }

body.high-contrast .dgrid-row:hover { outline-width: calc(2px * var(--ui-scale)); background: none; }

/* Reduced motion / flashing: no spinner spin, no row fades (JS also skips the
   enter/leave classes when readReduceFlashing() is set). */

@media (prefers-reduced-motion: reduce) {
  .dgrid-spinner { animation: none; }
  .dgrid-row.dgrid-entering, .dgrid-row.dgrid-leaving { animation: none; }
}

body.reduce-motion .dgrid-spinner,
body.reduce-flashing .dgrid-spinner { animation: none; }

body.reduce-motion .dgrid-row.dgrid-entering,
body.reduce-motion .dgrid-row.dgrid-leaving,
body.reduce-flashing .dgrid-row.dgrid-entering,
body.reduce-flashing .dgrid-row.dgrid-leaving { animation: none; }

/* ===== EnhanceWindow (HUD spec §3.2) — grade / enchant / reform shell ========
   The window wears the shared service-window chrome — the modal frame, the
   win-head header, pricePanel gold, the shared `.hud-gauge`, statusPanel and
   the ron-btn kit; the only local paint is the two selection lists and the
   chance fill gradient. Composes ItemSlot (target) and ResultFlash (results).
   Tokens only; all sizes scale with --ui-scale. */

/* The frame comes from the shared -panel rule; this owns only the working
   column and the window's dimensions. */

.enhance-panel {
  display: flex;
  flex-direction: column;
  color: var(--text);
  width: min(calc(560px * var(--ui-scale)), calc(100vw - 32px));
  max-height: min(calc(560px * var(--ui-scale)), calc(100vh - 48px));
}

/* The working body between the header and the status line. */

.enhance-panel .enhance-win {
  display: flex;
  flex-direction: column;
  gap: calc(10px * var(--ui-scale));
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  padding: calc(10px * var(--ui-scale));
  font-size: calc(13px * var(--ui-scale));
}

/* Target row: the worked ItemSlot + its (refine-prefixed) name. */

.enhance-win .enhance-target {
  display: flex;
  align-items: center;
  gap: calc(10px * var(--ui-scale));
}

.enhance-win .enhance-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.enhance-win .enhance-name.muted { color: var(--muted); font-weight: 400; font-style: italic; }

/* Equipment + material lists sit side by side so the gauge and action stay in view. */

.enhance-win .enhance-lists {
  display: flex;
  gap: calc(10px * var(--ui-scale));
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.enhance-win .enhance-equips { height: calc(172px * var(--ui-scale)); display: flex; flex-direction: column; flex: 1; min-width: 0; }

.enhance-win .enhance-mats { height: calc(172px * var(--ui-scale)); display: flex; flex-direction: column; flex: 1.35; min-width: 0; }

/* Column header row — the muted labels over each list. */

.enhance-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(2.4rem, 3.2rem) minmax(2.4rem, 3.2rem);
  gap: calc(6px * var(--ui-scale));
  padding: 0 calc(6px * var(--ui-scale)) calc(3px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.enhance-cols.cols-one { grid-template-columns: minmax(0, 1fr); }

.enhance-cols span:not(:first-child) { text-align: right; }

/* Rows: listbox options rendered in Lit. The picked row carries its state in
   words and weight, never hue alone. */

.enhance-rows {
  display: flex;
  flex-direction: column;
  gap: calc(2px * var(--ui-scale));
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: calc(2px * var(--ui-scale));
  border: 1px solid var(--ui-line-soft);
  border-radius: calc(6px * var(--ui-scale));
  background: rgba(5, 10, 12, 0.22);
}

.enhance-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(2.4rem, 3.2rem) minmax(2.4rem, 3.2rem);
  gap: calc(6px * var(--ui-scale));
  align-items: center;
  width: 100%;
  min-width: 0;
  padding: calc(4px * var(--ui-scale)) calc(6px * var(--ui-scale));
  border: 1px solid transparent;
  border-radius: calc(5px * var(--ui-scale));
  background: none;
  color: var(--text);
  font: inherit;
  font-size: calc(12px * var(--ui-scale));
  text-align: left;
  cursor: var(--cursor-action);
}

.enhance-row:hover:not(:disabled) { border-color: var(--ui-line-bright); background: var(--row-hover, rgba(255, 255, 255, 0.04)); }

.enhance-row:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }

.enhance-row.is-picked { border-color: var(--accent); color: var(--accent); font-weight: 700; }

.enhance-row:disabled { opacity: 0.55; cursor: var(--cursor-default); }

.enhance-row-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.enhance-row-num { text-align: right; font-variant-numeric: tabular-nums; }

.enhance-row.insufficient .enhance-have-short,
.enhance-have-short { color: var(--danger); font-weight: 700; }

.enhance-empty {
  margin: 0;
  padding: calc(8px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(12px * var(--ui-scale));
  font-style: italic;
}

/* Gold cost line — the amount itself is the shared pricePanel. */

.enhance-win .enhance-gold {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* Success gauge — the shared `.hud-gauge` at window scale; only the row height
   and the `.chance` fill gradient are local. */

.enhance-win .enhance-gauge { display: flex; flex-direction: column; gap: calc(3px * var(--ui-scale)); }

.enhance-win .enhance-gauge-label {
  font-size: calc(11px * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}

.enhance-win .enhance-gauge .hud-gauge {
  --gauge-h: calc(16px * var(--ui-scale));
  --gauge-ring: var(--grid-line, var(--ui-line-soft));
}

body.high-contrast .enhance-win .enhance-gauge .hud-gauge { --gauge-ring-w: 2px; }

.enhance-win .hud-gauge-fill.chance { background: linear-gradient(90deg, #c8821f, var(--accent)); }

/* Guarantee / protect copy — never hue-only. */

.enhance-win .enhance-guaranteed,
.enhance-win .enhance-protect {
  margin: 0;
  font-size: calc(12px * var(--ui-scale));
  font-weight: 600;
  color: var(--good, #6fd06a);
}

.enhance-win .enhance-blessing {
  display: flex;
  align-items: center;
  gap: calc(8px * var(--ui-scale));
  font-size: calc(12px * var(--ui-scale));
  cursor: var(--cursor-action);
}

.enhance-win .enhance-blessing input {
  margin: 0;
}

/* Smith pick-dialog (HUD §3.2) — the small "choose one item" window shared by
   the equip picker, the appraise list and the card-insert list. Reuses the
   GameWindow chrome + DataGrid; only the body layout + the gold confirm button
   (mirrors .enhance-action, scoped here) are local. Tokens only. */

.smith-pick {
  display: flex;
  flex-direction: column;
  gap: calc(8px * var(--ui-scale));
  min-width: calc(220px * var(--ui-scale));
  padding: calc(4px * var(--ui-scale));
  color: var(--text);
  font-size: calc(13px * var(--ui-scale));
}

.smith-pick .smith-pick-grid { height: calc(220px * var(--ui-scale)); display: flex; }

.smith-pick .smith-pick-grid .dgrid { min-height: 0; }

.smith-pick .smith-pick-action {
  align-self: stretch;
  padding: calc(7px * var(--ui-scale)) calc(12px * var(--ui-scale));
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: calc(6px * var(--ui-scale));
  color: #1a1206;
  font-size: calc(13px * var(--ui-scale));
  font-weight: 700;
  cursor: var(--cursor-action);
}

.smith-pick .smith-pick-action:hover { filter: none; background: color-mix(in srgb, var(--accent) 88%, white); }

/* ── 2.f Nameplate — world-projected overhead plate ─────────────────────────
   One stack per actor: name, vitals, threat, windup. The cast row is the target
   frame's own `.target-cast` element, so the two surfaces share one look by
   construction rather than by hand-matching. Positioned in CSS pixels from the
   renderer's anchor feed, which is why the text is device-resolution crisp and
   stays square to the screen at every camera pitch. */

.nameplate-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none; /* a plate must never eat a click meant for the actor */
  z-index: 6; /* over the canvas, under every HUD window and the target panel */
  contain: strict; /* plate writes can't invalidate layout outside this layer */
}

.nameplate {
  --plate-accent: var(--hud-line, #5a6472);
  /* The bars are capped at one width for the whole field, so a crowd of plates
     reads as one gauge column. The name is not capped — it is the identity, and
     a truncated one is worse than a wide one. */
  --plate-bar-w: calc(104px * var(--ui-scale));
  position: absolute;
  top: 0;
  left: 0;
  width: max-content;
  /* The stack hangs off its anchor (see the translate in the plate's transform),
     so it always sits on the actor's head no matter which rows are showing. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(2px * var(--ui-scale));
  will-change: transform;
  contain: layout style;
}

.nameplate-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(4px * var(--ui-scale));
}

.nameplate-name {
  font: 700 calc(11px * var(--ui-scale)) / 1.25 'Segoe UI', sans-serif;
  color: var(--plate-name, #f2f5fa);
  text-shadow: 0 1px 2px #000, 0 0 3px rgba(0, 0, 0, 0.85);
  white-space: nowrap;
  text-align: center;
}

/* Speech — what the actor just said, hanging off the top of its own plate. It is
   the same box the gauges below it are: same ink, same hairline, same 1px black
   edge. Two things separate it: a lit top edge in the speaker's accent, and a
   tail. The tail is the only one in the HUD, which is exactly what makes it read
   as spoken rather than as one more gauge. No speaker name — the plate's own name
   row, one row down, already says who is talking. */

.nameplate-speech {
  --speech-accent: var(--gold);
  position: relative;
  max-width: calc(190px * var(--ui-scale));
  margin-bottom: calc(4px * var(--ui-scale));
  padding: calc(3px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid rgba(0, 0, 0, 0.65);
  /* Thicker than the other three edges and never faded: at gameplay distance a
     hairline accent disappears, and this edge is the only thing separating your
     own line from everyone else's. */
  border-top: calc(2px * var(--ui-scale)) solid var(--speech-accent);
  border-radius: calc(5px * var(--ui-scale));
  background: rgba(4, 6, 10, 0.86);
  box-shadow: 0 calc(2px * var(--ui-scale)) calc(6px * var(--ui-scale)) rgba(0, 0, 0, 0.5);
  font: 500 calc(11px * var(--ui-scale)) / 1.35 'Segoe UI', sans-serif;
  color: #e9eef6;
  text-align: center;
  text-shadow: 0 1px 2px #000;
  /* A long line wraps rather than clipping: the glyph budget that keeps it to a
     few rows is applied where the line is raised, and the chat log keeps the
     whole thing either way. Nothing here may set `overflow`, which would take the
     tail with it. */
  overflow-wrap: break-word;
}

/* Your own line reads cool, so it separates from the warm accent every other
   speaker on screen is drawn with. */

.nameplate-speech.self {
  --speech-accent: #9bd0ff;
}

.nameplate-speech::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(-5px * var(--ui-scale));
  width: 0;
  height: 0;
  transform: translateX(-50%);
  border-left: calc(4px * var(--ui-scale)) solid transparent;
  border-right: calc(4px * var(--ui-scale)) solid transparent;
  border-top: calc(5px * var(--ui-scale)) solid rgba(4, 6, 10, 0.86);
}

.nameplate-relation {
  flex: 0 0 auto;
  width: calc(12px * var(--ui-scale));
  height: calc(12px * var(--ui-scale));
  align-self: center;
  box-sizing: border-box;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px #000);
}

.nameplate[data-player-relation='guild'] .nameplate-relation {
  image-rendering: pixelated;
}

/* The attention arrow tops the plate column, so it is never covered by the name,
   the gauges or the quest mark. Colour is a flat tinted silhouette cut from the
   promoted arrow bitmap; the same bitmap multiplied over it puts its faceted
   shading back, so the mark reads as world art rather than a flat glyph. */

.nameplate-attention {
  flex: 0 0 auto;
  width: calc(30px * var(--ui-scale));
  aspect-ratio: 256 / 155;
  background-color: var(--plate-attention, #f2f5fa);
  -webkit-mask: url('/play/assets/hud-attention-arrow-DYWFUDDH.webp') center / contain no-repeat;
  mask: url('/play/assets/hud-attention-arrow-DYWFUDDH.webp') center / contain no-repeat;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.75));
  transition: opacity 0.12s ease-out;
}

.nameplate-attention-shade {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

.reduce-motion .nameplate-attention { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .nameplate-attention { transition: none; }
}

/* The quest mark sits under the arrow and over everything else the plate says. */

.nameplate-quest-mark {
  flex: 0 0 auto;
  width: calc(22px * var(--ui-scale));
  height: calc(22px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.95));
  /* The hop squashes against the mark's own base rather than its middle. */
  transform-origin: 50% 100%;
}

/* The two marks the player can act on right now hop: something to take here, and
   something finished waiting to be handed in. A quest already underway holds
   still, so movement in a crowd always means there is something for you. The
   delay follows the shorthand, which would otherwise reset it to zero and put
   every mark on screen back on the same beat. */

.nameplate[data-quest-mark='available'] .nameplate-quest-mark,
.nameplate[data-quest-mark='ready_to_turn_in'] .nameplate-quest-mark,
.nameplate[data-quest-mark='story_available'] .nameplate-quest-mark,
.nameplate[data-quest-mark='story_ready_to_turn_in'] .nameplate-quest-mark {
  animation: nameplate-quest-mark-hop 2.4s cubic-bezier(0.33, 0, 0.2, 1) infinite;
  animation-delay: var(--plate-mark-phase, 0s);
}

/* The finished quest also carries a warm rim and hops on a shorter cycle: of the
   two marks that move, the hand-in is the more urgent. */

.nameplate[data-quest-mark='ready_to_turn_in'] .nameplate-quest-mark,
.nameplate[data-quest-mark='story_ready_to_turn_in'] .nameplate-quest-mark {
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.95))
    drop-shadow(0 0 6px rgba(255, 226, 154, 0.65));
  animation-duration: 1.7s;
}

/* Most of the cycle is rest. The hop is one beat of movement between long holds,
   never a constant float, so a plaza full of marks does not read as noise. */

@keyframes nameplate-quest-mark-hop {
  0%,
  55%,
  100% { transform: translateY(0) scaleY(1); }
  61% { transform: translateY(0) scaleY(0.9); }
  73% { transform: translateY(calc(-5px * var(--ui-scale))) scaleY(1.05); }
  85% { transform: translateY(0) scaleY(0.95); }
  93% { transform: translateY(calc(-1.5px * var(--ui-scale))) scaleY(1); }
}

/* Matched through the same state attribute the hop rides, so the opt-out outranks
   it instead of losing on specificity. */

.reduce-motion .nameplate[data-quest-mark] .nameplate-quest-mark { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .nameplate[data-quest-mark] .nameplate-quest-mark { animation: none; }
}

/* The objective marker rides the name row, not the mark slot above the plate: a
   monster that fills a current quest objective is annotated, never beaconed. It
   deliberately holds still — no hop — so a field of quest targets reads as
   information, not motion. Stillness is what keeps it quiet, so it can carry the
   size and the warm rim it needs to be seen at all: the mark sits on a moving
   creature at a distance, where a 12px slate glyph is lost against the ground. */

.nameplate-objective-mark {
  flex: 0 0 auto;
  width: calc(16px * var(--ui-scale));
  height: calc(16px * var(--ui-scale));
  align-self: center;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9))
    drop-shadow(0 0 4px rgba(255, 235, 160, 0.45));
}

.nameplate-rank {
  flex: 0 0 auto;
  width: calc(11px * var(--ui-scale));
  height: calc(11px * var(--ui-scale));
  display: block;
  align-self: center;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9));
}

/* Rank reads on the plate the same way it reads on the target panel, and outranks
   the friend/foe tint — an elite is an elite whoever it belongs to. */

.nameplate[data-tier='elite'] .nameplate-name,
.nameplate[data-tier='mvp'] .nameplate-name { color: var(--plate-accent); }

.nameplate[data-tier='elite'] .nameplate-hp,
.nameplate[data-tier='mvp'] .nameplate-hp { --gauge-ring: var(--plate-accent); }

/* Vitals and threat are one bar drawn twice — and that bar is the HUD's shared
   `.hud-gauge`, at plate size. A plate only says how wide it may grow. */

.nameplate-bar {
  /* Capped on a plate; the target panel reuses the same bar at panel width. */
  width: var(--plate-bar-w, 100%);
}

/* The bands themselves are the shared gauge's; a plate only decides when it has
   nothing to show. */

.nameplate.no-vitals .nameplate-hp { display: none; }

/* The harm on this actor, under its gauge. The same chip every other status
   surface draws, at the only density a plate has: art and its harm frame. A
   plate is read across a whole pull at a distance, so the countdown, the card
   and the tab stop stay on the focused target's frame. */

.nameplate-statuses {
  display: flex;
  justify-content: center;
  gap: calc(2px * var(--ui-scale));
}

.nameplate-statuses:empty { display: none; }

.plate-status-chip {
  box-sizing: border-box;
  width: calc(13px * var(--ui-scale));
  height: calc(13px * var(--ui-scale));
  min-width: 11px;
  min-height: 11px;
  border-radius: calc(2px * var(--ui-scale));
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 1px solid rgba(0, 0, 0, 0.65);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 70%, transparent);
}

/* Threat: how close the local player is to taking this monster. 100% = holding
   it. The band carries the colour AND the label, never colour alone. */

.nameplate-threat {
  --threat-accent: #7f8b9c;
}

.nameplate-threat-fill {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--threat-accent) 92%, #fff 8%),
    var(--threat-accent));
}

.nameplate-threat[data-band='holding'] { --gauge-ring: var(--threat-accent); }

@media (prefers-reduced-motion: reduce) {
  .nameplate-bar-fill { transition: none; }
}

body.reduce-motion .nameplate-bar-fill,
body.reduce-flashing .nameplate-bar-fill { transition: none; }

/* High contrast: the plate floats over a live 3D scene, so it cannot rely on a
   text shadow alone to stay legible. */

body.high-contrast .nameplate-rank {
  flex: 0 0 auto;
  width: calc(11px * var(--ui-scale));
  height: calc(11px * var(--ui-scale));
  display: block;
  align-self: center;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9));
}

body.high-contrast .nameplate-relation {
  filter: drop-shadow(0 0 2px #000) drop-shadow(0 1px 2px #000);
}

body.high-contrast .nameplate-name,
body.high-contrast .nameplate-bar-label {
  color: #fff;
  text-shadow: 0 0 3px #000, 0 1px 2px #000, 0 -1px 2px #000;
}

body.high-contrast .nameplate-speech {
  background: rgba(0, 0, 0, 0.94);
  border-color: #fff;
  color: #fff;
  text-shadow: 0 0 3px #000, 0 1px 2px #000, 0 -1px 2px #000;
}

body.high-contrast .nameplate-speech::after { border-top-color: rgba(0, 0, 0, 0.94); }

/* Docked windup: the target frame's cast bar at plate scale, directly under the
   gauges at the same width and anchor. */

/* The windup hangs BELOW the stack instead of joining it, so a cast starting
   never shoves the name and gauges up off the actor's head. */

.nameplate .target-cast {
  position: absolute;
  top: 100%;
  /* Centred on the plate's anchor at the gauge width, since the stack is now as
     wide as the name rather than as wide as the bars. */
  left: 50%;
  transform: translateX(-50%);
  width: var(--plate-bar-w);
  --gauge-h: max(14px, calc(15px * var(--ui-scale)));
  --gauge-shear: 2px;
  margin-top: calc(2px * var(--ui-scale));
}

.nameplate .target-cast-icon {
  width: calc(11px * var(--ui-scale));
  height: calc(11px * var(--ui-scale));
}

.nameplate .target-cast-label {
  left: calc(15px * var(--ui-scale));
  right: calc(26px * var(--ui-scale));
  top: calc(1px * var(--ui-scale));
  font-size: max(8px, calc(6px * var(--ui-scale)));
  line-height: calc(13px * var(--ui-scale));
}

.nameplate .target-cast-countdown {
  top: calc(3px * var(--ui-scale));
  font-size: max(7px, calc(5px * var(--ui-scale)));
}

/* The plate has room for one marker, not the panel's full row. "This one is
   winding up at ME" is the read you need on the mobs you are NOT focused on, so
   the danger markers survive and the rest fall away. */

.nameplate .target-cast-markers {
  left: auto;
  right: calc(2px * var(--ui-scale));
  bottom: auto;
  top: 50%;
  transform: translateY(-50%);
  justify-content: flex-end;
}

.nameplate .target-cast-marker { display: none; }

.nameplate .target-cast-marker--you,
.nameplate .target-cast-marker--cc,
.nameplate .target-cast-marker--lethal {
  display: inline-flex;
  max-width: calc(46px * var(--ui-scale));
  padding: 0 calc(3px * var(--ui-scale));
  font-size: max(7px, calc(5px * var(--ui-scale)));
}

/* Only one fits, so the most urgent wins: targeting-you, then lethal, then CC.
   Written against the row rather than sibling order, which puts the markers in
   the opposite order to their urgency. */

.nameplate .target-cast-markers:has(.target-cast-marker--you) .target-cast-marker--cc,
.nameplate .target-cast-markers:has(.target-cast-marker--you) .target-cast-marker--lethal,
.nameplate .target-cast-markers:has(.target-cast-marker--lethal) .target-cast-marker--cc {
  display: none;
}

.nameplate .target-cast-countdown { display: none; }

/* The focused target's plate outranks the field it stands in. */

.nameplate.focused { z-index: 1; }

.nameplate.focused .nameplate-name { font-size: calc(12px * var(--ui-scale)); }

.nameplate.focused .nameplate-bar {
  --gauge-h: max(10px, calc(11px * var(--ui-scale)));
}

/* The target panel's copy of the shared threat bar — same element and tokens as
   the nameplate's, at panel scale. */

/* Health is the panel's headline; threat is the line under it. Same box, same
   tokens, drawn thinner and off full chroma so a monster already on the player
   does not paint a saturated slab across the frame. */

.target-threat {
  --gauge-h: max(10px, calc(11px * var(--ui-scale)));
  /* On the panel it stands beside the full-size health gauge, so it leans the
     same amount rather than the plate's smaller shear. */
  --gauge-shear: 3px;
  margin-top: calc(2px * var(--ui-scale));
}

.target-threat .nameplate-threat-fill {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--threat-accent) 42%, transparent),
    color-mix(in srgb, var(--threat-accent) 28%, transparent));
}

/* Holding is the band the player is normally in, so at panel size it is the
   resting state of the frame, not an alarm: the accent stays on the fill and the
   box keeps the neutral edge every other gauge has. */

.target-threat[data-band='holding'] { --gauge-ring: rgba(220, 232, 226, 0.16); }

.target-threat .nameplate-threat-label {
  font-size: max(8px, calc(7px * var(--ui-scale)));
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.86);
}
/* Quest journal: owner-grouped accordion with detail kept beside its navigation row. */
.window.win-quests {
  width: calc(540px * var(--ui-scale));
  max-width: calc(100vw - 32px);
  background: color-mix(in srgb, var(--bg) 99%, transparent) padding-box;
}

.win-quests .win-body {
  max-height: min(72vh, calc(640px * var(--ui-scale)));
  padding: 0;
  overflow: auto;
}

ront-quest-achievement-window,
.qa-section { display: block; min-width: 0; }

.qa-journal-list {
  min-width: 0;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
}

.qa-owner-group { --qa-owner-color: var(--accent); }
.qa-owner-group + .qa-owner-group { border-block-start: 1px solid color-mix(in srgb, var(--grid-line) 72%, transparent); }

.qa-owner-heading {
  position: sticky;
  z-index: 3;
  top: 0;
  min-height: calc(30px * var(--ui-scale));
  border-block-end: 1px solid color-mix(in srgb, var(--qa-owner-color) 18%, var(--grid-line));
  padding: 0 calc(9px * var(--ui-scale));
  background: color-mix(in srgb, var(--qa-owner-color) 8%, var(--panel));
}

.qa-owner {
  display: flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  min-height: inherit;
  margin: 0;
  color: color-mix(in srgb, var(--qa-owner-color) 72%, var(--text));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 900;
  letter-spacing: 0.12em;
  line-height: 1;
  text-transform: uppercase;
}

.qa-owner img {
  width: calc(22px * var(--ui-scale));
  height: calc(22px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.8));
}

.qa-owner small {
  display: grid;
  place-items: center;
  min-width: calc(17px * var(--ui-scale));
  height: calc(17px * var(--ui-scale));
  margin-inline-start: auto;
  border: 1px solid color-mix(in srgb, var(--qa-owner-color) 32%, var(--grid-line));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.qa-owner small::before { content: attr(data-count); }

.qa-owner-list { display: grid; }
.qa-quest-item { min-width: 0; }
.qa-quest-item + .qa-quest-item { border-block-start: 1px solid color-mix(in srgb, var(--grid-line) 48%, transparent); }

.qa-quest-index-button {
  position: relative;
  display: grid;
  grid-template-columns: calc(10px * var(--ui-scale)) minmax(0, 1fr) auto;
  gap: calc(3px * var(--ui-scale)) calc(7px * var(--ui-scale));
  align-items: center;
  width: 100%;
  min-height: calc(48px * var(--ui-scale));
  border: 0;
  padding: calc(7px * var(--ui-scale)) calc(10px * var(--ui-scale));
  color: var(--text);
  background: transparent;
  font: inherit;
  text-align: start;
  cursor: var(--cursor-action);
}

.qa-quest-index-button::after {
  position: absolute;
  inset: calc(7px * var(--ui-scale)) auto calc(7px * var(--ui-scale)) 0;
  width: 2px;
  content: '';
  background: color-mix(in srgb, var(--qa-owner-color) 52%, transparent);
}

.qa-quest-index-button:hover { background: var(--row-hover); }

.qa-quest-item.is-expanded > .qa-quest-index-button {
  background: color-mix(in srgb, var(--qa-owner-color) 13%, var(--panel));
  box-shadow: inset calc(3px * var(--ui-scale)) 0 0 color-mix(in srgb, var(--qa-owner-color) 78%, var(--text));
}

.qa-quest-index-button:focus-visible {
  z-index: 1;
  outline: 2px solid var(--text);
  outline-offset: -3px;
}

.qa-quest-index-button .qa-row-title {
  grid-column: 2;
  min-width: 0;
  overflow: hidden;
  color: var(--text);
  font-size: calc(11px * var(--ui-scale));
  font-weight: 850;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.qa-index-objective {
  grid-column: 2;
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.qa-quest-index-button > strong {
  grid-column: 3;
  grid-row: 2;
  color: var(--text);
  font-size: calc(10px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A story row says where on the road it is, in the track's own brass, so the
   chapter count the compass reads is the same count the journal shows. */
.qa-index-story {
  color: color-mix(in srgb, var(--qa-owner-color) 78%, var(--text));
  font-weight: 800;
  letter-spacing: 0.04em;
}

/* A one-shot step previews as a word, not a fraction. */
.qa-index-step {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.qa-index-step.is-done { color: var(--good); }

.qa-lifecycle {
  grid-column: 3;
  grid-row: 1;
  align-self: start;
  border-block: 1px solid color-mix(in srgb, var(--qa-owner-color) 40%, transparent);
  padding: calc(2px * var(--ui-scale)) calc(4px * var(--ui-scale));
  color: color-mix(in srgb, var(--qa-owner-color) 68%, var(--text));
  font-size: calc(10px * var(--ui-scale));
  font-weight: 900;
  letter-spacing: 0.07em;
  line-height: 1.2;
  text-transform: uppercase;
  white-space: nowrap;
}

.qa-row-chevron {
  grid-column: 1;
  grid-row: 1 / span 2;
  width: calc(7px * var(--ui-scale));
  height: calc(7px * var(--ui-scale));
  border-inline-end: 1.5px solid var(--muted);
  border-block-end: 1.5px solid var(--muted);
  rotate: -45deg;
  transition: rotate 120ms ease;
}

.qa-quest-item.is-expanded .qa-row-chevron { rotate: 45deg; }

.qa-lifecycle--ready {
  border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  color: color-mix(in srgb, var(--accent) 70%, var(--text));
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* The pane opens under its row rather than appearing: a short settle from just
   above, so the eye follows the chevron down into the detail. */
.qa-quest-detail-pane {
  --qa-owner-color: var(--accent);
  min-width: 0;
  animation: qa-detail-open 160ms ease-out backwards;
  border-block-start: 1px solid color-mix(in srgb, var(--qa-owner-color) 20%, var(--grid-line));
  padding: calc(10px * var(--ui-scale)) calc(12px * var(--ui-scale)) calc(13px * var(--ui-scale)) calc(27px * var(--ui-scale));
  background:
    linear-gradient(115deg, color-mix(in srgb, var(--qa-owner-color) 7%, transparent), transparent 55%),
    color-mix(in srgb, var(--panel) 96%, transparent);
}

.qa-quest-description {
  margin: 0;
  border-inline-start: 2px solid color-mix(in srgb, var(--qa-owner-color) 55%, transparent);
  padding-inline-start: calc(9px * var(--ui-scale));
  color: color-mix(in srgb, var(--text) 72%, var(--muted));
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.42;
}

.qa-objective-section {
  margin-block-start: calc(10px * var(--ui-scale));
  border-block-start: 1px solid color-mix(in srgb, var(--grid-line) 84%, transparent);
  padding-block-start: calc(8px * var(--ui-scale));
}

.qa-objective-section h3 {
  margin: 0 0 calc(7px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.qa-objectives { display: grid; gap: calc(8px * var(--ui-scale)); }

.qa-objective {
  display: grid;
  grid-template-columns: calc(28px * var(--ui-scale)) minmax(0, 1fr);
  gap: calc(3px * var(--ui-scale)) calc(8px * var(--ui-scale));
  align-items: center;
}

.qa-objective-icon-wrap {
  position: relative;
  grid-row: 1 / span 2;
  width: calc(28px * var(--ui-scale));
  height: calc(28px * var(--ui-scale));
}

.qa-objective-icon {
  display: block;
  width: calc(28px * var(--ui-scale));
  height: calc(28px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.75));
}

.qa-objective-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(10px * var(--ui-scale));
  color: var(--text);
  font-size: calc(10px * var(--ui-scale));
}

.qa-objective-heading span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qa-objective-heading strong { flex: 0 0 auto; color: var(--text); font-size: calc(10px * var(--ui-scale)); font-variant-numeric: tabular-nums; }

/* A scripted step's instruction is a whole sentence, not a target name: it wraps
   instead of clipping, and its row carries no counter or bar. */
.qa-objective--goal .qa-objective-icon-wrap { grid-row: 1; }

.qa-objective--goal .qa-objective-heading span {
  overflow: visible;
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.42;
  white-space: normal;
}

@keyframes qa-detail-open {
  from { opacity: 0; translate: 0 calc(-4px * var(--ui-scale)); }
  to { opacity: 1; translate: 0 0; }
}

/* A step is a checklist line: a state word where the counter sits on a counted
   row, and no bar. Done is said in the journal's one "finished" colour. */
.qa-objective--step .qa-objective-icon-wrap { grid-row: 1; }
.qa-objective--step .qa-objective-heading span { overflow: visible; white-space: normal; line-height: 1.35; }
.qa-step-state {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.qa-objective--step.is-done .qa-step-state { color: var(--good); }
.qa-objective--step.is-done .qa-objective-icon { opacity: 0.55; }

.qa-progress-pill {
  display: flex;
  width: 100%;
  height: calc(8px * var(--ui-scale));
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--text) 11%, transparent);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.9);
  clip-path: polygon(3px 0, 100% 0, calc(100% - 3px) 100%, 0 100%);
}

.qa-progress-shared {
  width: var(--qa-shared-progress, 0%);
  background: linear-gradient(90deg, color-mix(in srgb, var(--qa-owner-color) 58%, var(--panel)), var(--qa-owner-color));
}

.qa-progress-self {
  width: var(--qa-self-progress, 0%);
  border-inline-start: 1px solid color-mix(in srgb, var(--bg) 90%, transparent);
  background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--accent) 38%, var(--text)) 0 3px, var(--accent-2) 3px 6px);
}

.qa-quest-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(7px * var(--ui-scale)) calc(14px * var(--ui-scale));
  margin-block-start: calc(10px * var(--ui-scale));
  border-block-start: 1px solid color-mix(in srgb, var(--grid-line) 84%, transparent);
  padding-block-start: calc(8px * var(--ui-scale));
}

.qa-quest-detail,
.qa-self-contribution { color: var(--muted); font-size: calc(10px * var(--ui-scale)); font-variant-numeric: tabular-nums; }
.qa-self-contribution { color: color-mix(in srgb, var(--accent) 62%, var(--text)); font-weight: 750; }
.qa-quest-detail--urgent { color: var(--danger); font-weight: 850; }

.qa-quest-reward {
  flex-basis: 100%;
  border-inline-start: 2px solid color-mix(in srgb, var(--accent) 62%, transparent);
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  color: color-mix(in srgb, var(--accent) 62%, var(--text));
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-size: calc(10.5px * var(--ui-scale));
  line-height: 1.35;
}

/* Itemized rewards: each promised item as icon + name, on the same accent rail
   the prose reward line uses. */
.qa-reward-section { margin-block-start: calc(10px * var(--ui-scale)); }

.qa-reward-section h3 {
  margin: 0 0 calc(6px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(9.5px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.qa-reward-items {
  display: flex;
  flex-wrap: wrap;
  gap: calc(6px * var(--ui-scale)) calc(10px * var(--ui-scale));
}

.qa-reward-item {
  display: inline-flex;
  align-items: center;
  gap: calc(6px * var(--ui-scale));
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  padding: calc(3px * var(--ui-scale)) calc(8px * var(--ui-scale)) calc(3px * var(--ui-scale)) calc(4px * var(--ui-scale));
  color: color-mix(in srgb, var(--accent) 62%, var(--text));
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: calc(10.5px * var(--ui-scale));
}

.qa-reward-item-icon {
  width: calc(22px * var(--ui-scale));
  height: calc(22px * var(--ui-scale));
  object-fit: contain;
  image-rendering: auto;
}

/* Achievements use one continuous chronicle plane: rank first, milestones second,
   recent authority events last. Rows are separated by rhythm and rails rather
   than nested card containers. */
.window.win-achievements {
  width: calc(470px * var(--ui-scale));
  max-width: calc(100vw - 32px);
}

.win-achievements .win-body {
  max-height: min(72vh, calc(620px * var(--ui-scale)));
  padding: 0;
  overflow: auto;
}

.qa-achievements {
  background:
    linear-gradient(115deg, color-mix(in srgb, var(--accent) 4%, transparent), transparent 48%),
    color-mix(in srgb, var(--bg) 73%, transparent);
}

.qa-achievements-overview {
  display: grid;
  grid-template-columns: calc(58px * var(--ui-scale)) minmax(0, 1fr) auto;
  gap: calc(4px * var(--ui-scale)) calc(11px * var(--ui-scale));
  align-items: center;
  padding: calc(13px * var(--ui-scale)) calc(14px * var(--ui-scale)) calc(12px * var(--ui-scale));
  border-block-end: 1px solid color-mix(in srgb, var(--accent) 25%, var(--grid-line));
  background:
    radial-gradient(circle at 10% 24%, color-mix(in srgb, var(--accent) 13%, transparent), transparent 29%),
    color-mix(in srgb, var(--panel) 94%, transparent);
}

.qa-achievements-emblem {
  grid-row: 1 / span 3;
  width: calc(58px * var(--ui-scale));
  height: calc(58px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.78));
}

.qa-achievements-rank { display: grid; gap: calc(2px * var(--ui-scale)); }
.qa-achievements-rank span {
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 850;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.qa-achievements-rank strong {
  color: color-mix(in srgb, var(--accent) 64%, var(--text));
  font-family: var(--font-display);
  font-size: calc(19px * var(--ui-scale));
  line-height: 1.1;
}

.qa-achievements-totals {
  display: flex;
  align-items: center;
  justify-content: end;
  gap: calc(12px * var(--ui-scale));
}
.qa-achievements-totals span {
  display: grid;
  justify-items: end;
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.qa-achievements-totals strong {
  color: var(--text);
  font-size: calc(14px * var(--ui-scale));
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.qa-achievement-level {
  grid-column: 2 / -1;
  display: flex;
  justify-content: space-between;
  gap: calc(8px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(10px * var(--ui-scale));
}
.qa-achievement-level strong { color: var(--text); font-variant-numeric: tabular-nums; }
.qa-achievement-level-progress {
  grid-column: 2 / -1;
  height: calc(7px * var(--ui-scale));
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--text) 11%, transparent);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.86);
  clip-path: polygon(3px 0, 100% 0, calc(100% - 3px) 100%, 0 100%);
}
.qa-achievement-level-progress span {
  display: block;
  width: var(--qa-achievement-progress, 0%);
  height: 100%;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 43%, var(--panel)), var(--accent));
}

.qa-achievement-list { display: grid; }
.qa-achievement-row {
  position: relative;
  display: grid;
  grid-template-columns: calc(34px * var(--ui-scale)) minmax(0, 1fr) auto;
  gap: calc(8px * var(--ui-scale));
  align-items: center;
  min-height: calc(54px * var(--ui-scale));
  padding: calc(8px * var(--ui-scale)) calc(11px * var(--ui-scale));
  border-block-end: 1px solid color-mix(in srgb, var(--grid-line) 63%, transparent);
}
.qa-achievement-row::before {
  position: absolute;
  inset: calc(8px * var(--ui-scale)) auto calc(8px * var(--ui-scale)) 0;
  width: 2px;
  content: '';
  background: color-mix(in srgb, var(--muted) 45%, transparent);
}
.qa-achievement-row--claimable {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.qa-achievement-row--claimable::before { background: var(--accent); }
.qa-achievement-row--rewarded { opacity: 0.76; }
.qa-achievement-row--rewarded::before { background: color-mix(in srgb, var(--good) 68%, transparent); }
.qa-achievement-row-icon {
  width: calc(34px * var(--ui-scale));
  height: calc(34px * var(--ui-scale));
  object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.75));
}
.qa-achievement-row-copy { min-width: 0; }
.qa-row-title { min-width: 0; overflow: hidden; color: var(--text); font-size: calc(11px * var(--ui-scale)); font-weight: 800; text-overflow: ellipsis; white-space: nowrap; }
.qa-row-meta { margin-top: calc(3px * var(--ui-scale)); color: var(--muted); font-size: calc(10px * var(--ui-scale)); }
.qa-achievement-state {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.qa-achievement-row--claimable .qa-achievement-state { color: color-mix(in srgb, var(--accent) 72%, var(--text)); }
.qa-achievement-row .ron-btn {
  grid-column: 3;
  grid-row: 1;
  margin-top: calc(19px * var(--ui-scale));
}

.qa-achievement-result {
  margin: calc(9px * var(--ui-scale)) calc(11px * var(--ui-scale)) 0;
  border-inline-start: 2px solid var(--muted);
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  color: var(--muted);
  background: color-mix(in srgb, var(--panel-2) 84%, transparent);
  font-size: calc(10px * var(--ui-scale));
}
.qa-achievement-result.is-success { border-color: var(--good); color: color-mix(in srgb, var(--good) 66%, var(--text)); }
.qa-achievement-result.is-failure { border-color: var(--danger); color: color-mix(in srgb, var(--danger) 68%, var(--text)); }

.qa-achievement-activity {
  padding: calc(11px * var(--ui-scale));
}
.qa-achievement-activity h2 {
  margin: 0 0 calc(5px * var(--ui-scale));
  color: var(--muted);
  font-size: calc(9px * var(--ui-scale));
  font-weight: 850;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.qa-achievement-activity .qa-row {
  display: grid;
  gap: calc(2px * var(--ui-scale));
  padding: calc(7px * var(--ui-scale)) 0;
  border-block-end: 1px solid color-mix(in srgb, var(--grid-line) 54%, transparent);
}
.qa-empty { padding: calc(18px * var(--ui-scale)); color: var(--muted); text-align: center; }
.qa-empty--quests { display: grid; justify-items: center; gap: calc(8px * var(--ui-scale)); padding-block: calc(54px * var(--ui-scale)); }
.qa-empty--quests img { width: calc(58px * var(--ui-scale)); height: calc(58px * var(--ui-scale)); object-fit: contain; opacity: 0.82; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.78)); }
.qa-empty--quests small { max-width: 32ch; color: color-mix(in srgb, var(--muted) 78%, transparent); font-size: calc(10.5px * var(--ui-scale)); line-height: 1.4; }
.qa-empty--achievements { display: grid; justify-items: center; gap: calc(7px * var(--ui-scale)); }
.qa-empty--achievements img { width: calc(44px * var(--ui-scale)); height: calc(44px * var(--ui-scale)); object-fit: contain; opacity: 0.7; }

@media (max-width: 720px) {
  .window.win-quests,
  .window.win-achievements {
    top: calc(54px * var(--ui-scale));
    left: 50%;
    right: auto;
    width: 94vw;
    max-width: 94vw;
    transform: translateX(-50%);
  }
  .win-quests .win-body { max-height: 72vh; }
  .qa-achievements-overview { grid-template-columns: calc(48px * var(--ui-scale)) minmax(0, 1fr); }
  .qa-achievements-emblem { width: calc(48px * var(--ui-scale)); height: calc(48px * var(--ui-scale)); }
  .qa-achievements-totals { grid-column: 2; justify-content: start; }
  .qa-achievement-level,
  .qa-achievement-level-progress { grid-column: 1 / -1; }
  .qa-achievement-row { grid-template-columns: calc(30px * var(--ui-scale)) minmax(0, 1fr) auto; }
  .qa-achievement-row-icon { width: calc(30px * var(--ui-scale)); height: calc(30px * var(--ui-scale)); }
  .qa-quest-index-button .qa-row-title {
    grid-column: 2 / -1;
    display: -webkit-box;
    overflow: hidden;
    white-space: normal;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }
  .qa-quest-index-button:has(.qa-lifecycle) .qa-lifecycle { grid-column: 2 / -1; grid-row: 2; justify-self: start; }
  .qa-quest-index-button:has(.qa-lifecycle) .qa-index-objective { grid-row: 3; }
  .qa-quest-index-button:has(.qa-lifecycle) > strong { grid-row: 3; }
  .qa-quest-index-button:has(.qa-lifecycle) .qa-row-chevron { grid-row: 1 / span 3; }
  .qa-quest-detail-pane { padding-inline: calc(24px * var(--ui-scale)) calc(10px * var(--ui-scale)); }
}

@media (prefers-reduced-motion: reduce) {
  .qa-row-chevron,
  .qa-progress-shared,
  .qa-progress-self { transition: none; }
  .qa-quest-detail-pane { animation: none; }
}

.reduce-motion .qa-row-chevron,
.reduce-motion .qa-progress-shared,
.reduce-motion .qa-progress-self { transition: none; }
.reduce-motion .qa-quest-detail-pane { animation: none; }

/* The journal's guide control (doc "Wayfinding"): offered only on objectives the
   authority can resolve to a place, so pressing it always does something. */
.qa-objective-guide {
  grid-column: 2;
  justify-self: end;
  margin-top: calc(2px * var(--ui-scale));
  padding: calc(2px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border-radius: 4px;
  border: 1px solid color-mix(in srgb, var(--wayfind-accent) 48%, transparent);
  background: transparent;
  color: color-mix(in srgb, var(--wayfind-accent) 82%, var(--text, #f2e9d0));
  font: inherit;
  font-size: calc(11px * var(--ui-scale));
  line-height: 1.3;
  white-space: nowrap;
  cursor: var(--cursor-action);
}
.qa-objective-guide:hover,
.qa-objective-guide:focus-visible {
  background: color-mix(in srgb, var(--wayfind-accent) 16%, transparent);
}
body.high-contrast .qa-objective-guide {
  border-color: var(--wayfind-accent);
  color: var(--text, #f2e9d0);
}

[data-ui-profile="handheld"] .qa-achievements-overview {
  grid-template-columns: calc(48px * var(--ui-scale)) minmax(0, 1fr);
}

[data-ui-profile="handheld"] .qa-quest-index-button .qa-row-title {
  grid-column: 2 / -1;
  display: -webkit-box;
  overflow: hidden;
  white-space: normal;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
/* The Archive: tab strip, index of pages, and the open page beside it. */
.window.win-archive {
  width: calc(620px * var(--ui-scale));
  max-width: calc(100vw - 32px);
  background: color-mix(in srgb, var(--bg) 99%, transparent) padding-box;
}

/* The body is the height authority: the index scrolls inside it so the tab strip
   and the found-count stay put however long the list gets. */
.win-archive .win-body {
  display: flex;
  flex-direction: column;
  height: min(72vh, calc(560px * var(--ui-scale)));
  padding: 0;
  overflow: hidden;
}

ront-archive-window,
.archive {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.archive-tabs {
  display: flex;
  gap: calc(2px * var(--ui-scale));
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale)) 0;
  border-block-end: 1px solid color-mix(in srgb, var(--grid-line) 72%, transparent);
  background: color-mix(in srgb, var(--panel) 84%, transparent);
}

.archive-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: calc(6px * var(--ui-scale)) calc(8px * var(--ui-scale));
  border: 1px solid transparent;
  border-block-end: none;
  border-radius: calc(4px * var(--ui-scale)) calc(4px * var(--ui-scale)) 0 0;
  background: transparent;
  color: color-mix(in srgb, var(--fg) 62%, transparent);
  font: inherit;
  cursor: pointer;
}

.archive-tab:hover { color: var(--fg); }

.archive-tab.is-active {
  border-color: color-mix(in srgb, var(--accent) 34%, var(--grid-line));
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
  color: var(--fg);
}

.archive-body {
  display: grid;
  grid-template-columns: minmax(0, 34%) minmax(0, 1fr);
  min-height: 0;
  flex: 1 1 auto;
}

.archive-index {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow-y: auto;
  border-inline-end: 1px solid color-mix(in srgb, var(--grid-line) 72%, transparent);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
}

.archive-row {
  display: block;
  width: 100%;
  padding: calc(6px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border: none;
  border-inline-start: calc(2px * var(--ui-scale)) solid transparent;
  background: transparent;
  color: var(--fg);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.archive-row:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }

.archive-row.is-active {
  border-inline-start-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.archive-row.is-locked {
  color: color-mix(in srgb, var(--fg) 38%, transparent);
  font-style: italic;
}

.archive-row-title {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.archive-page {
  min-width: 0;
  padding: calc(12px * var(--ui-scale));
  overflow-y: auto;
}

.archive-page.is-locked { color: color-mix(in srgb, var(--fg) 52%, transparent); }

.archive-page-title {
  margin: 0 0 calc(6px * var(--ui-scale));
  font-size: calc(15px * var(--ui-scale));
}

.archive-facts {
  margin: 0 0 calc(9px * var(--ui-scale));
  padding: 0;
  list-style: none;
  color: color-mix(in srgb, var(--fg) 62%, transparent);
  font-size: calc(11px * var(--ui-scale));
}

.archive-facts li + li { margin-block-start: calc(2px * var(--ui-scale)); }

.archive-page-body {
  margin: 0;
  line-height: 1.55;
}

.archive-empty,
.archive-hint {
  margin: 0;
  padding: calc(12px * var(--ui-scale));
  color: color-mix(in srgb, var(--fg) 52%, transparent);
}

.archive-foot {
  padding: calc(5px * var(--ui-scale)) calc(9px * var(--ui-scale));
  border-block-start: 1px solid color-mix(in srgb, var(--grid-line) 72%, transparent);
  background: color-mix(in srgb, var(--panel) 84%, transparent);
  color: color-mix(in srgb, var(--fg) 62%, transparent);
  font-size: calc(11px * var(--ui-scale));
  text-align: end;
}

@media (max-width: 720px) {
  .archive-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 40%) minmax(0, 1fr); }
  .archive-index { border-inline-end: none; border-block-end: 1px solid color-mix(in srgb, var(--grid-line) 72%, transparent); }
}
/* Handheld presentation profile. Desktop is the unprefixed HUD. Do not use
   max-width or (pointer: coarse) alone as a phone signal — see Handheld Play
   Presentation. */

:root {
  --hud-touch-min: 44px;
  --ime-inset: 0px;
}

[data-ui-profile="handheld"] {
  /* The bezel: how far from the glass edge anything sits when the device asks
     for no more. The safe edges (--hud-safe-*) are this or the device's inset,
     whichever is larger — the one way any surface touches the screen edge. */
  --hud-edge: 8px;
  /* Short-axis budget: thumbs stay in corner capsules. JS may stamp exact px. */
  --hud-skill: clamp(44px, 11dvh, 52px);
  --hud-skill-aa: clamp(56px, 15dvh, 72px);
  --hud-skill-gap: 6px;
  /* The near ring's coins. Smaller than the far ring's, which is what lets a
     second four stand in the band between Attack and the far ring at all: a ring
     of four spans a quarter turn, so its radius follows its coin size, and four
     full-size coins in there would overlap each other. */
  --hud-skill-inner: clamp(40px, 10dvh, 48px);
  /* Both rings put four coins on the quarter turn between "above Attack" and
     "left of Attack" — 0/30/60/90° — spaced so neighbouring circles keep
     --hud-skill-gap. The quarter is the whole budget: past 0° a coin leaves the
     screen edge, past 90° it drops below Attack. */
  --hud-arc-r-inner: max(
    calc((var(--hud-skill-inner) + var(--hud-skill-gap)) / (2 * sin(15deg))),
    calc(var(--hud-skill-aa) / 2 + var(--hud-skill-gap) + var(--hud-skill-inner) / 2)
  );
  --hud-arc-r: max(
    calc((var(--hud-skill) + var(--hud-skill-gap)) / (2 * sin(15deg))),
    calc(
      var(--hud-arc-r-inner)
      + var(--hud-skill-inner) / 2
      + var(--hud-skill) / 2
      + var(--hud-skill-gap)
    )
  );
  /* The far ring reaches its widest at 90° and its tallest at 0°, so the wheel
     is the ring's radius plus one coin on each of those two edges. The left
     column carries the innate controls the arc does not. */
  --hud-skill-wheel-w: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r)
    + var(--hud-skill) / 2
    + 12px
    + max(var(--hud-touch-min), var(--hud-skill))
  );
  --hud-skill-wheel-h: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r)
    + var(--hud-skill) / 2
    + 8px
  );
  --hud-stick: clamp(80px, 22dvh, 112px);
  --hud-cluster-inset: 8px;
  --hud-window-max: min(520px, calc(100vw - 64px));
  --hud-window-max-h: min(82dvh, calc(100dvh - var(--hud-safe-top, 8px) - var(--hud-safe-bottom, 8px) - 16px));
}

@media (min-width: 1100px) {
  [data-ui-profile="handheld"] {
    --hud-cluster-inset: 16px;
    --hud-window-max: min(780px, calc(100vw - 72px));
  }
}

/* Pin the play shell only after the world mounts. A frozen html/body on iOS
   also freezes the orientation query and clips login and character select. */
html[data-ui-profile="handheld"]:not(:has(.menu-root)),
html[data-ui-profile="handheld"]:not(:has(.menu-root)) body {
  overflow: hidden;
  overscroll-behavior: none;
  width: 100%;
  width: 100dvw;
  height: 100%;
  height: 100dvh;
  touch-action: none;
}

html[data-ui-profile="handheld"]:not(:has(.menu-root)) #app {
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: none;
}

html[data-ui-profile="handheld"]:has(.menu-root),
html[data-ui-profile="handheld"]:has(.menu-root) body,
html[data-ui-profile="handheld"]:has(.menu-root) #app {
  height: auto;
  min-height: 100%;
  min-height: 100dvh;
  overflow: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
}

html[data-ui-profile="handheld"]:has(.menu-root) #app {
  display: block;
}

html[data-ui-profile="handheld"] #game-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
}

/* The play shell is pinned so a drag on the world never pans the page, which
   leaves every scrolling surface to hand the vertical pan back for itself. A
   conversation is the one the player is acting through: its body and its list of
   replies are both longer than the plate on a phone. */
html[data-ui-profile="handheld"] .win-body,
html[data-ui-profile="handheld"] .chat-log,
html[data-ui-profile="handheld"] .npc-text,
html[data-ui-profile="handheld"] .npc-controls {
  touch-action: pan-y;
}

[data-ui-profile="handheld"] .action-bars,
[data-ui-profile="handheld"] .action-bars.hud-railed:not(.hud-layout-detached) {
  --action-bars-slot: var(--hud-skill);
  position: fixed;
  top: auto;
  right: var(--hud-safe-right);
  bottom: var(--hud-safe-bottom);
  left: auto;
  transform: none;
  pointer-events: none;
  /* The wheel is the whole size of a thumb cluster, floor as well as width. The
     desktop fold surface floors itself at the bar stack, and a floor left in
     place pads the surface out to the right of the wheel: the surface still
     reaches the safe edge, while Attack, the arc and Recall — anchored to the
     wheel inside it — rest that far short of the bezel. */
  width: var(--hud-skill-wheel-w);
  min-width: var(--hud-skill-wheel-w);
  height: var(--hud-skill-wheel-h);
  min-height: var(--hud-skill-wheel-h);
  /* The fold trigger is not drawn on a handheld, so the room the desktop
     surface reserves for it below the bars is room the wheel then overhangs. */
  padding: 0;
  margin: 0;
  z-index: 30;
}

/* The runtime portals an active placement bar out of its dock row, so this
   foreground layer belongs to that surface alone. It is an ordinary stacking
   value: no importance override and no raised dock siblings. */
[data-ui-profile="handheld"] .action-bars.is-placing {
  z-index: 130;
}

[data-ui-profile="handheld"] .action-bars__place-prompt {
  right: calc(100% + 8px);
  bottom: 0;
  width: min(220px, 54vw);
  max-width: calc(100dvw - var(--hud-safe-left) - var(--hud-safe-right) - 16px);
  font-size: max(11px, calc(12px * var(--ui-scale)));
}

[data-ui-profile="handheld"] .action-bars.hud-fold-surface.is-collapsed {
  width: var(--hud-skill-wheel-w);
  min-width: var(--hud-skill-wheel-w);
  height: var(--hud-skill-wheel-h);
  min-height: var(--hud-skill-wheel-h);
  pointer-events: none;
}

[data-ui-profile="handheld"] .action-bars .hud-fold-trigger,
[data-ui-profile="handheld"] .action-bars .hot-key {
  display: none;
}

[data-ui-profile="handheld"] .action-bars.hud-fold-surface.is-collapsed .hud-fold-content {
  display: flex;
  visibility: visible;
  opacity: 1;
  pointer-events: none;
  transform: none;
  clip-path: none;
}

[data-ui-profile="handheld"] .action-bars__content {
  position: relative;
  width: var(--hud-skill-wheel-w);
  height: var(--hud-skill-wheel-h);
}

[data-ui-profile="handheld"] .action-bars__stack {
  position: absolute;
  inset: 0;
}

/* All three bars stack in the same box; the rotated-to one is the only one drawn,
   and it outranks the desktop hidden-extra rule because on a handheld the extra-bar
   checkboxes are not on the glass to have said otherwise. */
[data-ui-profile="handheld"] .action-bar.is-arc {
  position: absolute;
  inset: 0;
  display: block;
  width: auto;
  height: auto;
  flex-wrap: nowrap;
  flex-direction: row;
}

[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot,
[data-ui-profile="handheld"] .action-bars__recall .hot-slot,
[data-ui-profile="handheld"] .action-bars__attack,
[data-ui-profile="handheld"] .action-bars__rotate,
[data-ui-profile="handheld"] .action-bars__cycle {
  position: absolute;
  box-sizing: border-box;
  border-radius: 50%;
  clip-path: none;
  overflow: hidden;
  pointer-events: auto;
}

[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot.filled .hot-icon {
  clip-path: none;
  border-radius: 50%;
}

[data-ui-profile="handheld"] .action-bars__attack {
  display: flex;
  right: 0;
  bottom: 0;
  width: var(--hud-skill-aa);
  height: var(--hud-skill-aa);
  z-index: 3;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(184, 203, 202, 0.5);
  background: rgba(7, 12, 14, 0.9);
  cursor: var(--cursor-action);
}

[data-ui-profile="handheld"] .action-bars__attack img,
[data-ui-profile="handheld"] .action-bars__rotate img,
[data-ui-profile="handheld"] .action-bars__cycle img {
  width: 70%;
  height: 70%;
  object-fit: contain;
  pointer-events: none;
}

/* The same four angles on both rings, so a slot and the one four places after it
   stand in the same spoke and the arc reads as two rows rather than eight
   scattered coins. */
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(1),
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(5) {
  --hud-arc-a: 0deg;
}

[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(2),
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(6) {
  --hud-arc-a: 30deg;
}

[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(3),
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(7) {
  --hud-arc-a: 60deg;
}

[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(4),
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(8) {
  --hud-arc-a: 90deg;
}

/* Far ring: skills 1-4, full size, the row the thumb sweeps without curling. */
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(-n+4) {
  right: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r) * sin(var(--hud-arc-a))
    - var(--hud-skill) / 2
  );
  bottom: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r) * cos(var(--hud-arc-a))
    - var(--hud-skill) / 2
  );
  width: var(--hud-skill);
  height: var(--hud-skill);
}

/* Near ring: skills 5-8, in the band Attack leaves free. */
[data-ui-profile="handheld"] .action-bar.is-arc .hot-slot:nth-child(n+5):nth-child(-n+8) {
  display: block;
  right: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r-inner) * sin(var(--hud-arc-a))
    - var(--hud-skill-inner) / 2
  );
  bottom: calc(
    var(--hud-skill-aa) / 2
    + var(--hud-arc-r-inner) * cos(var(--hud-arc-a))
    - var(--hud-skill-inner) / 2
  );
  width: var(--hud-skill-inner);
  height: var(--hud-skill-inner);
}

/* Recall stands in the left column rather than on a ring: both rings now reach
   the spot beside Attack it used to keep for itself. */
[data-ui-profile="handheld"] .action-bars__recall {
  position: absolute;
  right: auto;
  left: 0;
  bottom: calc(var(--hud-touch-min) + 8px);
  width: var(--hud-skill);
  height: var(--hud-skill);
  z-index: 2;
}

[data-ui-profile="handheld"] .action-bars__recall .hot-slot {
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The left column, bottom up: Cycle, Recall, then the bar the arc is showing. The
   rotate control is a readout as much as a button — the arc holds eight of the
   twenty-four slots a player has bound, and nothing else says which eight. */
[data-ui-profile="handheld"] .action-bars__rotate {
  display: flex;
  overflow: visible;
  right: auto;
  left: 0;
  bottom: calc(var(--hud-touch-min) + var(--hud-skill) + 16px);
  width: var(--hud-touch-min);
  height: var(--hud-touch-min);
  z-index: 2;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(184, 203, 202, 0.38);
  background: rgba(7, 12, 14, 0.82);
  cursor: var(--cursor-action);
}

/* The chip rides the corner rather than the middle so the skills glyph still
   reads as the control's subject. */
[data-ui-profile="handheld"] .action-bars__rotate-chip {
  position: absolute;
  right: -2px;
  bottom: -2px;
  min-width: 20px;
  padding: 1px 3px;
  border: 1px solid rgba(184, 203, 202, 0.5);
  border-radius: 999px;
  background: rgba(7, 12, 14, 0.95);
  font-size: 9px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  color: #dfe7ee;
  pointer-events: none;
}

[data-ui-profile="handheld"] .action-bars__cycle {
  display: flex;
  right: auto;
  left: 0;
  bottom: 0;
  width: var(--hud-touch-min);
  height: var(--hud-touch-min);
  z-index: 2;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(184, 203, 202, 0.38);
  background: rgba(7, 12, 14, 0.82);
  cursor: var(--cursor-action);
}

[data-ui-profile="handheld"] .action-bars__config {
  display: none;
}

[data-ui-profile="handheld"] .action-bar:not(.is-arc),
[data-ui-profile="handheld"] .action-bar:not(.is-arc) .hot-slot {
  display: none !important;
}

[data-ui-profile="handheld"] .fps-hud {
  display: none;
}

[data-ui-profile="handheld"] .virtual-stick {
  display: block;
  width: var(--hud-stick);
  height: var(--hud-stick);
  z-index: 30;
}

[data-ui-profile="desktop"] .virtual-stick {
  display: none;
}

[data-ui-profile="handheld"] .hud-dock-row--top {
  top: var(--hud-safe-top);
  left: var(--hud-safe-left);
  right: var(--hud-safe-right);
}

[data-ui-profile="handheld"] .hud-dock-row--bottom {
  left: var(--hud-safe-left);
  right: var(--hud-safe-right);
  bottom: var(--hud-safe-bottom);
}

[data-ui-profile="handheld"] .hud-dock--topLeft .hud-rail {
  flex-flow: column;
  align-items: flex-start;
  max-width: min(15rem, 32vw);
}

[data-ui-profile="handheld"] .hud-dock--topLeft [data-hud-widget="questTracker"].hud-railed:not(.hud-layout-detached) {
  width: 100%;
}

[data-ui-profile="handheld"] .hud-dock--bottomRight {
  padding-bottom: calc(var(--hud-skill-wheel-h) + 8px);
  align-items: flex-end;
}

[data-ui-profile="handheld"] .menu-buttons.is-collapsible {
  isolation: auto;
}

[data-ui-profile="handheld"] .menu-buttons.is-collapsed .menu-items {
  display: none;
  will-change: auto;
}

[data-ui-profile="handheld"] .hud-safe-area-probe {
  inset: auto;
  width: 1px;
  height: 1px;
}

[data-ui-profile="handheld"] .overlay.window:not(.is-open),
[data-ui-profile="handheld"] .npc-win:not(.is-open) {
  display: none !important;
}

[data-ui-profile="handheld"] .virtual-stick-knob {
  width: calc(var(--hud-stick) * 0.38);
  height: calc(var(--hud-stick) * 0.38);
  margin: calc(var(--hud-stick) * -0.19) 0 0 calc(var(--hud-stick) * -0.19);
}

[data-ui-profile="handheld"] .menu-btn {
  width: max(var(--hud-touch-min), calc(48px * var(--ui-scale, 1)));
  height: max(var(--hud-touch-min), calc(48px * var(--ui-scale, 1)));
}

[data-ui-profile="handheld"] [data-hud-widget="menu"]:not(.hud-layout-detached) {
  overflow: visible;
  z-index: 48;
}

[data-ui-profile="handheld"] [data-hud-widget="skillFlow"]:not(.hud-layout-ghost),
[data-ui-profile="handheld"] .screen-skill-flow:not(.hud-layout-ghost) {
  width: min(52vw, 280px);
  max-height: none;
  z-index: 32;
}

/* A key chord is not a control on a glass with no keyboard, so the self-cast
   hint is desktop's alone; the handheld press is the self medallion then the
   skill (doc **Handheld Play Presentation**). Dropping it is also what leaves
   the strip's one row wide enough for the gauge and the cancel cap together. */
[data-ui-profile="handheld"] .skill-flow-self { display: none; }

[data-ui-profile="handheld"] .skill-flow-icon-frame {
  width: 28px;
  height: 28px;
}

[data-ui-profile="handheld"] .window {
  position: fixed !important;
  top: 50% !important;
  left: 50% !important;
  right: auto !important;
  bottom: auto !important;
  transform: translate(-50%, -50%) !important;
  width: var(--hud-window-max) !important;
  max-width: var(--hud-window-max) !important;
  max-height: var(--hud-window-max-h) !important;
  box-sizing: border-box;
  overflow: auto;
  border-radius: 12px !important;
}
[data-ui-profile="handheld"] .win-head {
  min-height: var(--hud-touch-min);
}
[data-ui-profile="handheld"] .win-close {
  width: var(--hud-touch-min);
  height: var(--hud-touch-min);
}

/* The skills window keeps the frame budget every window gets, and the cast wheel
   sits behind it rather than the window shrinking to clear the wheel. On a phone
   in landscape the wheel is most of the glass, so reserving room for it leaves
   the window its own title bar and nothing else — and a window too short to show
   a skill cannot be dragged from at all. The window is pinned to the top inset so
   what it does keep is the part a thumb can reach. */
[data-ui-profile="handheld"] .window.win-skills {
  /* The build flows left to right on a handheld — chrome rail beside the
     catalog — so this one window spends the landscape width the shared cap
     leaves idle. Centered, its margins stay at least the safe inset; 780px is
     the wide-glass shared cap, so no glass draws this card narrower than its
     siblings. */
  --hud-window-max: min(780px, calc(100dvw - var(--hud-safe-left, 8px) - var(--hud-safe-right, 8px) - 16px));
  top: var(--hud-safe-top) !important;
  transform: translateX(-50%) !important;
  /* Keep a visible strip of the thumb cluster below the catalog. Placement
     lifts the whole cluster above the card, but the resting layout should still
     show the player where that second step will happen. */
  max-height: min(var(--hud-window-max-h), 74dvh) !important;
}
/* Every window body caps itself against the frame budget, except the skills
   list. That one is a flex column whose own sheet gives it a fixed height for
   the desktop cockpit, so a second cap here would fight it rather than replace
   it; the rule below releases the fixed height instead and lets the body take
   whatever the frame left. The skills window is excluded by name so that stays
   a decision rather than a specificity accident. */
[data-ui-profile="handheld"] .window:not(.win-skills) .win-body {
  max-height: calc(var(--hud-window-max-h) - 3.25rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* One height decision, made by the frame: the body takes the room it left and
   releases every cap it carries for the desktop cockpit — the base rule budgets
   against a hotbar and a 72px default top that a landscape phone does not have,
   which on a 320px glass leaves a list about a hundred pixels tall. */
[data-ui-profile="handheld"] .win-skills .win-body {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Collapsed rail folds shrink to the edge chip. Desktop keeps the empty slot
   so neighbours do not jump; a landscape phone cannot spend that empty strip. */
[data-ui-profile="handheld"] .hud-fold-surface.hud-fold-preserve-rail-slot.hud-railed:not(.hud-layout-detached).is-collapsed:not(.action-bars) {
  width: var(--hud-fold-shell-size) !important;
  min-width: var(--hud-fold-shell-size) !important;
  height: var(--hud-fold-shell-size) !important;
  min-height: var(--hud-fold-shell-size) !important;
  padding: var(--hud-fold-inset);
}

[data-ui-profile="handheld"] .chat-wrap.hud-railed:not(.hud-layout-detached) {
  width: min(52vw, 280px, calc(var(--hud-short, 100dvh) * 0.52));
  margin: 0;
  transform: translateY(calc(-1 * var(--ime-inset, 0px)));
  z-index: 30;
}

[data-ui-profile="handheld"] .chat-send {
  display: inline-flex;
  min-width: var(--hud-touch-min);
  min-height: var(--hud-touch-min);
}

/* The windup strip is one row on a handheld, the same as everywhere else: a
   landscape phone has about 340px of glass, and a cancel on a row of its own is
   a control taller than the gauge, under the gauge, for a quarter of the screen.
   Cancel is thumb-sized without spending that row — the square cap every window
   closes with, wearing the same close icon, named by its aria-label. The words
   and the `Esc` key cap are hidden rather than dropped, so the profile flipping
   under a strip that has already rendered cannot leave a caption inside a 44px
   square. */
[data-ui-profile="handheld"] .skill-flow-cancel {
  justify-content: center;
  width: var(--hud-touch-min);
  min-width: var(--hud-touch-min);
  height: var(--hud-touch-min);
  min-height: var(--hud-touch-min);
  padding: 0;
  border: 1px solid rgba(92, 104, 123, 0.58);
  border-radius: 8px;
  color: transparent;
  background-color: rgba(10, 14, 20, 0.64);
  background-image: url('/play/assets/hud-close-DhU3PbeL.webp');
  background-position: center;
  background-repeat: no-repeat;
  background-size: 22px 22px;
  font-size: 0;
  cursor: var(--cursor-action);
}
[data-ui-profile="handheld"] .skill-flow-cancel > * { display: none; }

[data-ui-profile="handheld"] .minimap-frame.hud-fold-surface {
  --hud-fold-expanded-width: min(calc(152px * var(--ui-scale, 1)), 22dvh, calc(var(--hud-short, 100dvh) * 0.22));
  --hud-fold-expanded-height: min(calc(152px * var(--ui-scale, 1)), 22dvh, calc(var(--hud-short, 100dvh) * 0.22));
}

/* The phone's radar is capped at a fifth of the short axis, so the caption takes
   the frame's own edge rather than an inset the trigger is already standing in. */
[data-ui-profile="handheld"] .minimap-expanded-readout {
  right: 4px;
  bottom: calc(var(--hud-fold-reserve-bottom) + 4px);
  left: 4px;
}

[data-ui-profile="handheld"] .minimap-recenter {
  display: none;
}

/* In a rail the dock owns the position; a widget the layout editor has moved
   carries its own fixed coordinate instead. */
[data-ui-profile="handheld"] .combat-telemetry-fold.hud-fold-surface.hud-railed:not(.hud-layout-detached) {
  position: relative;
  inset: auto;
  min-width: var(--hud-fold-shell-size, 44px);
  z-index: 20;
}

[data-ui-profile="handheld"] .combat-telemetry-fold.hud-fold-surface:not(.is-collapsed) {
  width: min(280px, calc(100vw - var(--hud-skill-wheel-w) - 48px));
}

[data-ui-profile="handheld"] .loot-prompt {
  min-height: var(--hud-touch-min);
  pointer-events: auto;
  cursor: var(--cursor-action);
}

[data-ui-profile="handheld"] .death-card-positioner {
  width: min(var(--hud-window-max), calc(100vw - 24px));
  max-height: var(--hud-window-max-h);
  overflow: hidden;
}

[data-ui-profile="handheld"] button.death-action {
  min-height: var(--hud-touch-min);
}

[data-ui-profile="handheld"] .npc-win {
  position: fixed !important;
  top: 50% !important;
  bottom: auto !important;
  left: 50% !important;
  right: auto !important;
  transform: translate(-50%, -50%) !important;
  width: min(28rem, calc(100vw - 72px));
  max-width: min(28rem, calc(100vw - 72px));
  max-height: min(46dvh, var(--hud-window-max-h));
  z-index: 45;
}

[data-ui-profile="handheld"] .npc-menu-item,
[data-ui-profile="handheld"] .npc-controls .ron-btn {
  min-height: var(--hud-touch-min);
}

[data-ui-profile="handheld"] .npc-speaker {
  flex-basis: 88px;
  min-height: 0;
}

[data-ui-profile="handheld"] .npc-speaker-art {
  width: 96px;
  min-height: 0;
  height: calc(100% + 24px);
  max-height: 120px;
}

[data-ui-profile="handheld"] .npc-win.npc-offer {
  max-height: min(70dvh, var(--hud-window-max-h));
}

[data-ui-profile="handheld"] .npc-text {
  height: calc(12px * 1.45 * 3);
  min-height: calc(12px * 1.45 * 2);
  max-height: 4.8rem;
  overflow-y: auto;
  font-size: 12px;
  line-height: 1.45;
}

/* Landscape phones are short, not narrow. Compact the stage; leave the world. */
[data-ui-profile="handheld"] .target-panel.unit-frame {
  width: min(32vw, 220px);
  min-width: 0;
  max-width: min(32vw, 220px);
  gap: 6px;
}

/* The centre dock is centred on its own content, so a frame that shrinks as it
   folds drags the dock — and the chip the player just pressed — sideways. The
   band keeps the frame's width and the chip keeps the portrait's corner; the
   empty rest of the band is stage, which is the one strip a phone can spare. */
[data-ui-profile="handheld"] .hud-dock--topCenter .hud-rail--centerPrimary {
  min-width: min(32vw, 220px);
  align-items: flex-start;
}

[data-ui-profile="handheld"] .unit-portrait {
  width: 36px;
  height: 36px;
}

[data-ui-profile="handheld"] .target-class {
  display: none;
}

[data-ui-profile="handheld"] .unit-frame .target-name {
  font-size: 12px;
}

[data-ui-profile="handheld"] .target-cast {
  --gauge-h: 18px;
  margin-top: 1px;
}

[data-ui-profile="handheld"] .target-cast-icon {
  width: 14px;
  height: 14px;
}

[data-ui-profile="handheld"] .target-cast-label {
  left: 18px;
  right: 36px;
  font-size: 9px;
  line-height: 11px;
}

[data-ui-profile="handheld"] .target-cast-countdown {
  font-size: 8px;
}

[data-ui-profile="handheld"] .target-cast-markers {
  left: 18px;
  gap: 2px;
}

[data-ui-profile="handheld"] .target-cast-marker {
  max-width: 52px;
  font-size: 8px;
  padding: 0 3px;
}

/* An 18px bar cannot carry the skill's name, its clock and a word chip at once,
   so on a phone the panel's cast bar keeps the two the player reads and lets the
   frame's own tier colour and danger ring say the rest. A nameplate has no frame
   to colour, which is why its single marker stays. */
[data-ui-profile="handheld"] .target-panel .target-cast-marker {
  display: none;
}

[data-ui-profile="handheld"] .target-status {
  max-height: 1.1rem;
  overflow: hidden;
  margin-top: 1px;
  gap: 2px;
}

[data-ui-profile="handheld"] .notice-feed {
  max-width: min(22vw, 10rem);
  max-height: 1.2rem;
  overflow: hidden;
  z-index: 5;
}

[data-ui-profile="handheld"] .notice-row {
  font-size: 11px;
  padding: 3px 8px 3px 10px;
}

[data-ui-profile="handheld"] [data-hud-widget="lostGear"] {
  max-width: min(12rem, 28vw);
  max-height: min(26dvh, 7.5rem);
  overflow: hidden;
}

/* The identity chips lie in a row on a phone. Stacked, three collapsed shells
   claim half the left edge and leave the quest tracker below them a single line;
   side by side they cost one shell's height and the tracker reads. A tablet is
   tall enough to stack them, so it still does. */
@media (max-height: 520px) {
  /* The row's slots are the collapsed medallions, and they are all it ever
     spends. A chip that opens grows out of its own slot over the stage instead
     of re-flowing the row: the control stays under the finger that pressed it,
     every other chip keeps its place, and the panel grows away from its own
     trigger, so closing it again is the same tap. */
  [data-ui-profile="handheld"] .hud-dock--topLeft .hud-rail--leftChips {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: var(--hud-fold-shell-size);
    grid-auto-rows: var(--hud-fold-shell-size) auto;
    justify-content: start;
    align-items: start;
    /* A shell wears counters on its corners. Stacked they overhang nothing; side
       by side they overhang the neighbour, so the row is given room for them. */
    gap: calc(var(--hud-gap) * 2);
    max-width: none;
  }

  [data-ui-profile="handheld"] .hud-dock--topLeft .hud-rail--leftChips > .hud-fold-surface:not(.is-collapsed) {
    z-index: 26;
  }

  /* The focused ally is a card rather than a medallion, so it takes the line
     under the strip instead of a slot in it. */
  [data-ui-profile="handheld"] .hud-dock--topLeft .hud-rail--leftChips > [data-hud-widget="partyFocus"] {
    grid-row: 2;
    grid-column: 1;
  }
}

[data-ui-profile="handheld"] .status-tray {
  max-width: min(28vw, 10rem);
}

/* Only while the stack floats free. Docked in a rail it is `position: relative`,
   where an offset moves the box but not the slot it was given — the alerts lift
   off the rail and leave an empty band the height of the thumb stick behind. */
[data-ui-profile="handheld"] .alert-stack.hud-layout-detached,
[data-ui-profile="handheld"] .alert-stack:not(.hud-railed) {
  bottom: calc(var(--hud-stick) + 16px);
}

[data-ui-profile="handheld"] .coach-banner {
  width: min(56vw, calc(100% - 10rem));
  top: calc(var(--hud-top-center-bottom, 80px) + var(--hud-gap, 8px));
}

[data-ui-profile="handheld"]:has(.npc-win.is-open) .coach-banner {
  display: none !important;
}

[data-ui-profile="handheld"] .coach-banner-card {
  grid-template-columns: 44px minmax(0, 1fr);
  min-height: 44px;
}

[data-ui-profile="handheld"] .coach-banner-text {
  font-size: 12px;
  padding: 6px 12px;
}

[data-ui-profile="handheld"] .death-overlay {
  overflow: hidden;
  padding: 8px;
}

[data-ui-profile="handheld"] .death-card {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(13rem, 0.85fr);
  grid-template-rows: auto minmax(0, 1fr);
  max-height: var(--hud-window-max-h);
}

[data-ui-profile="handheld"] .death-card__head {
  grid-column: 1 / -1;
  padding: 8px 12px 8px 16px;
}

[data-ui-profile="handheld"] .death-card__title {
  font-size: 18px;
}

[data-ui-profile="handheld"] .death-card__body {
  min-height: 0;
  overflow-y: auto;
}

[data-ui-profile="handheld"] .death-card__foot {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 0;
}

[data-ui-profile="handheld"] .death-card:not(:has(.death-card__body)) .death-card__foot {
  grid-column: 1 / -1;
}

[data-ui-profile="handheld"] .death-timer__value {
  font-size: 22px;
}

[data-ui-profile="handheld"] .menu-overlay {
  align-items: center;
  justify-content: center;
}

[data-ui-profile="handheld"] .menu-overlay__card {
  width: min(var(--hud-window-max), 100%);
  max-height: var(--hud-window-max-h);
  overflow-y: auto;
  padding: 16px 20px;
}

[data-ui-profile="handheld"] .menu-button {
  min-height: var(--hud-touch-min);
}

[data-ui-profile="handheld"] .screen-combat-overlays__incoming {
  top: calc(var(--hud-top-center-bottom, 80px) + var(--hud-gap, 8px)) !important;
  width: min(180px, 42vw);
  padding: 4px 10px;
}

[data-ui-profile="handheld"] .screen-combat-overlays__lockout {
  top: calc(var(--hud-top-center-bottom, 80px) + var(--hud-gap, 8px) + 4px) !important;
  width: min(180px, 42vw);
  padding: 3px 10px;
}

[data-ui-profile="handheld"] .screen-combat-overlays__lockout-label {
  font-size: 12px;
  letter-spacing: 0.08em;
}

/* Live occupants hide overlapping stage chrome only. Quest tracker and chat
   stay on the edges. Closed HUD windows are not occupants. Cancel lives in the
   skill cluster, so a windup does not hide the radar column. */
/* A live cast strip and the command chip share the space above the thumb cluster.
   The command chip remains available as the permanent route to player windows;
   only the two transient cards under the radar step back, leaving the radar column
   and the strip clear of each other. Cancel stays on the strip. A tablet holds all
   of it and nothing moves. */
@media (max-height: 520px) {
  /* A conversation plate is the surface the player is acting through, and on a
     phone it is as wide as the glass allows. The thumb cluster and the toast stack
     stand where it lands, and neither is reachable while a reply is waiting, so
     both step back until the plate closes. The stick stays: walking away is how a
     player leaves a conversation. With the cluster gone the plate takes the room
     it left, because a reply the player cannot see is a conversation they cannot
     finish. The quest column steps back for the same reason: the plate is as wide
     as the server authored it, so no width the column could take is guaranteed to
     clear it, and half a tracker under a plate reads worse than none. */
  [data-ui-profile="handheld"] .npc-win {
    top: calc(var(--hud-top-center-bottom, 76px) + var(--hud-gap)) !important;
    transform: translateX(-50%) !important;
    max-height: calc(
      100dvh - var(--hud-top-center-bottom, 76px) - var(--hud-safe-bottom) - var(--hud-gap) * 2
    );
  }
  [data-ui-profile="handheld"]:has(.npc-win.is-open) [data-hud-widget="actionBars"],
  [data-ui-profile="handheld"]:has(.npc-win.is-open) [data-hud-widget="questTracker"],
  [data-ui-profile="handheld"]:has(.npc-win.is-open) .screen-combat-overlays,
  [data-ui-profile="handheld"]:has(.npc-win.is-open) .alert-stack {
    display: none !important;
  }
}

@media (max-height: 520px) {
  [data-ui-profile="handheld"]:has(.screen-skill-flow:not([hidden])) [data-hud-widget="lostGear"],
  [data-ui-profile="handheld"]:has(.screen-skill-flow:not([hidden])) .notice-feed {
    display: none !important;
  }
}

[data-ui-profile="handheld"]:has([data-modal]) .coach-banner,
[data-ui-profile="handheld"]:has([data-modal]) [data-hud-widget="lostGear"],
[data-ui-profile="handheld"]:has([data-modal]) .notice-feed,
[data-ui-profile="handheld"]:has([data-modal]) .screen-skill-flow,
[data-ui-profile="handheld"]:has([data-modal]) [data-hud-widget="skillFlow"],
[data-ui-profile="handheld"]:has(.npc-win.is-open) [data-hud-widget="lostGear"],
[data-ui-profile="handheld"]:has(.npc-win.is-open) .notice-feed,
[data-ui-profile="handheld"]:has(.npc-win.is-open) .screen-skill-flow,
[data-ui-profile="handheld"]:has(.death-overlay:not([hidden])) .coach-banner,
[data-ui-profile="handheld"]:has(.death-overlay:not([hidden])) [data-hud-widget="lostGear"],
[data-ui-profile="handheld"]:has(.death-overlay:not([hidden])) .notice-feed,
[data-ui-profile="handheld"]:has(.death-overlay:not([hidden])) .screen-skill-flow,
[data-ui-profile="handheld"]:has(.death-overlay:not([hidden])) .screen-combat-overlays {
  display: none !important;
}

[data-ui-profile="handheld"] .shop-panel,
[data-ui-profile="handheld"] .cashshop-panel,
[data-ui-profile="handheld"] .mail-panel,
[data-ui-profile="handheld"] .itemsvc-panel,
[data-ui-profile="handheld"] .trade-panel {
  width: min(var(--hud-window-max), calc(100vw - 48px));
  max-width: min(var(--hud-window-max), calc(100vw - 48px));
  max-height: var(--hud-window-max-h);
}

[data-ui-profile="handheld"] .shop-body,
[data-ui-profile="handheld"] .trade-body,
[data-ui-profile="handheld"] .cashshop-content,
[data-ui-profile="handheld"] .itemsvc-content {
  max-height: calc(var(--hud-window-max-h) - 3.25rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

[data-ui-profile="handheld"] .shop-row-action,
[data-ui-profile="handheld"] .trade-actions .ron-btn,
[data-ui-profile="handheld"] .shop-tab {
  min-height: var(--hud-touch-min);
}

/* A thumb has to be able to end a route it no longer wants to follow, so the
   route strip's Stop control takes the same touch floor every other handheld
   control takes. */
[data-ui-profile="handheld"] .wayfind-bar-stop {
  min-height: var(--hud-touch-min);
}
/* Desktop glass. Handheld never declares backdrop-filter or a large filter:blur —
   iOS keeps a leftover frost layer across the whole compositor if those properties
   are present, including none and blur(0). Only the stamped desktop profile may
   declare them — the hover/pointer query alone is not enough on iPhone. */

@media (hover: hover), (pointer: fine) {
  html[data-ui-profile="desktop"] .menu-aurora {
    filter: blur(60px);
  }
  html[data-ui-profile="desktop"] .menu-aurora.menu-aurora--b {
    filter: blur(80px);
  }

  html[data-ui-profile="desktop"] .menu-overlay__scrim {
    backdrop-filter: blur(3px) saturate(0.82);
    -webkit-backdrop-filter: blur(3px) saturate(0.82);
  }
  html[data-ui-profile="desktop"] .account-methods {
    backdrop-filter: blur(7px);
    -webkit-backdrop-filter: blur(7px);
  }
  html[data-ui-profile="desktop"] .screen-modal {
    backdrop-filter: blur(3px) saturate(0.82);
    -webkit-backdrop-filter: blur(3px) saturate(0.82);
  }
  html[data-ui-profile="desktop"] .screen-help-overlay {
    backdrop-filter: blur(3px) saturate(0.84);
    -webkit-backdrop-filter: blur(3px) saturate(0.84);
  }
  html[data-ui-profile="desktop"] .menu-toast {
    backdrop-filter: blur(var(--blur));
    -webkit-backdrop-filter: blur(var(--blur));
  }
  html[data-ui-profile="desktop"] .menu-panel {
    backdrop-filter: blur(var(--blur)) saturate(120%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(120%);
  }
  html[data-ui-profile="desktop"] .login-panel {
    backdrop-filter: blur(4px) saturate(112%);
    -webkit-backdrop-filter: blur(4px) saturate(112%);
  }
  html[data-ui-profile="desktop"] .menu-buttons {
    backdrop-filter: blur(6px);
  }
  html[data-ui-profile="desktop"] .menu-buttons.is-collapsible .menu-items {
    backdrop-filter: blur(6px);
  }
  html[data-ui-profile="desktop"] .menu-buttons.is-collapsible.is-collapsed,
  html[data-ui-profile="desktop"] .chat-wrap.is-collapsed,
  html[data-ui-profile="desktop"] .hud-fold-surface.is-collapsed,
  html[data-ui-profile="desktop"] .status-panel,
  html[data-ui-profile="desktop"] .target-panel.unit-frame,
  html[data-ui-profile="desktop"] .cs-root .cs-hero {
    backdrop-filter: none;
  }
  html[data-ui-profile="desktop"] .unit-tooltip {
    backdrop-filter: blur(2px);
  }
  html[data-ui-profile="desktop"] .cs-root .cs-roster {
    backdrop-filter: blur(5px);
  }
  html[data-ui-profile="desktop"] .cs-hero__actions {
    backdrop-filter: blur(4px);
  }
  html[data-ui-profile="desktop"] .cs-root .cs-hero.is-empty,
  html[data-ui-profile="desktop"] .cs-root .create-panel,
  html[data-ui-profile="desktop"] .cs-root .delete-panel {
    backdrop-filter: blur(var(--blur)) saturate(115%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(115%);
  }
}
