* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #050308;
  font-family: "Segoe UI", system-ui, sans-serif;
  color: #f2e9ff;
}

#c {
  display: block;
  width: 100%;
  height: 100%;
}

#overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(180, 0, 255, 0.25), transparent 50%),
    radial-gradient(ellipse at 70% 80%, rgba(0, 200, 255, 0.18), transparent 45%),
    #07050c;
}

#overlay.hidden {
  display: none;
}

/* ── THE BOOT PANEL IS A LABEL, NOT A WALL ─────────────────────────────────
   The intro reel with the logo plays behind this and the owner could barely
   see it: a 480 px card at 88 % opacity, padded 2 rem, covered the middle of
   the frame — which is exactly where the logo sits. Shrunk to 360 px with
   tighter padding and a more transparent plate, so the film reads through and
   around it. The blur stays: it is what keeps the text legible over a moving
   picture without needing an opaque background. */
.panel {
  width: min(300px, 84vw);
  padding: 1rem 1.15rem;
  border: 1px solid rgba(255, 80, 220, 0.35);
  border-radius: 16px;
  background: rgba(12, 8, 20, 0.62);
  box-shadow:
    0 0 40px rgba(255, 0, 180, 0.15),
    inset 0 0 30px rgba(80, 0, 120, 0.2);
  backdrop-filter: blur(9px);
}

/* the keyed wordmark — see index.html. Its own glow is baked into the art, so
   no filter here; a drop-shadow on top of a neon sign reads as a smudge. */
.panel-logo {
  display: block;
  width: 76%;
  max-width: 220px;
  height: auto;
  margin: 0 auto 0.55rem;
}

.panel h1 {
  font-size: 1.9rem;
  letter-spacing: 0.16em;
  font-weight: 700;
  background: linear-gradient(90deg, #ff4fd8, #6cf, #ff9);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin-bottom: 0.25rem;
}

.tag {
  color: #b89cff;
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.panel p {
  line-height: 1.5;
  color: #d8cdf0;
  margin-bottom: 1rem;
  font-size: 0.95rem;
}

.panel ul {
  list-style: none;
  margin-bottom: 1.4rem;
  font-size: 0.9rem;
  color: #c4b8e0;
}

.panel li {
  margin: 0.35rem 0;
}

kbd {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  font-size: 0.8rem;
  color: #fff;
}

#start-btn {
  width: 100%;
  padding: 0.9rem 1rem;
  border: none;
  border-radius: 10px;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  cursor: pointer;
  color: #120816;
  background: linear-gradient(90deg, #ff4fd8, #6cf);
  box-shadow: 0 0 24px rgba(255, 79, 216, 0.4);
  transition: transform 0.12s ease, filter 0.12s ease;
}

#start-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.08);
}

/* the small print, folded away — see the comment in index.html */
.hint-fold {
  margin-top: 0.7rem;
  font-size: 0.72rem;
  color: #8a7aa8;
  text-align: center;
}
.hint-fold summary {
  cursor: pointer;
  list-style: none;
  letter-spacing: 0.08em;
  opacity: 0.75;
}
.hint-fold summary::-webkit-details-marker { display: none; }
.hint-fold summary::before { content: '▸ '; }
.hint-fold[open] summary::before { content: '▾ '; }
.hint-fold .hint { margin-top: 0.5rem !important; }

.hint {
  margin-top: 1rem !important;
  margin-bottom: 0 !important;
  font-size: 0.78rem !important;
  color: #8a7aa8 !important;
  text-align: center;
}

#hud.hidden {
  display: none;
}

/* THE HUD-CHIP CLICK BUG.
   ────────────────────────────────────────────────────────────────────────
   MEASURED with document.elementFromPoint at the centre of every chip, in
   every real input state (economy lane, chipprobe):

     title screen        chips zero-size      (#hud.hidden — correct)
     in the club         chips REACHABLE
     ESC / pause         chips BLOCKED by #overlay   <-- the bug
     panel open          chips REACHABLE

   The chips already carried `z-index: 30`, which is why this looked fixed and
   was not. `#hud` is `position: fixed`, and a fixed element ALWAYS creates a
   stacking context — so 30 only ordered the chips against their own siblings
   inside #hud. The whole subtree still painted at #hud's 10, and #overlay
   (z 20, a root-level sibling) covered every one of them the moment ESC
   brought the menu back.

   THE Z-INDEX BELOW STAYS 10. Lifting #hud to 21 permanently also fixes it,
   and was the first version of this patch, but it silently re-orders #hud
   against every other lane's panel — #np-elev-panel sits at z-index 12 on
   document.body, and would have started painting UNDER the HUD chips without
   the elevator lane ever touching a line. The lift is therefore scoped to the
   only state that needs it, using the same sibling combinator as the rule
   below: during play nothing about #hud's stacking changes at all.

   NOT fixed here, because it is not a layering bug: while pointer lock is
   ENGAGED the browser routes every pointer event to the canvas and there is no
   cursor at all. No z-index reaches that. Press ESC first — which now works.
   Touch is unaffected: no pointer lock, and #overlay is hidden once the player
   has entered, so the chips were already tappable and still are. */
#hud {
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}

/* #overlay precedes #hud in the DOM, so the general sibling combinator reads
   "the pause menu is up" with no JavaScript and no shared-code edit.
   Only then does #hud rise over #overlay (21 > 20)… */
#overlay:not(.hidden) ~ #hud {
  z-index: 21;
}

/* …and only the BUTTONS stay visible up there. The crosshair, minimap, zone
   and floor readouts, and every lane's E-prompt would otherwise draw across
   the pause panel. `visibility`, not `display`: the minimap canvas must keep
   its layout box or its 2D draw maths reads clientWidth 0, and the QA tools
   still read #fps / #zone text content. */
#overlay:not(.hidden) ~ #hud > :not(button) {
  visibility: hidden;
}

#zone {
  position: absolute;
  top: 18px;
  left: 20px;
  font-size: 0.95rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #ff8ae8;
  text-shadow: 0 0 12px rgba(255, 80, 200, 0.8);
}

#floor-label {
  position: absolute;
  top: 42px;
  left: 20px;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  color: #8fd8ff;
}

#bright-label {
  position: absolute;
  top: 64px;
  left: 20px;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: #ffe08a;
  text-shadow: 0 0 10px rgba(255, 200, 80, 0.6);
}

/* ΠΑΝΟΡΑΜΙΚΗ — the first-person field of view, one line under BRIGHT because
   it is the same kind of control: a live look setting the player owns. */
#fov-label {
  position: absolute;
  top: 84px;
  left: 20px;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: #8fffd8;
  text-shadow: 0 0 10px rgba(80, 255, 210, 0.5);
}

#fps {
  position: absolute;
  top: 18px;
  right: 20px;
  font-size: 0.8rem;
  color: #7fefb0;
  font-variant-numeric: tabular-nums;
}

#crosshair {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 22px;
  color: rgba(255, 255, 255, 0.75);
  text-shadow: 0 0 8px rgba(255, 100, 220, 0.6);
  line-height: 1;
}

#minimap-wrap {
  position: absolute;
  bottom: 20px;
  right: 20px;
  width: 140px;
  height: 140px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255, 100, 220, 0.35);
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
  background: rgba(8, 4, 16, 0.75);
}

#minimap {
  width: 100%;
  height: 100%;
  display: block;
}

#hint-bar {
  position: absolute;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.78rem;
  color: rgba(220, 200, 255, 0.65);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* mute button — audio-director wires behavior: toggles .muted + swaps 🔊/🔇,
   M key mirrors it. Sits above the minimap, same HUD language as FLOOR/BRIGHT. */
.hud-mute {
  position: absolute;
  right: 20px;
  bottom: 172px;
  pointer-events: auto;
  z-index: 30;
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 10px;
  background: rgba(8, 4, 16, 0.75);
  border: 1px solid rgba(255, 100, 220, 0.35);
  color: #f2e9ff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}

.hud-mute:hover {
  border-color: #2ef0aa;
}

.hud-mute.muted {
  color: #8a7aa8;
  border-color: rgba(138, 122, 168, 0.4);
  box-shadow: none;
}

/* interaction prompt (sit / stairs / elevator) — gameplay-engineer toggles
   .hidden and sets #interact-label text; position clears crosshair + hint-bar */
#interact-prompt {
  position: absolute;
  left: 50%;
  bottom: 96px;
  transform: translateX(-50%);
  padding: 6px 14px;
  border-radius: 8px;
  background: rgba(12, 8, 20, 0.82);
  border: 1px solid rgba(255, 100, 220, 0.4);
  color: #f2e9ff;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  box-shadow: 0 0 18px rgba(255, 0, 180, 0.18);
}

#interact-prompt.hidden {
  display: none;
}

.editor-toggle {
  display: block;
  margin: 10px auto 0;
  padding: 8px 18px;
  background: #1a1226;
  color: #cfe8dd;
  border: 1px solid #4a4060;
  border-radius: 6px;
  font: inherit;
  letter-spacing: 0.06em;
  cursor: pointer;
}
.editor-toggle:hover { border-color: #2ef0aa; }

.hud-edit {
  position: absolute;
  right: 14px;
  bottom: 14px;
  pointer-events: auto;
  z-index: 30;
  font-size: 13px;
  padding: 6px 12px;
}

/* ── chunk-transition load screen (sub-level loader) ─────────────────────
   BARAKI.club branding: the club's official gold/bronze arched sign on the
   dark ground, gold accents instead of the game's pink/cyan. z-index above
   HUD (10) and title overlay (20): a transition must cover everything. */
#chunkload {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  background:
    radial-gradient(ellipse at 50% 18%, rgba(212, 160, 60, 0.14), transparent 55%),
    radial-gradient(ellipse at 50% 100%, rgba(120, 70, 20, 0.10), transparent 50%),
    #08060a;
}

#chunkload.hidden {
  display: none;
}

.cl-panel {
  width: min(420px, 90vw);
  text-align: center;
}

.cl-sign {
  width: min(300px, 70vw);
  aspect-ratio: 776 / 552;      /* source PNG ratio — never squash the sign */
  object-fit: contain;
  margin-bottom: 0.6rem;
  filter: drop-shadow(0 0 24px rgba(212, 160, 60, 0.35));
}

.cl-title {
  font-size: 1.7rem;
  letter-spacing: 0.24em;
  font-weight: 700;
  color: #e8c06a;
  text-shadow: 0 0 18px rgba(212, 160, 60, 0.55);
  margin-bottom: 0.4rem;
}

.cl-tld {
  color: #a8894e;
  letter-spacing: 0.12em;
  font-weight: 400;
}

.cl-zone {
  font-size: 0.9rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #c9a86a;
  margin-bottom: 1.4rem;
}

.cl-bar {
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(212, 160, 60, 0.3);
  overflow: hidden;
}

.cl-fill {
  height: 100%;
  width: 0%;
  border-radius: 3px;
  background: linear-gradient(90deg, #b8873a, #f0d08a);
  box-shadow: 0 0 16px rgba(232, 192, 106, 0.55);
  transition: width 0.15s ease;
}

.cl-tip {
  margin-top: 1rem;
  font-size: 0.75rem;
  color: #94804f;
  letter-spacing: 0.08em;
}

/* ─── ΚΟΠΕΛΕΣ · CHROMA KEY PANEL ─────────────────────────────────────────────
   Same visual language as the P editor: dark violet glass, mint accent, Greek
   hints. It sits over the canvas rather than inside the menu because the whole
   point is watching the sprite change while you drag, so `.kp-mini` collapses
   it to a corner strip once you have walked over to her. */
.hud-keys {
  position: absolute;
  right: 14px;
  bottom: 52px;
  pointer-events: auto;
  z-index: 30;
  font-size: 13px;
  padding: 6px 12px;
}

#keypanel {
  position: fixed;
  inset: auto 0 0 0;
  max-height: 62vh;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: rgba(10, 6, 20, 0.94);
  border-top: 1px solid rgba(46, 240, 170, 0.35);
  box-shadow: 0 -14px 44px rgba(0, 0, 0, 0.7);
  color: #cfe8dd;
  font: 13px/1.45 inherit;
  backdrop-filter: blur(6px);
}
#keypanel.hidden { display: none; }

/* walked over to her: fold down to a thin column on the right so the club —
   and the sprite you are actually judging — stays visible */
#keypanel.kp-mini {
  inset: auto 0 0 auto;
  width: 340px;
  max-height: 76vh;
  border-left: 1px solid rgba(46, 240, 170, 0.35);
}
#keypanel.kp-mini .kp-grid { display: none; }
#keypanel.kp-mini .kp-body { grid-template-columns: 1fr; }

.kp-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  flex: 0 0 auto;
}
.kp-title {
  letter-spacing: 0.12em;
  color: #7fefb0;
  font-size: 12px;
  white-space: nowrap;   /* mini mode is only 340 px — wrapping shoves the ✕ */
}
.kp-toast {
  font-size: 11px;
  letter-spacing: 0.06em;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.kp-toast.hidden { visibility: hidden; }
.kp-x {
  margin-left: auto;
  background: none;
  border: 1px solid #4a4060;
  color: #cfe8dd;
  border-radius: 6px;
  width: 26px; height: 24px;
  cursor: pointer;
  font: inherit;
}
.kp-x:hover { border-color: #2ef0aa; }

.kp-body {
  display: grid;
  grid-template-columns: 1fr 340px;
  gap: 0;
  overflow: hidden;
  flex: 1 1 auto;
}

.kp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 8px;
  padding: 12px 14px;
  overflow-y: auto;
  /* without these the two rows stretch to the panel height and each 107 px
     thumbnail sits in a 239 px card, which reads as a broken image grid */
  align-content: start;
  grid-auto-rows: min-content;
}
.kp-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 5px 4px 4px;
  background: #150e22;
  border: 1px solid #332a47;
  border-radius: 8px;
  color: #a99fc0;
  font: 10px/1 inherit;
  letter-spacing: 0.04em;
  cursor: pointer;
}
.kp-card img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 5px;
  background: #0b0714;
}
.kp-card:hover { border-color: #2ef0aa; color: #cfe8dd; }
.kp-card.sel {
  border-color: #2ef0aa;
  background: #173a2a;
  color: #eafff4;
  box-shadow: 0 0 14px rgba(46, 240, 170, 0.25);
}
/* the dot marks a performer whose matte is tuned and stored in club.db, so at
   a glance you can see which of the cast is still on her shipped default */
.kp-dot {
  position: absolute;
  top: 8px; right: 8px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid #4a4060;
}
.kp-dot.on { background: #2ef0aa; border-color: #2ef0aa; }

.kp-edit {
  padding: 12px 16px 16px;
  border-left: 1px solid rgba(255, 255, 255, 0.08);
  overflow-y: auto;
}
.kp-empty { color: #8d84a6; font-size: 12px; line-height: 1.7; }
.kp-name {
  color: #7fefb0;
  letter-spacing: 0.1em;
  margin-bottom: 12px;
  font-size: 12px;
}
.kp-row { margin-bottom: 11px; }
.kp-row label {
  display: block;
  font-size: 11px;
  color: #cfe8dd;
  letter-spacing: 0.03em;
}
.kp-row label em {
  display: block;
  font-style: normal;
  font-size: 10px;
  color: #7d7396;
  margin-top: 1px;
}
.kp-row input[type='range'] {
  width: calc(100% - 58px);
  vertical-align: middle;
  accent-color: #2ef0aa;
  margin-top: 4px;
}
.kp-row output {
  display: inline-block;
  width: 52px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: #7fefb0;
  font-size: 11px;
}
.kp-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.kp-acts button {
  flex: 1 1 auto;
  padding: 7px 10px;
  background: #1a1226;
  color: #cfe8dd;
  border: 1px solid #4a4060;
  border-radius: 6px;
  font: 11px inherit;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.kp-acts button:hover { border-color: #2ef0aa; }
.kp-acts .kp-warn:hover { border-color: #ff7a8a; color: #ff7a8a; }
.kp-acts button.on { border-color: #ff5fb0; color: #ff9ed2; background: #2a132a; }

/* ΚΑΔΡΟ ΣΥΝΟΜΙΛΙΑΣ — the per-girl conversation framing, fenced off from the
   matte sliders above it because it tunes the CAMERA, not the key. */
.kp-cam {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid #3a3050;
}
.kp-sub {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: #ffb0dd;
  margin-bottom: 10px;
}
.kp-sub em {
  display: block;
  font-style: normal;
  font-size: 10px;
  letter-spacing: 0;
  color: #8c86a0;
  margin-top: 2px;
}

/* ── girl chat panel (#np-chat) — click a performer, type, she answers ──
   Same HUD language as the minimap/interact-prompt: dark glass, pink neon
   border. Bottom-left so the minimap (bottom-right) and hint-bar stay clear.
   z-index 30: over the HUD (10), under the chunk loader (40). */
#np-chat {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 30;
  width: min(360px, 92vw);
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  border: 1px solid rgba(255, 100, 220, 0.45);
  background: rgba(12, 8, 20, 0.92);
  box-shadow: 0 0 28px rgba(255, 0, 180, 0.22), inset 0 0 24px rgba(80, 0, 120, 0.18);
  backdrop-filter: blur(8px);
  pointer-events: auto;
}

#np-chat.hidden { display: none; }

.npc-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255, 100, 220, 0.25);
}

.npc-id { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; }

.npc-name {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  background: linear-gradient(90deg, #ff4fd8, #6cf);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.npc-vibe {
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #b89cff;
}

.npc-head button {
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #d8cdf0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.npc-head button:hover { border-color: #2ef0aa; }
.npc-head .npc-clear:hover { border-color: #ff7a8a; color: #ff7a8a; }

.npc-msgs {
  max-height: 34vh;
  min-height: 120px;
  overflow-y: auto;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  scrollbar-width: thin;
}

.npc-msg {
  max-width: 85%;
  padding: 7px 11px;
  border-radius: 12px;
  font-size: 0.88rem;
  line-height: 1.35;
  word-wrap: break-word;
  white-space: pre-wrap;
}

.npc-her {
  align-self: flex-start;
  background: rgba(120, 0, 160, 0.28);
  border: 1px solid rgba(255, 100, 220, 0.35);
  border-bottom-left-radius: 4px;
  color: #f6e8ff;
}

.npc-me {
  align-self: flex-end;
  background: rgba(0, 140, 200, 0.18);
  border: 1px solid rgba(102, 204, 255, 0.35);
  border-bottom-right-radius: 4px;
  color: #e2f4ff;
}

.npc-typing { opacity: 0.65; font-style: italic; }
.npc-greet { opacity: 0.9; }
.npc-err { border-color: rgba(255, 122, 138, 0.5); }

.npc-inputrow {
  display: flex;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid rgba(255, 100, 220, 0.25);
}

.npc-input {
  flex: 1;
  min-width: 0;
  padding: 8px 12px;
  border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  color: #f2e9ff;
  font: inherit;
  font-size: 0.9rem;
  outline: none;
}
.npc-input:focus { border-color: rgba(255, 100, 220, 0.6); box-shadow: 0 0 12px rgba(255, 79, 216, 0.25); }
.npc-input::placeholder { color: #8a7aa8; }

.npc-send {
  width: 40px;
  border: none;
  border-radius: 9px;
  font-size: 15px;
  cursor: pointer;
  color: #120816;
  background: linear-gradient(90deg, #ff4fd8, #6cf);
  box-shadow: 0 0 14px rgba(255, 79, 216, 0.35);
}
.npc-send:hover { filter: brightness(1.1); }

.npc-hint {
  padding: 0 12px 8px;
  font-size: 0.66rem;
  letter-spacing: 0.05em;
  color: #8a7aa8;
  text-align: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   DANCE BATTLE — the pista rhythm mini-game (gameplay-engineer)

   Same HUD language as #np-chat and #interact-prompt: dark glass, pink/cyan
   neon, 14 px radius, z-index 30 (over the HUD at 10, under the chunk loader
   at 40). Everything below is scoped under #np-dance / .npd-* — nothing above
   this line is touched.

   The scrolling arrows are pooled DOM nodes moved with transform only (no
   layout, no Three.js meshes, no extra draw calls), so the 60 FPS budget is
   unaffected by how busy the chart is.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── THE PANEL IS NOT A PANEL AND IS NOT A STRIP EITHER ────────────────────
   Version 1 was a 430x660 dark rectangle in the MIDDLE OF THE SCREEN, which is
   why the game read as a chart bolted onto a club: the eyes were on a box and
   the pista was wallpaper behind it. Version 2 collapsed it to a 168 px
   bottom-edge ribbon. The owner's verdict on that one, looking at his own
   screenshot: «βγάλε το embed και βάλ' το να φαίνεται σε όλη την πίστα και τα
   σχήματα να έρχονται από πάνω».

   So there is no container left. #np-dance is the WHOLE VIEWPORT, transparent,
   and it does not intercept the pointer — the club is behind it and stays
   visible, the four lanes span the full width, and the arrows fall the full
   height onto a hit line that sits over the dance floor itself. The only
   sub-elements that take pointer events are the ones you can click.

   Nothing about the JUDGE changed. The hit windows are still drawn to scale
   from pixels-per-second (danceDrawBands), and pps is derived from the field's
   measured height — so a full-screen field re-scales the bands automatically
   and the timing is byte-identical to the strip's. */
#np-dance {
  position: fixed;
  inset: 0;
  transform: none;
  z-index: 30;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
  pointer-events: none;
  user-select: none;
}

#np-dance.hidden { display: none; }

/* The head and the stats float over the top of the room now, centred and
   narrow — they are a readout, not a frame. pointer-events come back on only
   for the head, because the ✖ is the one thing here you can click. */
.npd-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px 2px;
  border-bottom: none;
  flex: 0 0 auto;
  width: min(560px, 96vw);
  margin: 0 auto;
  pointer-events: auto;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85);
}

.npd-title {
  flex: 1;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: #ff8fe4;
  text-shadow: 0 0 10px rgba(255, 79, 216, 0.5);
}

.npd-bpm {
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  color: #6cf;
  opacity: 0.85;
}

.npd-close {
  width: 26px;
  height: 24px;
  padding: 0;
  border-radius: 7px;
  cursor: pointer;
  color: #f2e9ff;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 100, 220, 0.3);
  font-size: 12px;
  line-height: 1;
}
.npd-close:hover { border-color: #ff7a8a; color: #ff7a8a; }

.npd-stats {
  display: flex;
  gap: 6px;
  padding: 2px 18px 5px;
  flex: 0 0 auto;
  width: min(560px, 96vw);
  margin: 0 auto;
}

.npd-stat {
  flex: 1;
  text-align: center;
  padding: 3px 2px;
  border-radius: 9px;
  background: rgba(10, 6, 18, 0.55);
  border: 1px solid rgba(255, 100, 220, 0.16);
  backdrop-filter: blur(3px);
}
.npd-stat b {
  display: block;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #f2e9ff;
  font-variant-numeric: tabular-nums;
}
.npd-stat i {
  display: block;
  font-style: normal;
  font-size: 0.56rem;
  letter-spacing: 0.14em;
  color: #8a7aa8;
}
.npd-stat.hot b { color: #2ef0ff; text-shadow: 0 0 10px rgba(46, 240, 255, 0.55); }

/* ── THE COMBO COUNTER GROWS AND GLOWS ─────────────────────────────────────
   Four tiers at 5 / 15 / 30 / 45 unbroken hits. The tier is a CLASS, written
   only when the tier changes (a handful of times a round), and the per-hit
   `bump` is a keyframe restart on the number itself. The tile has a fixed
   height and the number scales with `transform`, so the counter growing can
   never reflow the stats row or the field under it. */
.npd-stat b { transform-origin: 50% 60%; will-change: transform; }
.npd-stat.t1 b { color: #9ff7ff; text-shadow: 0 0 12px rgba(46, 240, 255, 0.5); transform: scale(1.10); }
.npd-stat.t2 b { color: #2ef0ff; text-shadow: 0 0 18px rgba(46, 240, 255, 0.7); transform: scale(1.22); }
.npd-stat.t3 b { color: #ff8fe4; text-shadow: 0 0 22px rgba(255, 79, 216, 0.8); transform: scale(1.34); }
.npd-stat.t4 b {
  color: #ffd76e;
  text-shadow: 0 0 12px rgba(255, 215, 110, 0.9), 0 0 30px rgba(255, 140, 40, 0.55);
  transform: scale(1.46);
}
.npd-stat.t1, .npd-stat.t2 { border-color: rgba(46, 240, 255, 0.4); }
.npd-stat.t3 { border-color: rgba(255, 79, 216, 0.5); }
.npd-stat.t4 { border-color: rgba(255, 200, 90, 0.6); background: rgba(255, 180, 60, 0.09); }
/* the one-per-round combo shield, shown as a badge on the COMBO tile so the
   player knows he has a slip in hand — and knows the moment he has spent it */
.npd-stat.shielded { position: relative; }
.npd-stat.shielded::after {
  content: '⛨';
  position: absolute;
  top: 1px; right: 4px;
  font-size: 0.62rem;
  color: #7ef6be;
  text-shadow: 0 0 8px rgba(126, 246, 190, 0.7);
  pointer-events: none;
}
.npd-combo.bump { animation: npdBump 0.18s ease-out; }
@keyframes npdBump {
  0%   { filter: brightness(2.2); }
  100% { filter: brightness(1); }
}

/* ── the lane ribbon ──────────────────────────────────────────────────────
   THE FLOOR IS THE BOARD; this is the vernier beside it. A four-row tile
   runway steps once every 533 ms and cannot resolve a ±110 ms PERFECT window,
   so the precise read stays here — 168 px tall instead of the old ~490, mostly
   transparent, hugging the bottom edge. Take this away and the game is not
   playable at full difficulty; leave it big and he never looks at the pista. */
/* THE FIELD IS THE SCREEN. Four lanes across the full width, arrows falling
   the full height onto a hit line that lands over the dance floor. No box, no
   border, no plate: the only wash is a faint darkening toward the hit line so
   the glyphs stay readable against a magenta room, and it fades to nothing at
   the top so the ceiling, the trusses and the stage are never veiled. */
.npd-field {
  position: relative;
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
  margin: 0;
  border-radius: 0;
  overflow: hidden;
  background: linear-gradient(180deg,
    rgba(6, 4, 11, 0) 0%, rgba(6, 4, 11, 0) 42%,
    rgba(255, 140, 40, 0.05) 72%, rgba(6, 4, 11, 0.34) 100%);
  border: none;
}

/* four vertical lane guides — pure decoration, no layout cost */
.npd-lane {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 25%;
  border-left: 1px solid rgba(255, 100, 220, 0.08);
  box-sizing: border-box;
}
.npd-lane:first-child { border-left: none; }

/* ── THE HIT WINDOW, DRAWN TO SCALE ────────────────────────────────────────
   Two strips centred on the hit line. Their height is set from JS as
   window_seconds x (px per second the arrows travel), so what the player sees
   IS the judge — and when the ramp eases out over the first 20 s the strips
   visibly shrink. `top` and `height` are the only properties JS writes, and it
   only writes them when the scale has moved 1 %. */
/* The alphas were halved when the field went full-screen. They are drawn to
   SCALE, so a band that was 31 px tall on a 96 px ribbon is ~190 px tall on a
   609 px field — the same fraction, eight times the area, and at the old alpha
   it read as fog laid across the middle of the room. Half the alpha over twice
   the height is the same amount of ink on screen. */
.npd-band {
  position: absolute;
  left: 0;
  right: 0;
  background: linear-gradient(180deg,
    rgba(46, 240, 255, 0.015) 0%, rgba(46, 240, 255, 0.05) 50%, rgba(46, 240, 255, 0.015) 100%);
  border-top: 1px dashed rgba(46, 240, 255, 0.22);
  border-bottom: 1px dashed rgba(46, 240, 255, 0.22);
  pointer-events: none;
}
/* the beat strobe rides on the arrow LAYER, not on 30 separate tiles */
.npd-arrows { will-change: filter; }

/* ── ΧΡΥΣΟ ΤΟΥΒΛΑΚΙ ────────────────────────────────────────────────────────
   Double points (both sides of the normalisation — see danceResolve). Gold is
   the one hue the four lanes do NOT use, so it never collides with the ← ↓ ↑ →
   colour code; the glyph keeps its own shape and stays readable. The halo
   animation is a pure compositor property (box-shadow + border-color) on at
   most a couple of tiles at a time, and the sway/pump come from JS so they stay
   locked to the beat instead of drifting on a fixed keyframe period. */
.npd-arrow.bonus {
  background: linear-gradient(150deg, #ffd76e, #ff9f1a) !important;
  color: #2a1a00;
  border: 2px solid #fff0b8;
  animation: npdBonus 0.5s ease-in-out infinite alternate;
}
@keyframes npdBonus {
  from { box-shadow: 0 0 14px rgba(255, 200, 70, 0.55); border-color: rgba(255, 240, 184, 0.55); }
  to   { box-shadow: 0 0 34px rgba(255, 215, 110, 0.95); border-color: #fff6d4; }
}
.npd-band-in {
  position: absolute;
  left: 0;
  right: 0;
  background: linear-gradient(180deg,
    rgba(255, 79, 216, 0.03) 0%, rgba(255, 79, 216, 0.10) 50%, rgba(255, 79, 216, 0.03) 100%);
  border-top: 1px solid rgba(255, 140, 232, 0.35);
  border-bottom: 1px solid rgba(255, 140, 232, 0.35);
  pointer-events: none;
}

/* the hit line + its four target rings */
.npd-hit {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, rgba(46, 240, 255, 0), #2ef0ff, rgba(46, 240, 255, 0));
  box-shadow: 0 0 14px rgba(46, 240, 255, 0.6);
  opacity: 0.8;
}

.npd-target {
  position: absolute;
  width: 60px;
  height: 60px;
  margin-left: -30px;
  margin-top: -30px;
  border-radius: 50%;
  border: 2px solid rgba(255, 170, 80, 0.5);
  color: rgba(255, 220, 180, 0.8);
  font-size: 30px;
  line-height: 56px;
  text-align: center;
  background: rgba(46, 240, 255, 0.05);
}
.npd-target.lit {
  border-color: #2ef0ff;
  color: #eaffff;
  background: rgba(46, 240, 255, 0.2);
  box-shadow: 0 0 18px rgba(46, 240, 255, 0.55);
}
/* the target ROW flashes in the colour of the judgement that just landed —
   this is the "did I hit it?" answer, at the place the player is looking */
.npd-target.hit-perfect {
  border-color: #7df6ff; background: rgba(46, 240, 255, 0.34);
  box-shadow: 0 0 26px rgba(46, 240, 255, 0.75);
}
.npd-target.hit-great {
  border-color: #ffa6ec; background: rgba(255, 79, 216, 0.28);
  box-shadow: 0 0 24px rgba(255, 79, 216, 0.6);
}
.npd-target.hit-good {
  border-color: #b9a6ff; background: rgba(150, 120, 255, 0.22);
  box-shadow: 0 0 20px rgba(150, 120, 255, 0.5);
}

/* ── THE ARROW POP ─────────────────────────────────────────────────────────
   Four nodes, one parked on each lane's target for the whole round. A hit
   restarts the keyframe; nothing is created, nothing is destroyed, and the
   ring scales out and fades in 260 ms. `will-change: transform` keeps it on
   its own layer so the pop never repaints the field behind it. */
.npd-burst {
  position: absolute;
  width: 34px;
  height: 34px;
  margin-left: -17px;
  margin-top: -17px;
  border-radius: 50%;
  border: 2px solid #2ef0ff;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
.npd-burst.go { animation: npdBurst 0.26s ease-out; }
.npd-burst.great { border-color: #ff8fe4; }
.npd-burst.good { border-color: #b9a6ff; }
@keyframes npdBurst {
  0%   { opacity: 0.95; transform: scale(0.72); }
  100% { opacity: 0;    transform: scale(2.0); }
}

/* pooled scrolling arrows — moved with transform only */
/* 34 -> 46 -> 60 px. On a 96 px ribbon a 34 px tile filled a third of the lane;
   on a full screen it is a pebble. 46 was the first pass and the owner's call on
   the frame was «μεγάλωσέ τα κι άλλο 30%» — 46 x 1.3 = 60. The margins stay
   half the size so the tile is still centred on its lane by margin and NOT by
   transform: the transform is spent on the fall and the pump. */
.npd-arrow {
  position: absolute;
  left: 0;
  top: 0;
  width: 60px;
  height: 60px;
  margin-left: -30px;
  margin-top: -30px;
  border-radius: 14px;
  font-size: 32px;
  line-height: 57px;
  text-align: center;
  color: #120816;
  background: linear-gradient(150deg, #ff3b2e, #d8241e);
  box-shadow: 0 0 12px rgba(255, 90, 40, 0.45);
  will-change: transform;
}
.npd-arrow.l1 { background: linear-gradient(150deg, #ff8a1e, #ff5e1e); }
.npd-arrow.l2 { background: linear-gradient(150deg, #ffd12e, #ffa01e); }
.npd-arrow.l3 { background: linear-gradient(150deg, #ff5ea0, #ff2e7a); }
.npd-arrow.off { display: none; }

/* ── floating over the club, ABOVE the ribbon ─────────────────────────────
   These four were children of .npd-field and were clipped by its
   overflow:hidden the moment the field shrank to 168 px. They are children of
   #np-dance now and anchor to `bottom: 100%` — i.e. they stack upward from the
   top edge of the strip, over the room, right where the pista is. */
/* ── THE FLOATERS, RE-ANCHORED ─────────────────────────────────────────────
   These four were pinned with `bottom: calc(100% + Npx)` — i.e. stacked UPWARD
   off the top edge of a 168 px strip parked at the bottom of the screen. The
   panel is the whole viewport now, so `100%` is the viewport and every one of
   them would have been thrown clean off the top and never seen again. They are
   anchored to the middle of the screen instead, above the hit line (which sits
   at 80 % of the field), where the eye already is. */
.npd-judge {
  position: absolute;
  left: 0;
  right: 0;
  bottom: auto;
  top: 38%;
  text-align: center;
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  opacity: 0;
  pointer-events: none;
  /* the callout now floats over a floor that can be BRIGHT RED at full streak,
     so every variant below adds its glow ON TOP of this hard dark rim — a
     coloured text-shadow alone disappears against a hot pista */
  -webkit-text-stroke: 0.5px rgba(0, 0, 0, 0.55);
  paint-order: stroke fill;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.95));
}
.npd-judge.show { animation: npdPop 0.42s ease-out; }
@keyframes npdPop {
  0%   { opacity: 0; transform: scale(0.7); }
  22%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 0; transform: scale(1); }
}
.npd-judge.perfect {
  color: #2ef0ff; font-size: 1.85rem;
  text-shadow: 0 0 20px rgba(46, 240, 255, 0.85), 0 0 44px rgba(46, 240, 255, 0.35);
}
.npd-judge.great   { color: #ff8fe4; font-size: 1.66rem; text-shadow: 0 0 18px rgba(255, 79, 216, 0.7); }
.npd-judge.good    { color: #b9a6ff; text-shadow: 0 0 14px rgba(150, 120, 255, 0.5); }
/* the near miss — this band used to be silent. It reads as ALMOST, in the
   warm colour, never in the grey the failure state owns. */
.npd-judge.near    { color: #ffcf6e; font-size: 1.15rem; letter-spacing: 0.16em;
                     text-shadow: 0 0 12px rgba(255, 190, 90, 0.45); }
/* the shield absorbing a break — mint, and unmistakably a GOOD thing */
.npd-judge.shield  { color: #7ef6be; font-size: 1.3rem;
                     text-shadow: 0 0 16px rgba(126, 246, 190, 0.65); }
.npd-judge.miss    { color: #8a7aa8; text-shadow: none; }

/* ── THE STREAK CALLOUT — a 25-combo milestone ─────────────────────────── */
.npd-streak {
  position: absolute;
  left: 0; right: 0;
  bottom: auto;
  top: 30%;
  text-align: center;
  font-size: 1.02rem;
  font-weight: 800;
  letter-spacing: 0.10em;
  color: #ffd76e;
  text-shadow: 0 0 18px rgba(255, 190, 60, 0.6);
  opacity: 0;
  pointer-events: none;
}
.npd-streak { filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.95)); }
.npd-streak.show { animation: npdStreak 1.6s ease-out; }
@keyframes npdStreak {
  0%   { opacity: 0; transform: translateY(10px) scale(0.85); }
  12%  { opacity: 1; transform: translateY(0)    scale(1.06); }
  70%  { opacity: 1; transform: translateY(0)    scale(1); }
  100% { opacity: 0; transform: translateY(-6px) scale(1); }
}

/* ── THE WARM-UP BADGE ─────────────────────────────────────────────────────
   The panel carries .warm for the first 8 s. It says, in one glance, that the
   game is currently being kind — and it goes away, which is what makes the
   ramp legible instead of mysterious. */
#np-dance.warm .npd-field::after {
  content: 'ΖΕΣΤΑΜΑ · φαρδύ παράθυρο · το σερί δεν σπάει';
  position: absolute;
  left: 0; right: 0; top: 6px;
  text-align: center;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  color: rgba(126, 246, 190, 0.8);
  pointer-events: none;
}

.npd-ready {
  position: absolute;
  left: 0; right: 0;
  bottom: auto;
  top: 22%;
  text-align: center;
  font-size: 0.9rem;
  letter-spacing: 0.10em;
  color: #ffc98a;
  text-shadow: 0 0 12px rgba(255, 150, 60, 0.5), 0 2px 6px rgba(0, 0, 0, 0.9);
  pointer-events: none;
}
.npd-ready.hidden { display: none; }

.npd-foot {
  padding: 3px 12px 10px;
  font-size: 0.64rem;
  letter-spacing: 0.05em;
  color: #b8a8d8;
  text-align: center;
  flex: 0 0 auto;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9);
}
.npd-foot kbd {
  display: inline-block;
  padding: 1px 5px;
  margin: 0 1px;
  border-radius: 5px;
  border: 1px solid rgba(255, 100, 220, 0.3);
  background: rgba(255, 255, 255, 0.05);
  color: #f2e9ff;
  font-size: 0.62rem;
}

/* ── result card — floats above the strip, never over the whole room ───── */
.npd-result {
  position: absolute;
  left: 50%;
  top: 50%;
  bottom: auto;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  width: min(400px, 92vw);
  max-height: 62vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 14px 18px;
  border-radius: 12px;
  border: 1px solid rgba(255, 150, 60, 0.4);
  background: rgba(8, 5, 14, 0.96);
  box-shadow: 0 0 30px rgba(255, 120, 40, 0.2);
  overflow-y: auto;
}
.npd-result.hidden { display: none; }

.npd-rtitle {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-align: center;
  color: #ff8fe4;
  text-shadow: 0 0 12px rgba(255, 79, 216, 0.5);
  margin-bottom: 4px;
}

.npd-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.8rem;
  padding: 3px 2px;
  border-bottom: 1px dashed rgba(255, 100, 220, 0.14);
  color: #cdbfe0;
}
.npd-row b { color: #f2e9ff; font-variant-numeric: tabular-nums; }
.npd-row.big { font-size: 1.0rem; border-bottom: none; padding-top: 8px; }
.npd-row.big b { color: #2ef0ff; text-shadow: 0 0 12px rgba(46, 240, 255, 0.5); }

.npd-pay {
  margin-top: 8px;
  padding: 9px 10px;
  border-radius: 10px;
  text-align: center;
  font-size: 0.82rem;
  color: #f2e9ff;
  border: 1px solid rgba(46, 240, 255, 0.35);
  background: rgba(46, 240, 255, 0.08);
}
.npd-pay.err {
  border-color: rgba(255, 122, 138, 0.5);
  background: rgba(255, 122, 138, 0.08);
  color: #ffc9d0;
}
.npd-pay b { color: #2ef0ff; }

.npd-again {
  margin-top: 10px;
  padding: 9px 0;
  border: none;
  border-radius: 9px;
  cursor: pointer;
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  color: #120816;
  background: linear-gradient(90deg, #ff4fd8, #6cf);
  box-shadow: 0 0 14px rgba(255, 79, 216, 0.35);
}
.npd-again:hover { filter: brightness(1.1); }

/* ── floor-marker prompt — a clone of #interact-prompt's look, its own node
      so the seat prompt's DOM and data structures stay untouched ─────────── */
#np-dance-prompt {
  position: absolute;
  left: 50%;
  bottom: 96px;
  transform: translateX(-50%);
  padding: 6px 14px;
  border-radius: 8px;
  background: rgba(12, 8, 20, 0.82);
  border: 1px solid rgba(46, 240, 255, 0.45);
  color: #f2e9ff;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  box-shadow: 0 0 18px rgba(46, 240, 255, 0.22);
}
#np-dance-prompt kbd {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 5px;
  border: 1px solid rgba(46, 240, 255, 0.4);
  background: rgba(255, 255, 255, 0.06);
}
#np-dance-prompt.hidden { display: none; }

/* ── SCREEN-EDGE BEAT PULSE ────────────────────────────────────────────────
   z-index 29 — the same slot the buddy bubble uses, i.e. one BELOW every panel
   (#np-chat 30, #np-dance 30, the HUD chips 30). pointer-events:none and
   display:none unless a round is live, so it can neither cover a panel nor
   swallow a click, and between rounds it is not in the layer tree at all.
   JS writes `opacity` and NOTHING else — one composited property, no repaint.
   Deliberately an INSET shadow with a transparent middle: the centre of the
   screen, where the arrows are, is never tinted. */
#npd-edge {
  position: fixed;
  inset: 0;
  z-index: 29;
  pointer-events: none;
  opacity: 0;
  box-shadow:
    inset 0 0 90px rgba(255, 79, 216, 0.55),
    inset 0 0 190px rgba(46, 240, 255, 0.22);
  /* will-change + paint containment promote this to its own compositor layer,
     so the per-frame opacity write is a compositor property and the 190 px
     blurred inset shadow is rasterised ONCE instead of every frame. Measured:
     without these it cost real frames; see tools/dancecost.mjs. */
  will-change: opacity;
  contain: paint;
}
#npd-edge.hidden { display: none; }

/* ─── Ο ΚΟΛΛΗΤΟΣ · onboarding buddy (gameplay-engineer) ──────────────────────
   TOP-CENTRE BY ELIMINATION, not by taste: #np-chat owns bottom-left, the
   minimap + EDIT/KEY/mute chips own bottom-right, #hint-bar owns bottom-centre
   and #interact-prompt owns bottom-96px. Top-centre is the only band a tip can
   live in without ever landing on the chat panel. #zone/#floor sit top-LEFT and
   #fps top-RIGHT, so a max-width-capped centred bubble clears both.
   z-index 29 ON PURPOSE — one BELOW every panel (#np-chat 30, #np-dance 30,
   the HUD chips 30, chunk loader 40, key panel 60). #np-dance is a CENTRED
   660 px modal, so on a 720 px viewport its header reaches y≈30 and a tip at
   z-35 would have sat on top of it. A tip must never cover a panel: the
   suppressors hide it anyway, this is the belt to that pair of braces.
   pointer-events: none — it is a toast, never a click target. */
#np-buddy {
  position: fixed;
  top: 22px;
  left: 50%;
  z-index: 29;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(560px, 74vw);
  padding: 10px 16px 10px 13px;
  border-radius: 14px;
  border: 1px solid rgba(232, 192, 106, 0.45);
  background: rgba(12, 8, 20, 0.92);
  box-shadow: 0 0 26px rgba(212, 160, 60, 0.22), inset 0 0 22px rgba(90, 60, 10, 0.16);
  backdrop-filter: blur(8px);
  color: #f6ecd8;
  font-size: 0.92rem;
  line-height: 1.4;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-50%) translateY(-7px);
  transition: opacity 0.38s ease, transform 0.38s ease;
}

#np-buddy.hidden { display: none; }
#np-buddy.nb-on { opacity: 1; transform: translateX(-50%) translateY(0); }

.nb-face {
  font-size: 20px;
  line-height: 1;
  flex: 0 0 auto;
  filter: drop-shadow(0 0 10px rgba(232, 192, 106, 0.5));
}

.nb-text { min-width: 0; }

/* «ξανά τις οδηγίες» — deliberately the SAME 38x38 glass chip as .hud-mute,
   stacked directly above it in the same right-hand column (mute 172 + 38 + 8). */
.hud-buddy {
  position: absolute;
  right: 20px;
  bottom: 218px;
  pointer-events: auto;
  z-index: 30;
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 10px;
  background: rgba(8, 4, 16, 0.75);
  border: 1px solid rgba(232, 192, 106, 0.35);
  color: #f2e9ff;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}

.hud-buddy:hover { border-color: #e8c06a; }

/* #chunkload-tip while it is on loan to the title panel during boot: same
   .cl-tip rule underneath, only re-toned for the violet menu it sits in. */
.nb-boot {
  color: #c9a86a !important;
  font-size: 0.82rem !important;
  letter-spacing: 0.02em !important;
  text-align: center;
}

/* ─── LOAD PROGRESS · persistent corner indicator (gameplay-engineer) ────────
   The club is playable while ~45 MB of meshes are still arriving, so this is
   the player's evidence that more is coming. Top-left, directly under the
   ZONE / FLOOR / BRIGHT status column it belongs to; #np-chat owns bottom-left
   and the minimap owns bottom-right, so this is the only free column.
   BARAKI gold, not the game's pink/cyan — it is chrome, not the club.
   It BORROWS #chunkload-fill and #chunkload-tip, so .cl-bar / .cl-fill /
   .cl-tip above still supply the bar's own look; only the frame is new. */
#np-loadhud {
  position: fixed;
  top: 92px;
  left: 20px;
  z-index: 29;
  width: 248px;
  padding: 9px 12px 10px;
  border-radius: 11px;
  border: 1px solid rgba(232, 192, 106, 0.34);
  background: rgba(10, 7, 16, 0.82);
  box-shadow: 0 0 22px rgba(0, 0, 0, 0.45), inset 0 0 18px rgba(90, 60, 10, 0.12);
  backdrop-filter: blur(6px);
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.5s ease, transform 0.5s ease;
}

#np-loadhud.nlh-off { opacity: 0; transform: translateY(-6px); }

.nlh-label {
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #c9a86a;
  margin-bottom: 7px;
}

/* the borrowed .cl-tip sits inside this panel while it is on loan — the
   chunk-loader sizing is for a 420 px centred panel, too big for a 248 px chip */
#np-loadhud .cl-tip {
  margin-top: 7px;
  font-size: 0.7rem;
  line-height: 1.35;
  color: #9c8756;
  letter-spacing: 0.01em;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ECONOMY — the credits chip (#np-cred) and the κέρασμα / VIP panel (#np-econ)
   ───────────────────────────────────────────────────────────────────────────
   Same HUD language as #np-chat and #np-dance: dark glass, pink/cyan neon,
   14 px radius. The panel sits at z 30 with its siblings; the chip lives INSIDE
   #hud as a <button>, which is what keeps it visible and clickable over the
   pause menu under the layering rule above.

   Every number this UI shows comes from the server. There is no cash-out
   control here and there is no disabled one either — credits stay in the shop,
   and .npe-nocash says so in words rather than by greying something out.
   ═══════════════════════════════════════════════════════════════════════════ */

/* the persistent balance chip — right column, directly under #fps (top 18) */
#np-cred {
  position: absolute;
  top: 40px;
  right: 20px;
  pointer-events: auto;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 10px;
  background: rgba(8, 4, 16, 0.75);
  border: 1px solid rgba(255, 100, 220, 0.35);
  color: #f2e9ff;
  font: inherit;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
  font-variant-numeric: tabular-nums;
}
#np-cred:hover { border-color: #2ef0aa; }
#np-cred.hidden { display: none; }
#np-cred .npe-amt { font-weight: 700; color: #ffd76a; }
/* one short pulse when the SERVER's balance comes back different */
#np-cred.bump { animation: npe-bump 0.5s ease; }
@keyframes npe-bump {
  0%   { transform: scale(1);    border-color: rgba(255, 100, 220, 0.35); }
  35%  { transform: scale(1.14); border-color: #ffd76a; }
  100% { transform: scale(1);    border-color: rgba(255, 100, 220, 0.35); }
}

/* the panel — bottom-RIGHT on purpose: #np-chat owns bottom-left and the two
   are meant to be open in sequence, never fighting for the same corner. It
   clears the minimap by sitting above it. */
#np-econ {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 30;
  width: min(340px, 92vw);
  max-height: 82vh;
  overflow-y: auto;
  scrollbar-width: thin;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  border: 1px solid rgba(255, 100, 220, 0.45);
  /* NO backdrop-filter here, unlike #np-chat, and it is a measured decision.
     This panel is up to 82vh tall, and a backdrop blur over a live WebGL canvas
     is re-composited every single frame across that whole area. Toggling only
     this property, 240-frame samples at the same pose in one session:

       panel CLOSED (baseline)                  median 16.7ms  p95 20.7  worst 22.8
       panel open, backdrop-filter: blur(8px)   median 17.8ms  p95 30.3  worst 82.5
       panel open, no backdrop-filter           median 16.7ms  p95 20.0  worst 23.0

     The blur cost 1.1 ms of median frame time, half the p95 headroom and an
     82 ms worst-frame hitch — and bought nothing, because the background below
     is already 0.96 opaque. Without it an open panel is indistinguishable from
     a closed one. #np-chat keeps its blur: it is a fraction of this area and
     does not show the same cost. */
  background: rgba(12, 8, 20, 0.96);
  box-shadow: 0 0 28px rgba(255, 0, 180, 0.22), inset 0 0 24px rgba(80, 0, 120, 0.18);
  pointer-events: auto;
  color: #f2e9ff;
}
#np-econ.hidden { display: none; }

.npe-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255, 100, 220, 0.25);
  position: sticky;
  top: 0;
  background: rgba(12, 8, 20, 0.96);
  border-radius: 14px 14px 0 0;
}
.npe-title {
  flex: 1;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  background: linear-gradient(90deg, #ff4fd8, #6cf);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.npe-head button {
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #d8cdf0;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.npe-head button:hover { border-color: #2ef0aa; }

.npe-body { padding: 11px 12px; display: flex; flex-direction: column; gap: 11px; }

/* wallet */
.npe-bal {
  display: flex;
  align-items: baseline;
  gap: 9px;
}
.npe-bal b {
  font-size: 1.7rem;
  font-weight: 700;
  color: #ffd76a;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 16px rgba(255, 200, 80, 0.35);
}
.npe-bal span { font-size: 0.7rem; letter-spacing: 0.18em; color: #b89cff; text-transform: uppercase; }

.npe-earn { font-size: 0.72rem; color: rgba(220, 200, 255, 0.6); letter-spacing: 0.02em; }
.npe-earn i { font-style: normal; color: #7fefb0; }

/* rule 1, in words. Never a greyed-out withdraw button. */
.npe-nocash {
  font-size: 0.68rem;
  line-height: 1.4;
  color: #8a7aa8;
  border-left: 2px solid rgba(138, 122, 168, 0.4);
  padding-left: 8px;
}

.npe-sec {
  border-top: 1px solid rgba(255, 100, 220, 0.18);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.npe-sec > h4 {
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #b89cff;
  font-weight: 600;
}
.npe-girl { font-size: 0.95rem; font-weight: 700; color: #ff8ae8; letter-spacing: 0.05em; }
.npe-none { font-size: 0.78rem; color: rgba(220, 200, 255, 0.6); line-height: 1.4; }

/* mood: a bar toward the SERVER's vip threshold, never a computed verdict */
.npe-moodrow { display: flex; align-items: baseline; justify-content: space-between; font-size: 0.72rem; color: #b89cff; }
.npe-moodrow b { color: #f2e9ff; font-variant-numeric: tabular-nums; }
.npe-moodbar {
  position: relative;
  height: 7px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
.npe-moodfill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: 4px;
  background: linear-gradient(90deg, #6cf, #ff4fd8);
  transition: width 0.45s ease;
}
.npe-moodfill.hot { background: linear-gradient(90deg, #ffd76a, #ff4fd8); }
.npe-moodhint { font-size: 0.68rem; color: rgba(220, 200, 255, 0.55); line-height: 1.35; }

/* the buy buttons */
.npe-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 12px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 100, 220, 0.32);
  color: #f2e9ff;
  font: inherit;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  cursor: pointer;
  text-align: left;
}
.npe-btn:hover:not(:disabled) { border-color: #2ef0aa; background: rgba(46, 240, 170, 0.08); }
.npe-btn:disabled { opacity: 0.45; cursor: default; }
.npe-btn .npe-lbl { flex: 1; }
.npe-btn .npe-cost { color: #ffd76a; font-weight: 700; font-variant-numeric: tabular-nums; }
.npe-btn.npe-vip { border-color: rgba(255, 215, 106, 0.45); }
.npe-btn.npe-vip:hover:not(:disabled) { border-color: #ffd76a; background: rgba(255, 215, 106, 0.09); }
.npe-btn.npe-pass { border-color: rgba(46, 240, 170, 0.5); }

/* what the server said — the one place any outcome is rendered */
.npe-say {
  font-size: 0.8rem;
  line-height: 1.4;
  padding: 8px 11px;
  border-radius: 11px;
  border-bottom-left-radius: 4px;
  background: rgba(120, 0, 160, 0.28);
  border: 1px solid rgba(255, 100, 220, 0.35);
  color: #f6e8ff;
}
.npe-say.ok  { background: rgba(46, 240, 170, 0.12); border-color: rgba(46, 240, 170, 0.4); color: #d8ffe8; }
.npe-say.err { background: rgba(255, 122, 138, 0.12); border-color: rgba(255, 122, 138, 0.45); color: #ffdfe3; }
.npe-say.hidden { display: none; }

/* ledger */
.npe-led { display: flex; flex-direction: column; gap: 3px; }
.npe-ledrow {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.7rem;
  color: rgba(220, 200, 255, 0.62);
  font-variant-numeric: tabular-nums;
}
.npe-ledrow b { color: #7fefb0; font-weight: 600; }
.npe-ledrow b.neg { color: #ff8ae8; }

.npe-foot {
  padding: 8px 12px 11px;
  font-size: 0.68rem;
  color: rgba(220, 200, 255, 0.5);
  letter-spacing: 0.02em;
}
.npe-foot kbd {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 4px;
  padding: 1px 5px;
  font: inherit;
  font-size: 0.66rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BOOT / LOADING SCREEN — the ping-ponged intro clip behind the start panel.
   The club needs ~42 s to dress itself and the player used to spend all of it
   reading a text box. Now the wait IS the trailer.
   Everything here lives inside #overlay, which every capture harness already
   hides via the shared `#hud,#overlay,.tx-panel` selector — so no gate frame
   can ever contain it and no tool needed editing.
   ═════════════════════════════════════════════════════════════════════════ */
.boot-vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* cover, so a 16:9 clip still fills a phone held upright. The BARAKI sign is
     centred in both cuts, which is what makes cropping safe on either axis. */
  object-fit: cover;
  /* the BARAKI sign sits in the upper third of both cuts; biasing the crop
     up keeps it in frame on a wide viewport instead of slicing it off */
  object-position: 50% 34%;
  z-index: 0;
  pointer-events: none;
}

/* Legibility, not decoration: white 2 rem type over a lit club plate is
   unreadable without it, and the panel's own blur is not enough at the edges. */
.boot-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* Tuned by LOOKING at the shot, not by taste: the first pass was
     0.35/0.82/0.55 and it crushed the clip to near-black, which threw away the
     whole reason for having it. This darkens only what sits behind the panel
     and lets the plate breathe at the edges. */
  background:
    radial-gradient(ellipse at 50% 50%, rgba(4, 2, 10, 0.10), rgba(4, 2, 10, 0.52) 72%),
    linear-gradient(180deg, rgba(4, 2, 10, 0.34), rgba(4, 2, 10, 0.06) 40%, rgba(4, 2, 10, 0.46));
}

#overlay .panel {
  position: relative;
  z-index: 2;
  /* a touch more transparent than the shipped panel, so the clip reads THROUGH
     the start screen instead of behind a wall */
  background: rgba(10, 6, 18, 0.72);
}

.b-tld {
  background: none;
  -webkit-text-fill-color: #d46cff;
  color: #d46cff;
  font-size: 0.5em;
  letter-spacing: 0.12em;
}

/* ── THE LOADING BAR ──────────────────────────────────────────────────────
   A condom unrolling, because the user asked for the Leisure Suit Larry gag
   and Larry's inventory is where the joke comes from. Mechanically it is an
   ordinary progress bar: `.ld-fill` is the rolled-down length and `.ld-roll`
   is the ring riding its leading edge, so both are driven by ONE number.
   The number is real — loadHudSet() counts .glb responses off a
   PerformanceObserver. Nothing here animates on a timer pretending to load. */
.ld { margin: 1.15rem 0 1.05rem; }

.ld-sleeve {
  position: relative;
  height: 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 140, 220, 0.28);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.55);
  overflow: visible;   /* the ring rides slightly proud of the sleeve */
}

.ld-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0%;
  border-radius: 999px;
  transition: width 0.35s ease-out;
  /* latex: a hot specular line along the top, deeper tone underneath */
  background:
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.55) 0%,
      rgba(255, 190, 235, 0.30) 18%,
      rgba(226, 92, 190, 0.95) 55%,
      rgba(150, 30, 130, 1) 100%);
  box-shadow: 0 0 14px rgba(255, 90, 210, 0.45);
}

/* The roll at the leading edge. `left` tracks the same percentage as the fill,
   and the translate keeps it centred on the edge instead of hanging off it. */
.ld-roll {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  transition: left 0.35s ease-out;
  background: radial-gradient(circle at 34% 32%, #ffd9f2, #e25cbe 45%, #7d1a6a 100%);
  box-shadow: 0 0 12px rgba(255, 110, 220, 0.65), inset -2px -2px 6px rgba(0, 0, 0, 0.4);
}

.ld-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  margin-top: 0.5rem;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
}

.ld-label { color: #d9c8ff; }
.ld-pct { color: #ff8fdc; font-variant-numeric: tabular-nums; font-weight: 700; }

/* The button cannot be pressed into a club that has no floor yet. Disabled is
   the honest state, and it says what it is waiting for. */
#start-btn[disabled] {
  opacity: 0.5;
  cursor: progress;
  filter: grayscale(0.55);
}

@media (max-aspect-ratio: 1/1) {
  /* Portrait: the tall cut of the clip, so a phone gets a composition framed
     for it rather than the middle third of a landscape one. */
  .boot-vid { object-position: 50% 40%; }
}

@media (prefers-reduced-motion: reduce) {
  /* The clip is decorative; the poster carries the same image. */
  .boot-vid { display: none; }
  #overlay { background-image: url('assets/textures/boot-wide.jpg'); background-size: cover; background-position: center; }
  .ld-fill, .ld-roll { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ΚΑΜΑΚΙ SCAN — the loading screen that is not a loading screen.
   Sierra/Larry flavour on purpose: a chunky bordered box, a monospace readout
   and a target reticle, because the honest alternative was a spinner over a
   girl who had not downloaded yet. ONE element, reused by all 43 performers.
   ═════════════════════════════════════════════════════════════════════════ */
.kamaki {
  position: fixed;
  left: 50%;
  bottom: 12%;
  transform: translateX(-50%);
  z-index: 24;                       /* over the club, under nothing that matters */
  display: flex;
  gap: 0.9rem;
  align-items: stretch;
  padding: 0.75rem 0.9rem;
  pointer-events: none;              /* never steals the click that opened it */
  border: 2px solid #ff8fdc;
  border-radius: 6px;
  background: linear-gradient(180deg, rgba(22, 6, 30, 0.94), rgba(8, 3, 14, 0.96));
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.7), 0 0 34px rgba(255, 60, 190, 0.35);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  opacity: 0;
  transition: opacity 0.18s ease-out;
}
.kamaki.km-live { opacity: 1; }
.kamaki.hidden { display: none; }

/* the reticle: a polaroid-sized window that fills in at the end */
.km-frame {
  position: relative;
  width: 150px; height: 188px;
  border: 1px solid rgba(255, 143, 220, 0.5);
  background: rgba(255, 143, 220, 0.06);
  overflow: hidden;
  flex: 0 0 auto;
}
.km-shot { display: block; width: 100%; height: 100%; opacity: 0; transition: opacity 0.25s ease-out; }
.km-shot-on .km-shot { opacity: 1; }

/* the sweep — the one thing that says "scanning" without a word */
/* ΚΟΚΚΙΝΗ ΓΡΑΜΜΗ, ΠΑΝΩ ΚΑΤΩ. Asked for exactly so. Two changes from the teal
   original: the colour, and `alternate` — a `linear infinite` sweep only ever
   travels one way and snaps back, which reads as a repeating wipe rather than a
   scanner passing over a face. `alternate` makes the return trip real, and it
   is the return trip that sells the machine. The hairline in the middle is what
   the eye actually tracks; the wash around it is the falloff. */
.km-sweep {
  position: absolute; left: 0; right: 0; height: 22%;
  background: linear-gradient(180deg,
    transparent 0%, rgba(255, 40, 60, 0.10) 35%, rgba(255, 70, 80, 0.85) 50%,
    rgba(255, 40, 60, 0.10) 65%, transparent 100%);
  box-shadow: 0 0 18px rgba(255, 40, 60, 0.5);
  animation: km-sweep 1.05s ease-in-out infinite alternate;
}
.km-shot-on .km-sweep { opacity: 0.9; }   /* it scans OVER her — that is the point */
.kamaki.km-locked .km-sweep { display: none; }
@keyframes km-sweep { from { top: -11%; } to { top: 89%; } }

.km-corner { position: absolute; width: 16px; height: 16px; border: 2px solid #78ffe6; }
.km-corner.tl { top: 4px; left: 4px; border-right: 0; border-bottom: 0; }
.km-corner.tr { top: 4px; right: 4px; border-left: 0; border-bottom: 0; }
.km-corner.bl { bottom: 4px; left: 4px; border-right: 0; border-top: 0; }
.km-corner.br { bottom: 4px; right: 4px; border-left: 0; border-top: 0; }

.km-body { display: flex; flex-direction: column; justify-content: center; min-width: 232px; gap: 0.45rem; }
.km-title {
  font-size: 1.02rem; font-weight: 700; letter-spacing: 0.16em; color: #ff8fdc;
  text-shadow: 0 0 12px rgba(255, 80, 210, 0.6);
}
.km-line { font-size: 0.72rem; letter-spacing: 0.05em; color: #9df6e4; min-height: 1em; }
.km-bar {
  height: 12px; border: 1px solid rgba(255, 143, 220, 0.45);
  background: rgba(0, 0, 0, 0.5);
}
/* deliberately STEPPED, not smooth — a chunky 8-bit fill is the joke, and it
   also makes real progress readable at a glance instead of creeping */
.km-fill {
  height: 100%; width: 0%;
  background: repeating-linear-gradient(90deg, #78ffe6 0 6px, transparent 6px 8px);
  transition: width 0.14s linear;
}
.km-pct { font-size: 0.72rem; color: #ff8fdc; font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
  .kamaki { bottom: 16%; padding: 0.55rem 0.6rem; gap: 0.6rem; }
  .km-frame { width: 96px; height: 120px; }
  .km-body { min-width: 168px; }
  .km-title { font-size: 0.86rem; }
}
@media (prefers-reduced-motion: reduce) { .km-sweep { animation: none; opacity: 0.35; } }
