/* Tylko waga 400. Reszta interfejsu i tak jest wymuszona na 400 przez
   `* { font-weight:400 !important }` ponizej, wiec wersja bold (700) nigdy
   nie byla uzywana - plik silkscreen-2.ttf zostal usuniety. */
@font-face {
  font-family:'Silkscreen';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(assets/fonts/silkscreen-1.ttf) format('truetype')
}
* {
  font-family:Silkscreen,sans-serif;
  font-weight:400 !important;
  user-select:none;
  image-rendering:pixelated
}
html {
  margin:0;
  padding:0
}
body {
  background:black;
  margin:0;
  padding:0;
  min-height:100vh;
  overflow:hidden
}
.loading-screen {
  position:fixed;
  inset:0;
  z-index:100000;
  display:flex;
  align-items:center;
  justify-content:center;
  background:#000;
  color:#fff;
  text-align:center;
  letter-spacing:0;
  transition:opacity .25s ease,visibility .25s ease
}
.loading-screen>div {
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:14px
}
.loading-screen strong {
  display:block;
  min-width:310px;
  font-size:28px;
  line-height:1.1;
  text-transform:uppercase
}
.loading-screen em {
  display:block;
  min-width:80px;
  font-size:18px;
  line-height:1;
  color:#72fa55;
  font-style:normal
}
.loading-dots {
  display:inline-block;
  width:3ch;
  text-align:left
}
body.loading-complete .loading-screen,
body.clean-mode .loading-screen {
  opacity:0;
  visibility:hidden;
  pointer-events:none
}
/* ⚠️ Rozmiar USTAWIA JS inline (applyViewportSize() w app.js), nie ta regula.
   Dawne width:min(100vw,1200px)/height:min(100vh,540px) trzymalo sztywny kadr
   1200x540; dzis kadr dopasowuje sie do okna, a w aplikacji natywnej ma
   sztywne 1200 px LOGICZNYCH rozciaganych transformem na cala szerokosc
   ekranu. Zostaje tylko zaczepienie w lewym gornym rogu - transform-origin
   jest 'top left', wiec dawne margin:auto (centrowanie) rozjezdzaloby skale. */
/* ⚠️ 100vw/100vh to WARTOSC AWARYJNA, nadpisywana inline przez JS. Potrzebna,
   bo .game-wrapper musi miec sensowny rozmiar ZANIM gra go policzy: w trybie
   ?clean=1 sekwencja startowa rzuca wyjatek jeszcze przed applyViewportSize(),
   a komunikat o wyczyszczonym zapisie siedzi w .game (inset:0) - bez tego
   wychodzil 0x0, czyli byl niewidoczny. */
.game-wrapper {
  position:absolute;
  top:0;
  left:0;
  width:100vw;
  height:100vh;
  overflow:hidden;
  opacity:0;
  animation:gameStartReveal 1s ease 1s forwards;
  pointer-events:none
}
body.game-started .game-wrapper {
  pointer-events:auto
}
body.clean-mode .game-wrapper {
  animation:none;
  opacity:1
}
/* Margines na notch (ustawienia: NOTCH MARGIN) - ⚠️ NIE box-model (border/
   padding, tak bylo tu wczesniej) - `#world-canvas`/`#ui-canvas` maja WLASNA
   rozdzielczosc ustawiana RAZ w JS (dopasowana do pierwotnego rozmiaru .game
   przy starcie gry) i nie sledza pozniejszych zmian szerokosci kontenera CSS
   - zmniejszenie samego .game-wrapper (np. przez border) zostawialoby
   canvasy przy STARYM rozmiarze, ktory wtedy WYSTAJE poza nowy, mniejszy
   kontener i zostaje UCIETY przez `overflow:hidden` (zglaszane jako
   "przycina zamiast zwezac"). Przeliczanie canvasow na zywo zmienialoby tez
   wspolrzedne swiata (middleBlockX), wiec zamiast tego caly .game-wrapper
   (kanwy + DOM na nim, w tym joystick) dostaje inline `transform:scaleX()`
   ustawiany z JS (setNotchScale() w app.js, wolane z applyNotchMargin() i
   przy resize okna, patrz komentarz tam) - to czysto WIZUALNE pomniejszenie
   JUZ WYRENDEROWANEGO obrazu, wiec nic nie jest ucinane, tylko rysowane
   mniejsze. Zero reguly CSS tutaj celowo - inline style ustawiany/czyszczony
   wprost przez JS jest prostszy niz posrednictwo przez custom property. */
/* ⚠️ min-width:768px usuniete - kadr jest dzis skalowany transformem, wiec
   dolna granica szerokosci pilnuje MIN_LOGICAL_WIDTH w app.js, a sztywne
   minimum tutaj tylko rozpychalo kontener przy malym oknie. */
.game {
  position:relative;
  width:100%;
  height:100%;
  margin:0 auto;
  background-image:url('assets/bg.png');
  background-size:cover;
  overflow:hidden;
  transform-origin:top left
}
.clean-message {
  position:absolute;
  inset:0;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:18px;
  color:#fff;
  text-align:center;
  background:#000;
  z-index:99999
}
.clean-message h1 {
  margin:0;
  font-size:42px
}
.clean-message p {
  margin:0;
  font-size:18px;
  line-height:1.6
}
.clean-message b {
  color:#9cff72
}
/* Brazowe tlo podziemia. Zaczyna sie dokladnie na linii gruntu, a ta jedzie
   w dol razem z pojazdem (vehicleScreenRowOffset w app.js) - dlatego wysokosc
   ustawia JS przez --sky-bottom, a nie stala. Fallback to stara wartosc dla
   zerowego przesuniecia. Nad ta linia widac bg.png z .game, czyli niebo. */
.game>span {
  position:absolute;
  left:0;
  top:var(--sky-bottom, calc(64px * 4 + 8px));
  display:block;
  width:200px;
  height:100%;
  width:100%;
  background:#22110c;
  position:absolute
}
/* Fundament silnika canvas (Faza 1 przejscia z DOM na canvas) - na razie
   wspoisniejace OBOK renderowania DOM, nic jeszcze na nie nie rysuje.
   #world-canvas przejmie bloki/pojazd/moby/bomby/fog (Fazy 2-6),
   #ui-canvas panele/sklep/menu/minimape (Fazy 7-9). pointer-events:none na
   obu, dopoki Faza 9 nie podepnie prawdziwego hit-testingu klikniec. */
#world3d-canvas, #world-canvas, #ui-canvas {
  position:absolute;
  left:0;
  top:0;
  pointer-events:none
}
/* Faza 9/10B: #ui-canvas obsluguje klikniecia nakladek (sklep/pauza/staty/
   reset-confirm). ⚠️ Musi tez byc NAD elementami DOM (.panel z-index:200,
   .info 250, .stats-overlay 400) - inaczej to one lapia klikniecia, mimo ze
   sa niewidoczne, a przyciski na canvasie nie reaguja (realnie w to
   wdepnalem). Canvas jest przezroczysty tam, gdzie nic nie rysuje, wiec
   podniesienie go nie zaslania niczego wizualnie. */
#ui-canvas {
  pointer-events:auto;
  z-index:500;
  /* Jedno okno z zakladkami wprowadzilo przeciaganie palcem (przesuwanie
     mapy, drag&drop narzedzi na sloty HUD) - bez tego przegladarka mobilna
     przewijalaby/skalowala strone zamiast przekazac gest do canvasu. */
  touch-action:none
}
@property --mob-gradient-angle {
  syntax:'<angle>';
  inherits:true;
  initial-value:270deg
}
.mob {
  position:absolute;
  width:32px;
  height:32px;
  --mob-gradient-angle:270deg;
  background:#050705;
  border:1px solid #183a22;
  box-shadow:0 2px 6px #0009, inset 0 0 6px #4dff7033;
  border-radius:16px 18px / 16px 16px;
  /* Faza 4 planu DOM->canvas: element zostaje w pelni funkcjonalny (pozycja/
     klasy nadal aktualizowane przez nietkniety kod ruchu mobow), ale wizualnie
     maluje go canvas (drawMob w app.js). */
  visibility:hidden;
  overflow:visible;
  transition:left .25s linear,top .25s linear,width .25s linear,height .25s linear,border-radius .45s ease-in-out
}
.mob::before {
  content:'';
  position:absolute;
  inset:0;
  z-index:0;
  border-radius:inherit;
  background:linear-gradient(var(--mob-gradient-angle),#050705 0%,#0b110c 46%,#10381d 100%);
  transition:--mob-gradient-angle .18s ease-in-out;
  pointer-events:none
}
.mob-eyes {
  position:absolute;
  z-index:2;
  left:50%;
  top:50%;
  width:14px;
  height:18px;
  transform:translate(-50%,-50%);
  transition:left .18s ease-in-out,top .18s ease-in-out,transform .18s ease-in-out;
  pointer-events:none
}
.mob-eyes i {
  position:absolute;
  right:0;
  top:2px;
  width:6px;
  height:6px;
  background:#f00;
  border-radius:50%;
  box-shadow:0 0 5px #f00
}
.fuel-worm .mob-eyes i {
  background:#ffea00;
  box-shadow:0 0 5px #ffea00
}
.air-worm .mob-eyes i {
  background:#48cfff;
  box-shadow:0 0 5px #48cfff
}
/* Stalowy robak - TYLKO podglad w edytorze struktur. W grze rysuje go
   drawSteelWorm na canvasie (nachodzace plyty pancerza z zuwaczkami), czego
   DOM-em nie odtworzymy 1:1 - ale kafelek ma czytac sie jako PLYTA PANCERZA
   ze swiecacymi szczelinami, a nie jako kolejny kolorowy robak.
   ⚠️ Kolory 1:1 z STEEL_WORM_COLORS w app.js (jasny/sredni/ciemny/obrys/
   panel/neon/poswiata) - zmieniajac tam, zmien i tu. */
.steel-worm {
  background:#434a53 !important;
  border-color:#15171a !important;
  /* osmiokat: kwadrat ze scietymi rogami, jak steelPlatePath w app.js */
  border-radius:6px !important;
  clip-path:polygon(26% 0,74% 0,100% 26%,100% 74%,74% 100%,26% 100%,0 74%,0 26%)
}
/* ⚠️ MUSI byc nadpisane: bazowe .mob::before maluje ZIELONY gradient na calym
   elemencie (inset:0), wiec bez tego stalowe tlo bylo calkowicie zaslonione -
   stad zgloszenie "stalowy wyglada jak tlenowy" (zielone cialo + blekitne oczy
   = dokladnie air-worm). Sam `background` na .steel-worm tego nie zalatwia. */
.steel-worm::before {
  background:linear-gradient(180deg,#6b737d 0%,#434a53 45%,#252a30 100%) !important;
  /* wpuszczony panel wewnetrzny - odwrocony gradient, jak drawSteelPlate */
  box-shadow:inset 0 0 0 4px #20242a, inset 0 0 10px #15171acc
}
/* Dwie swiecace szczeliny zamiast oczu - element rozpoznawczy tego robaka.
   Reuzywamy istniejacych <i> z .mob-eyes, zeby nie dokladac markupu.
   ⚠️ Selektory musza miec `.mob.steel-worm` (0,3,0), a nie samo `.steel-worm`
   (0,2,0): reguly kierunkowe `.mob-dir-...` oraz `.mob-eyes i:last-child` stoja
   NIZEJ w pliku i przy remisie specyficznosci to one by wygraly, zeslizgujac
   szczeliny na brzeg plyty i ustawiajac je jedna pod druga. */
.mob.steel-worm .mob-eyes {
  left:50%;
  top:50%;
  width:14px;
  height:16px;
  transform:translate(-50%,-50%)
}
.mob.steel-worm .mob-eyes i {
  left:2px;
  right:auto;
  top:1px;
  width:4px;
  height:14px;
  border-radius:2px;
  background:#ffffff;
  box-shadow:0 0 6px #3fc8f5, 0 0 2px #d6f6ff
}
.mob.steel-worm .mob-eyes i:last-child {
  left:8px;
  top:1px
}
/* Pajak - TYLKO podglad w edytorze struktur. W grze rysuje go drawSpider na
   canvasie (szesc nog z IK + obrotowe dzialko), czego DOM-em nie odtworzymy -
   kafelek ma sie czytac jako opancerzony korpus z dzialkiem na srodku.
   ⚠️ Kolory 1:1 z SPIDER_COLORS w app.js.
   ⚠️ Ta sama pulapka co przy stalowym robaku: bazowe .mob::before maluje
   ZIELONY gradient na calym elemencie (inset:0), wiec musi zostac nadpisane, a
   selektory dzialka potrzebuja specyficznosci .mob.spider (0,3,0) - reguly
   kierunkowe .mob-dir-... stoja NIZEJ w pliku i przy remisie by wygraly. */
.spider {
  background:#6c747e !important;
  border-color:#191c20 !important;
  border-radius:6px !important;
  clip-path:polygon(26% 0,74% 0,100% 26%,100% 74%,74% 100%,26% 100%,0 74%,0 26%)
}
.spider::before {
  background:linear-gradient(180deg,#9aa2ab 0%,#6c747e 45%,#3b4148 100%) !important;
  box-shadow:inset 0 0 0 4px #565e67, inset 0 0 10px #191c20cc
}
/* dzialko: talerz podstawy (kontener oczu) + lufa (pierwsze <i>) */
.mob.spider .mob-eyes {
  left:50%;
  top:50%;
  width:14px;
  height:14px;
  border-radius:50%;
  background:radial-gradient(circle at 35% 35%,#9aa2ab,#3b4148);
  box-shadow:0 0 0 1px #191c20;
  transform:translate(-50%,-50%)
}
.mob.spider .mob-eyes i {
  left:5px;
  right:auto;
  top:-9px;
  width:5px;
  height:18px;
  border-radius:1px;
  background:linear-gradient(90deg,#79818b,#3f454c);
  box-shadow:0 0 0 1px #191c20
}
.mob.spider .mob-eyes i:last-child {
  display:none
}
/* Mina morska - TYLKO podglad w edytorze struktur. Gra rysuje ja proceduralnie
   na canvasie (drawSeaMine), wiec ten kafelek z natury NIE odtwarza renderera -
   ma sie tylko czytac jako ciemna, kolczasta kula, a nie jako zielony robak.
   ⚠️ Kolory 1:1 z SEA_MINE_COLORS w app.js.
   ⚠️ .mob::before maluje zielony gradient na inset:0 - MUSI zostac nadpisane,
   inaczej cale tlo miny jest zaslonione (ta sama pulapka co przy steel-worm).
   ⚠️ Selektory swiatelek potrzebuja specyficznosci .mob.seamine (0,3,0) -
   reguly kierunkowe .mob-dir-... stoja NIZEJ w pliku i przy remisie by wygraly. */
.seamine {
  background:#2d394c !important;
  border-color:#151d2f !important;
  border-radius:50% !important;
  /* wypustki (rogi Hertza) - osiem krotkich kolcow na obwodzie kuli */
  clip-path:polygon(46% 0,54% 0,54% 12%,66% 5%,72% 10%,64% 20%,78% 22%,90% 10%,95% 15%,83% 27%,95% 34%,100% 46%,100% 54%,88% 54%,95% 66%,90% 72%,80% 64%,78% 78%,90% 90%,85% 95%,73% 83%,66% 95%,54% 100%,46% 100%,46% 88%,34% 95%,28% 90%,36% 80%,22% 78%,10% 90%,5% 85%,17% 73%,5% 66%,0 54%,0 46%,12% 46%,5% 34%,10% 28%,20% 36%,22% 22%,10% 10%,15% 5%,27% 17%,34% 5%
  );
}
.seamine::before {
  background:radial-gradient(circle at 34% 32%,#404f5e 0%,#2d394c 52%,#1e283b 100%) !important;
  box-shadow:inset 0 0 0 1px #151d2f, inset 0 0 8px #151d2fcc;
  border-radius:50%
}
/* swiatelka na czubkach wypustek - w grze mrugaja bialo (spokoj) albo
   czerwono (alarm); w podgladzie sa po prostu zapalone na bialo */
.mob.seamine .mob-eyes {
  left:50%; top:50%;
  width:22px; height:22px;
  transform:translate(-50%,-50%)
}
.mob.seamine .mob-eyes i {
  left:0; right:auto; top:2px;
  width:4px; height:4px;
  border-radius:50%;
  background:#ffffff;
  box-shadow:0 0 5px #bfe6ff
}
.mob.seamine .mob-eyes i:last-child {
  left:auto; right:0; top:auto; bottom:2px
}

/* Tesla - podglad kafelka w EDYTORZE STRUKTUR (gra rysuje ja proceduralnie na
   canvasie: drawTesla). Ma tylko czytac sie jako metalowy trojkat z niebieskim
   rdzeniem - z natury nie odtwarza obrotu ani pioruna.
   ⚠️ Kolory 1:1 z TESLA_COLORS w app.js.
   ⚠️ .mob::before maluje zielony gradient na inset:0 - MUSI zostac nadpisane,
   inaczej cale tlo tesli jest zaslonione (ta sama pulapka co przy steel-worm
   i minie).
   ⚠️ Selektor rdzenia potrzebuje specyficznosci .mob.tesla (0,3,0) - reguly
   kierunkowe .mob-dir-... stoja NIZEJ w pliku i przy remisie by wygraly. */
.tesla {
  background:#7d8792 !important;
  border-color:#171a1e !important;
  border-radius:2px !important;
  clip-path:polygon(50% 0,100% 93%,0 93%)
}
.tesla::before {
  background:linear-gradient(160deg,#c3ccd6 0%,#7d8792 52%,#3e454e 100%) !important;
  box-shadow:inset 0 0 0 1px #171a1e, inset 0 0 8px #171a1ecc;
  clip-path:polygon(50% 0,100% 93%,0 93%)
}
/* rdzen - w grze rozjarza sie na jasnoniebiesko przed strzalem, w podgladzie
   swieci na stale */
.mob.tesla .mob-eyes {
  left:50%; top:58%;
  width:10px; height:10px;
  transform:translate(-50%,-50%)
}
.mob.tesla .mob-eyes i {
  left:0; right:0; top:0; bottom:0;
  width:auto; height:auto;
  border-radius:50%;
  background:#eaf9ff;
  box-shadow:0 0 6px #4fc3ff
}
.mob.tesla .mob-eyes i:last-child { display:none }
/* Blob - TYLKO podglad w edytorze struktur. Gre rysuje drawBlob na canvasie
   (falujaca, splaszczona o sciane bryla z macka), wiec ten kafelek z natury NIE
   odtwarza renderera - ma sie czytac jako jasna kropla zelu, a nie jako robak.
   Kolory 1:1 z BLOB_COLORS w app.js.
   UWAGA: bazowe .mob::before maluje ciemnozielony gradient na inset:0 - MUSI
   zostac nadpisane, inaczej jasna limonka jest w calosci zaslonieta (ta sama
   pulapka co przy stalowym robaku, pajaku, minie i tesli).
   UWAGA: selektor polysku potrzebuje specyficznosci .mob.blob (0,3,0) - reguly
   kierunkowe .mob-dir-... stoja NIZEJ w pliku i przy remisie by wygraly. */
.blob {
  background:#7ecb1f !important;
  border-color:#27590b !important;
  /* nierowne rogi - bryla zelu, a nie kwadrat */
  border-radius:58% 42% 46% 54% / 62% 56% 44% 38% !important
}
.blob::before {
  background:radial-gradient(circle at 34% 30%,#e2ff9c 0%,#a9ec3d 38%,#7ecb1f 66%,#3f7d12 100%) !important;
  box-shadow:inset 0 0 0 1px #27590b, inset 0 0 8px #27590bcc;
  border-radius:58% 42% 46% 54% / 62% 56% 44% 38%
}
/* mokry polysk zamiast oczu */
.mob.blob .mob-eyes {
  left:30%; top:28%;
  width:12px; height:9px;
  transform:translate(-50%,-50%)
}
.mob.blob .mob-eyes i {
  left:0; right:0; top:0; bottom:0;
  width:auto; height:auto;
  border-radius:52% 48% 44% 56% / 58% 62% 38% 42%;
  background:#e2ff9c;
  box-shadow:none;
  opacity:.85
}
.mob.blob .mob-eyes i:last-child { display:none }
.mob-fuel-bubble {
  position:absolute;
  z-index:2;
  left:50%;
  top:50%;
  width:12px;
  height:12px;
  transform:translate(-50%,-50%);
  border-radius:50%;
  background:radial-gradient(circle at 35% 30%,#fff36a 0 18%,#ffd400 35%,#b88900 100%);
  box-shadow:0 0 4px #ffd400, inset 0 0 3px #fff8;
  pointer-events:none
}
.mob-air-bubble {
  position:absolute;
  z-index:2;
  left:50%;
  top:50%;
  width:12px;
  height:12px;
  transform:translate(-50%,-50%);
  border-radius:50%;
  background:radial-gradient(circle at 35% 30%,#e7fbff 0 18%,#48cfff 35%,#08698f 100%);
  box-shadow:0 0 4px #48cfff, inset 0 0 3px #fff8;
  pointer-events:none
}
.mob-eyes i:last-child {
  top:10px
}
.mob-dir-right .mob-eyes {
  left:100%;
  top:50%;
  transform:translate(-9px,-50%)
}
.mob-dir-left .mob-eyes {
  left:0;
  top:50%;
  transform:translate(-5px,-50%) rotate(180deg)
}
.mob-dir-up .mob-eyes {
  left:50%;
  top:0;
  transform:translate(-50%,-5px) rotate(-90deg)
}
.mob-dir-down .mob-eyes {
  left:50%;
  top:100%;
  transform:translate(-50%,-9px) rotate(90deg)
}
.block {
  display:block;
  position:absolute;
  width:64px;
  height:64px;
  background-size:cover;
  background-repeat:no-repeat;
  background-position:center
}
.block>o,
.block>b,
.block>s,
.block>u {
  display:block;
  position:absolute;
  inset:0;
  background-size:cover;
  background-repeat:no-repeat;
  background-position:center;
  pointer-events:none
}
.block>o {
  z-index:0
}
.block>b {
  z-index:1
}
.block>s {
  z-index:2
}
.block>u {
  z-index:4;
  text-decoration:none
}
/* losowa (deterministyczna, seededNumber(x,y,'objRotate',4)), trwala rotacja
   samego obiektu (blask/element/cien) - NIE warstwy tla pod nim (kolor ziemi
   / void), stad osobny element <o> zamiast transformu na calym .block */
.block>o.rot90 {
  transform:rotate(90deg)
}
.block>o.rot180 {
  transform:rotate(180deg)
}
.block>o.rot270 {
  transform:rotate(270deg)
}
/* Dawniej pickupy w korytarzach robakow mialy wlasne, recznie sklejone sprite'y
   (cash-in-void.png / fuel-on-void.png / air-on-void.png) z wrysowanym obiektem,
   wiec <o> bylo tu gaszone, zeby obiekt sie nie zdublowal. Te pliki zniknely z
   assets, a reguly zostaly - blok tracil tlo (404) I obiekt, czyli renderowal sie
   jako przezroczysta dziura. Teraz te typy uzywaja generycznego .on-void jak lup
   z robaka: tlo to czysty void*.png, a obiekt rysuje normalnie <o>. */
.block {
  width:66px;
  height:66px;
  margin:-1px
}
.block[data-type="sky"] {
}
/* .block[data-type="void"] (tlo) - teraz w blocks.generated.css */
/* ⚠️ Musi pasowac tez do .on-void (hot/dekoracje/teleportbase2/mina w edytorze)
   - inaczej 44 "lisciowe" reguly narozne nizej w pliku (ktore ustawiaja tylko
   OFFSETY .void-corner-* / .void-outer-*) nie maja zadnego efektu, bo brakuje im
   bazowego display/position. To byla prawdziwa przyczyna braku zaokraglonych
   narozy przy strzalce/hot mimo poprawionych 44 regul - te dwie bazowe reguly
   zostaly wtedy pominiete. */
.block[data-type="void"]>i, .block.on-void>i {
  display:block;
  position:absolute;
  inset:0;
  z-index:3;
  pointer-events:none
}
.block[data-type="void"]>i>em, .block.on-void>i>em {
  display:block;
  position:absolute;
  width:16px;
  height:16px;
  background:red
}
.block[data-type="void"].r1 {
  background-image:url('assets/blocks/void2.png')
}
.block[data-type="void"].r2 {
  background-image:url('assets/blocks/void3.png')
}
.block[data-type="void"].r3 {
  background-image:url('assets/blocks/void4.png')
}
.block[data-type="void"].y4 {
  background-image:url('assets/blocks/void-top.png')
}
/* .block[data-type="dirt"] (tlo) - teraz w blocks.generated.css */
.block[data-type="dirt"].r1 {
  background-image:url('assets/blocks/dirt2.png')
}
.block[data-type="dirt"].r2 {
  background-image:url('assets/blocks/dirt3.png')
}
.block[data-type="dirt"].r3 {
  background-image:url('assets/blocks/dirt4.png')
}
.block[data-type="dirt"].y4 {
  background-image:url('assets/blocks/dirt-top.png')
}
/* Kolor ziemi zmienia sie co 200 blokow glebokosci (dirtColorFamilyAt w app.js):
   standard, e, d, c, b, a, standard... dc0 (standard) to reguly powyzej -
   dc1-dc5 to kolejne rodziny (w tej odwroconej kolejnosci liter), kazda
   z wlasnymi wariantami ksztaltu r0-r3. */
.block[data-type="dirt"].dc1 {
  background-image:url('assets/blocks/dirte.png')
}
.block[data-type="dirt"].dc1.r1 {
  background-image:url('assets/blocks/dirte2.png')
}
.block[data-type="dirt"].dc1.r2 {
  background-image:url('assets/blocks/dirte3.png')
}
.block[data-type="dirt"].dc1.r3 {
  background-image:url('assets/blocks/dirte4.png')
}
.block[data-type="dirt"].dc2 {
  background-image:url('assets/blocks/dirtd.png')
}
.block[data-type="dirt"].dc2.r1 {
  background-image:url('assets/blocks/dirtd2.png')
}
.block[data-type="dirt"].dc2.r2 {
  background-image:url('assets/blocks/dirtd3.png')
}
.block[data-type="dirt"].dc2.r3 {
  background-image:url('assets/blocks/dirtd4.png')
}
.block[data-type="dirt"].dc3 {
  background-image:url('assets/blocks/dirtc.png')
}
.block[data-type="dirt"].dc3.r1 {
  background-image:url('assets/blocks/dirtc2.png')
}
.block[data-type="dirt"].dc3.r2 {
  background-image:url('assets/blocks/dirtc3.png')
}
.block[data-type="dirt"].dc3.r3 {
  background-image:url('assets/blocks/dirtc4.png')
}
.block[data-type="dirt"].dc4 {
  background-image:url('assets/blocks/dirtb.png')
}
.block[data-type="dirt"].dc4.r1 {
  background-image:url('assets/blocks/dirtb2.png')
}
.block[data-type="dirt"].dc4.r2 {
  background-image:url('assets/blocks/dirtb3.png')
}
.block[data-type="dirt"].dc4.r3 {
  background-image:url('assets/blocks/dirtb4.png')
}
.block[data-type="dirt"].dc5 {
  background-image:url('assets/blocks/dirta.png')
}
.block[data-type="dirt"].dc5.r1 {
  background-image:url('assets/blocks/dirta2.png')
}
.block[data-type="dirt"].dc5.r2 {
  background-image:url('assets/blocks/dirta3.png')
}
.block[data-type="dirt"].dc5.r3 {
  background-image:url('assets/blocks/dirta4.png')
}
.block[data-type="rock"].y4 {
  background-image:url('assets/blocks/rock-top.png')
}
/* .in-cargo.{typ} (obrazek) dla wiekszosci typow - teraz w blocks.generated.css.
   Ponizej zostaja tylko wlasciwosci NIEZWIazane ze zrodlem obrazka (rozmiar,
   przezroczyste tlo, obwodka) - nie koliduja z regula z blocks.generated.css,
   bo dotycza innych wlasciwosci CSS. */
.in-cargo.bomb {
  background-size:contain;
  filter:drop-shadow(0 0 1px #fff)
}
.in-cargo.superbomb {
  background-size:contain;
  filter:drop-shadow(0 0 1px #fff)
}
.in-cargo.teleport {
  background-size:contain;
  background-color:transparent
}
.in-cargo.gas {
  background-size:contain;
  background-color:transparent
}
.in-cargo.boost {
  background-size:contain;
  background-color:transparent
}
.in-cargo.shield {
  background-size:contain;
  background-color:transparent
}
.in-cargo.stun {
  background-size:contain;
  background-color:transparent
}
.in-cargo.mutiny {
  background-size:contain;
  background-color:transparent
}
.in-cargo.nuke {
  background-size:contain;
  background-color:transparent
}
.in-cargo.decoy {
  background-size:contain;
  background-color:transparent
}
.in-cargo.locator {
  background-size:contain;
  background-color:transparent
}
.in-cargo.fogremover {
  background-size:contain;
  background-color:transparent
}
.in-cargo.dirt-monetizer {
  background-size:contain;
  background-color:transparent
}
.in-cargo.ore-sucker {
  background-size:contain;
  background-color:transparent
}
.in-cargo.cash {
  background-size:contain;
  background-color:transparent
}
.in-cargo.air {
  background-size:contain;
  background-color:transparent
}
.block[data-type="dirt"]::after {
  z-index:4;
  pointer-events:none
}
[data-type="sell"]::after {
  background-image:url(assets/bbuy.png)
}
[data-type="fuel"]::after {
  background-image:url(assets/bfuel.png)
}
[data-type="bombs"]::after {
  background-image:url(assets/bbomb.png)
}
[data-type="cargoupdate"]::after {
  background-image:url(assets/bupgrade.png)
}
[data-type="airupdate"]::after {
  background-image:url(assets/bair.png)
}
[data-type="fueltankupdate"]::after {
  background-image:url(assets/bfueltank.png)
}
[data-type="teleports"]::after {
  background-image:url(assets/bteleport.png)
}
[data-type="teleportbase"]::after {
  width:64px;
  height:64px;
  top:2px;
  left:0;
  background-image:url(assets/teleportbase.png);
  background-size:64px auto !important;
  background-position:bottom center;
  height:6px;
  top:auto;
  bottom:-2px
}
[data-type="engines"]::after {
  background-image:url(assets/bengine.png)
}
[data-type="fogs"]::after {
  background-image:url(assets/bfog.png)
}
[data-type="invest"]::after {
  background-image:url(assets/binvest.png)
}
/* Placeholder do czasu docelowych ikon - ta sama grafika co INVEST. */
[data-type="shieldupdate"]::after,
[data-type="repairs"]::after,
[data-type="quests"]::after {
  background-image:url(assets/bdefault.png)
}
.panel.fogs q,
.original-fogs q {
  background:#bbb
}
.panel.engine q,
.original-engines q {
  background:#ff9b22
}
/* ⚠️ Ciemny, przypalony pomarancz - musi byc wyraznie ciemniejszy od silnika
   (#ff9b22), bo oba znaczniki stoja w tej samej kolumnie paneli. */
.panel.shield q,
.original-shieldupdate q,
.original-repairs q {
  background:#b34700;
  color:#fff
}
.panel.fuel q,
.original-fueltankupdate q {
  background:#fff33b
}
.panel.air q,
.original-airupdate q {
  background:#55dfff
}
.panel.cargo q,
.original-cargoupdate q {
  background:#72fa55
}
.panel.cash q,
.original-invest q {
  background:#dda6ff
}
/* Stare, goloklasowe reguly (.cash/.air/.fuel/.engine/.pos, bez prefiksu
   .panel) - kazdy panel ma klase "panel X", wiec te selektory nadal trafiaja.
   Kiedys mialy tez wlasne "top:Npx" (jeszcze z ery position:absolute), ale
   przy tej samej specyficznosci co .panel{} wygrywaly przez kolejnosc w
   pliku i psuly odstepy w .side-panel (grid) - usuniete, zostaly tylko te
   wlasciwosci, ktorych .panel.X nigdzie nie ustawia (kolor/font tutaj,
   gradienty pasków nizej). Jesli znow zobaczysz "rozsypany" .side-panel,
   sprawdz najpierw czy ktos nie dopisal tu nowego "top"/"left"/"position". */
.cash {
  font-size:25px;
  text-align:center;
  color:yellow;
  letter-spacing:-2px
}
.block[data-type="sky"] {
}
/* .a-{typ} / .a-{typ}::after (obrazek) - teraz w blocks.generated.css (ten sam plik jako kolor i sylwetka) */
.done.shine {
  filter:drop-shadow(0 0 30px rgba(255,255,255,1)) drop-shadow(0 0 10px rgba(255,255,0,1)) drop-shadow(0 0 10px rgba(255,255,0,1));
  transition:filter 1s ease
}
/* Kontener boczny (logo/cash/air/fuel/engine/cargo) - jeden CSS Grid zamiast
   position:absolute+top na kazdym panelu osobno (dawne .game::before/::after
   + .panel.cash/air/fuel/engine/cargo{top:...}). Wyglad identyczny jak
   wczesniej - siatka wierszy odtwarza dokladnie te same odstepy pionowe
   (10px przed logo, logo 80px, 10px przerwy, cash/air/fuel/engine po 80px,
   cargo wypelnia reszte do dolu, tak jak dawne top:100/180/260/340/420+
   bottom:0). Puste wiersze 1 i 3 to dawne marginesy/przerwy - nic sie w nich
   nie renderuje.
*/
.side-panel {
  position:absolute;
  top:0;
  left:var(--minimap-offset,0px);
  width:192px;
  height:100%;
  background:url('assets/mn.png');
  background-position:right top;
  background-size:100% 100%;
  z-index:1;
  box-sizing:border-box;
  /* Faza 7 planu DOM->canvas: layout (grid, wiec pozycje elementow) dalej
     realnie sie liczy - visibility:hidden nie rusza layoutu, tylko malowanie
     - getBoundingClientRect() w drawSidePanels()/drawCargoPanel() (app.js)
     nadal zwraca poprawne wspolrzedne. Sam DOM jest tylko niewidoczny, bo
     wizualnie renderuje go teraz #ui-canvas. */
  visibility:hidden;
  /* 10px odstepu przed logo - dawne .game::after mialo top:10px */
  padding-top:4px;
  display:grid;
  /* cash/air/fuel/engine byly dawniej ulozone jeden pod drugim BEZ przerwy
     (top co rowno 80px = ich wlasna, naturalna wysokosc z paddingu i tresci,
     nie sztuczny odstep) - "auto" odtwarza to dokladnie, bez zgadywania
     konkretnej liczby pikseli. Jedyna prawdziwa przerwa (10px) jest miedzy
     logo a cash - stad pusty wiersz 2. Cargo jako jedyne mialo bottom:0,
     wiec dostaje 1fr (wypelnia reszte) i align-self:stretch nizej. */
  /* wiersze: logo, przerwa 10px, cash, air, fuel, shield, engine, cargo(1fr) */
  grid-template-rows:auto 10px auto auto auto auto auto 1fr;
  grid-template-columns:100%;
  row-gap:0;
  /* Jawne "start" zamiast domyslnego "normal" - defensywnie, zeby wiersze
     "auto" nigdy nie zostaly rozciagniete do wypelnienia calej wysokosci
     kontenera. Jedyny wiersz, ktory ma naprawde rosnac (cargo, 1fr), i tak
     to robi niezaleznie od tego ustawienia. */
  align-content:start
}
.in-cargo,
.cargo>p s {
  display:inline-block;
  vertical-align:top;
  width:13px;
  height:13px;
  border:0;
  margin:1px;
  background-position:center;
  background-repeat:no-repeat;
  background-size:39px
}
@keyframes gameStartReveal {
to {
  opacity:1
}
}
.info-color-default {
  color:white
}
.info-color-danger {
  color:#FF9E9E
}
.info-color-red {
  color:#f00
}
.info-color-lime {
  color:lime
}
.info-color-yellow {
  color:yellow
}

/* Joystick dotykowy - cztery ksztalty WPISANE WPROST w index.html (blok
   generowany przez node dev/split-joystick.js z assets/joy.svg), nie <img> -
   patrz komentarz w generatorze, dlaczego to musi byc inline SVG (stan
   wcisniecia + trafienie tylko w narysowany ksztalt, nie caly kwadrat 280x280).
   Pliki assets/joy-*.svg zostaja jako samodzielna kopia tego samego wygladu.
   ⚠️ Siedzi w .game-wrapper, NIE w .game - .game bywa skalowane transformem na
   mobile (CLAUDE.md 3 pkt 5), a joystick ma trzymac staly rozmiar i realny
   prawy dolny rog widocznego pudelka gry.
   ⚠️ z-index MUSI byc wyzszy niz 500 (#ui-canvas). .game ma position:relative
   bez z-indexu, wiec nie tworzy kontekstu ukladania i canvasy licza sie w tym
   samym stosie co joystick. */
/* ⚠️ bottom obnizone z 40px na 10px (na zyczenie: "joystick obniz o 30px").
   Kolka .joy-extra maja WLASNE bottom liczone od tej samej krawedzi - jesli
   znow bedziesz przesuwac krzyzak, sprawdz, czy nie zaczely na siebie wchodzic
   (patrz komentarz przy .joy-circle-tnt). */
.joystick {
  position:absolute;
  right:20px;
  bottom:10px;
  width:280px;
  height:280px;
  z-index:600;
  /* HUD dotykowy pomniejszony o 30% (na zyczenie), nadal przy prawej dolnej krawedzi:
     skalowanie wokol ROGU KONTENERA (right:20/bottom:10 od rogu), a nie wokol wlasnego
     rogu - dzieki temu krzyzak i luk kolek .joy-extra (skalowany wokol tego samego
     rogu) zachowuja wzajemny uklad. */
  transform:scale(.7);
  transform-origin:calc(100% + 20px) calc(100% + 10px)
}
/* Chowany z ekranu USTAWIENIA ('Pokazuj hud sterowania'). Domyslnie WIDOCZNY -
   flaga w JS startuje jako 1, zeby gra dzialala tak samo jak przed dodaniem
   przelacznika, dopoki gracz sam go nie wylaczy. */
.joystick.hidden {
  display:none
}
/* HUD "pod warstwa okna" pauzy/ustawien/statystyk (na zyczenie) - osobna
   klasa od .hidden (przelacznik gracza), zeby obie mogly obowiazywac
   niezaleznie bez nadpisywania sie nawzajem. Ustawiana w JS,
   syncJoystickInteractivity() w app.js, na podstawie gamePaused. */
.joystick.paused {
  display:none
}
/* Jedno okno z zakladkami (drawGameWindow, #ui-canvas z-index 500) MA nie
   chowac HUD-u dotykowego jak .paused - ma go zostawic widocznym, ale POD
   oknem (na zyczenie). Obnizenie z-index ponizej #ui-canvas + wylaczenie
   pointer-events daje dokladnie ten efekt bez ryzyka z-index:-1 (ktore
   wypchneloby HUD pod tlo .game-wrapper). Ustawiana w JS,
   syncJoystickInteractivity() w app.js, na podstawie gameWindowOpen. */
.joystick.under-window {
  z-index:400;
  pointer-events:none
}
/* Sklep i questy potrafia wyjechac w prawy dolny rog ekranu (pozycja liczona
   od kiosku + kamera, tylko przycinana do granic canvasu), czyli dokladnie
   tam, gdzie stoi joystick - a joystick lezy WYZEJ (z-index 600) niz
   #ui-canvas (500), ktory te menu obsluguje. Bez tego dotkniecie menu w tym
   rogu trafialoby w joystick zamiast w przycisk pod nim. Klase ustawia
   app.js, syncJoystickInteractivity() wolane co klatke z handleHeldKeys().
   ⚠️ DWIE reguly, nie jedna: samo pointer-events:none na `.joystick` NIE
   wystarcza, bo `.joy-btn path` ma WLASNA, jawna wartosc `visiblePainted`
   (patrz blok generowany nizej) - jawna deklaracja na potomku wygrywa z
   odziedziczonym "none" z przodka, wiec sciezki dalej lapalyby klikniecia.
   A samo wylaczenie sciezek NIE wystarcza odwrotnie - pusty <div class="joystick">
   (bez wlasnego pointer-events) sam w sobie jest pelnoprawnym celem trafienia
   na CALYM swoim boksie 280x280 (zwykly HTML-owy box, nie SVG - nie ma tu
   "visiblePainted", "auto" znaczy caly prostokat). Zweryfikowane: bez tej
   drugiej reguly klik nad joystickiem w trakcie otwartego sklepu trafial w
   `DIV.joystick`, nie w `#ui-canvas` pod spodem. `!important` na sciezkach,
   bo musi wygrac z konkretniejszymi regulami .joy-shape/.joy-hit ponizej
   (generowanymi, patrz dev/split-joystick.js). */
.joystick.menu-blocked {
  pointer-events:none
}
.joystick.menu-blocked .joy-btn path {
  pointer-events:none !important
}
/* ⚠️ pointer-events:none TUTAJ jest kluczowe. Kazdy przycisk to OSOBNY,
   nalozony <svg> 280x280 (nie <path> wewnatrz jednego wspolnego <svg>) - a dla
   elementu <svg> jako CSS-owego boksu "auto" trafia w CALY prostokat, nie
   tylko w narysowany ksztalt (w odroznieniu od <path>, gdzie "auto" = tylko
   pomalowany obszar). Bez tego KAZDE klikniecie w kwadrat 280x280 trafialo w
   OSTATNI <svg> w DOM (dzis .joy-left, bo lezy na wierzchu), niezaleznie od
   tego, w ktory kwadrant realnie kliknieto. pointer-events:auto na samym
   <path> (blok generowany nizej) przywraca trafianie tylko w pomalowany
   ksztalt i pozwala klikniecu "przebic sie" przez pusty obszar gornego <svg>
   do wlasciwego przycisku pod spodem. */
.joystick .joy-btn {
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  filter:drop-shadow(0 0 10px #000);
  transition:filter .1s ease;
  cursor:pointer;
  touch-action:none;
  -webkit-tap-highlight-color:transparent;
  pointer-events:none;
  /* Domyslne dla <svg> to overflow:hidden - bez tego niewidoczny margines
     klikalnosci (.joy-hit, HIT_PADDING_PX w dev/split-joystick.js) bylby
     przycinany do viewBoxa i de facto nie robilby nic poza jego krawedziami. */
  overflow:visible
}
/* Wcisniety przycisk: cien znika (pelne bialo idzie przez blok generowany
   nizej, razem z fill-opacity/stroke-opacity/stroke-width - patrz tam). */
.joystick .joy-btn.pressed {
  filter:none
}
/* >>>>> POCZATEK BLOKU GENEROWANEGO: joystick-styl */
.joystick .joy-btn path.joy-shape {
  fill:#fff;
  fill-opacity:0.25;
  stroke:#fff;
  stroke-opacity:0.55;
  stroke-width:0.334389;
  stroke-linejoin:round;
  stroke-linecap:round;
  pointer-events:visiblePainted;
  transition:fill-opacity .1s ease, stroke-opacity .1s ease
}
.joystick .joy-btn.pressed path.joy-shape {
  fill-opacity:1;
  stroke-opacity:1
}
.joystick .joy-btn path.joy-hit {
  fill:none;
  stroke:transparent;
  stroke-width:8.359737;
  stroke-linejoin:round;
  stroke-linecap:round;
  pointer-events:visiblePainted
}
.joy-circle {
  background:rgba(255,255,255,0.4375);
  border-color:rgba(255,255,255,0.6625)
}
.joy-circle.pressed {
  background:rgba(255,255,255,1);
  border-color:rgba(255,255,255,1)
}
/* <<<<< KONIEC BLOKU GENEROWANEGO: joystick-styl */
/* Cztery okragle przyciski NAD krzyzakiem (dynamit / bomba / powietrze /
   "multi" ze skrotami TNT-boost-superbomba-paliwo-teleport-bomba wokol
   odkurzacza rud).
   ⚠️ Wyglad (fill/border-color, stan .pressed) jest WYGENEROWANY wyzej razem
   z krzyzakiem (WYPELNIENIE_ALPHA/OBWODKA_ALPHA w dev/split-joystick.js) -
   tu siedzi WYLACZNIE layout, bo ten nie zalezy od tamtych stalych.
   ⚠️ Ulozone na LUKU wspolsrodkowym z krzyzakiem (na zyczenie) - kazdy
   `.joy-circle-*` ma WLASNE, policzone z gory `right`/`bottom` (nie flex +
   `gap` jak w poprzedniej, plaskiej wersji), bo pozycje na luku nie da sie
   wyrazic rownomiernym odstepem miedzy sasiadami w jednej osi. `.joy-extra`
   jest wiec czystym kontenerem pozycjonujacym (inset:0 - ta sama ramka co
   .game-wrapper, czyli ten sam uklad wspolrzednych right/bottom co .joystick
   ponizej), bez wlasnego rozmiaru/ukladu.
   Geometria (liczona raz, wpisana jako gotowe px, nie ma tu dynamicznego JS):
   srodek krzyzaka to (right:160, bottom:180) - polowa jego pudelka 280x280
   przy right:20/bottom:40. Wszystkie 4 przyciski leza w TEJ SAMEJ odleglosci
   R=240.58px od tego srodka (stala odleglosc od srodka = stala odleglosc od
   obrysu krzyzaka, bo obrys krzyzaka sam jest kolem), co 23.33° miedzy
   sasiadami - ale, na zyczenie, luk NIE JEST juz symetryczny wzgledem pionu:
   przesuniety o -5.625° (dynamit/bomba/powietrze/multi siedza pod katem
   -40.625/-17.29/+6.04/+29.375° od pionu), bo trzy jednoczesne ograniczenia
   nie daja sie spelnic przy symetrycznym rozstawie - patrz nizej.
   ⚠️ R, rozstaw katowy I przesuniecie sa rozwiazaniem UKLADU TRZECH ograniczen
   na raz, nie efektem "rownego odstepu" ani "wygladu": (1) odleglosc miedzy
   kolkiem a krzyzakiem = 6.6px (60% MNIEJ niz poprzednie 16.5px, na zyczenie)
   - to WLASNIE ono wymusza asymetrie: przy symetrycznym rozstawie i tak
   ciasnej odleglosci od krzyzaka, skrajne prawe kolko (multi) wyjezdzalo
   poza prawa krawedz .game-wrapper (ktora jest tuz za joystickiem, right:20
   - to NAJCIASNIEJSZE z trzech ograniczen), wiec caly luk trzeba bylo
   obrocic w lewo, zeby zmiescic multi, kosztem wiekszej odleglosci
   dynamitu od pionu (patrz jego kat -40.6° - najbardziej "wychylony");
   (2) odleglosc miedzy sasiednimi kolkami (srodek-srodek) = 97.3px, czyli
   72px (srednica) + 25.3px wolnej przestrzeni (10.3px poprzednie + 15px na
   zyczenie); (3) multi zostaje z ok. 6px zapasu od prawej krawedzi
   .game-wrapper. Zmiana KTOREGOKOLWIEK z tych trzech wymaga przeliczenia
   WSZYSTKICH czterech pozycji na nowo (patrz historia rozmowy/commitow za
   wzorem) - nie da sie przesunac jednego kolka bez naruszenia pozostalych. */
/* ⚠️ pointer-events:none TUTAJ jest krytyczne - dokladnie ta sama pulapka co
   .joystick .joy-btn (patrz komentarz tam). Odkad .joy-extra ulozylo kolka
   na luku (inset:0 - ta sama, PELNOEKRANOWA ramka co .game-wrapper, zeby
   right/bottom kolek liczyly sie od tych samych krawedzi co .joystick),
   PUSTY <div> bez wlasnego pointer-events domyslnie "auto" jest klikalny na
   CALYM swoim boksie - czyli na calym ekranie, WYZEJ (z-index:600) niz
   #ui-canvas (500). Efekt: KAZDE klikniecie w gre (ikony HUD, sklep, panel
   osiagniec, wszystko na #ui-canvas) trafialo w ten pusty div zamiast w
   canvas pod spodem - zgloszone jako "kliknięcie w [gorne] przyciski nie
   dziala". `.joy-circle` (nizej) dostaje wlasny, jawny pointer-events:auto,
   zeby kolka same zostaly klikalne mimo pointer-events:none na rodzicu. */
.joy-extra {
  position:absolute;
  inset:0;
  z-index:600;
  pointer-events:none;
  /* -30% (na zyczenie) wokol prawego dolnego rogu, tego samego co .joystick */
  transform:scale(.7);
  transform-origin:100% 100%
}
/* ⚠️ Wszystkie bottom obnizone o 40px na zyczenie (right bez zmian - to
   czysto pionowe przesuniecie calego luku w dol, ta sama tarcza/rozstaw,
   tylko nizej). Przy odleglosci kolko-krzyzak wczesniej ustalonej na 6.6px
   (patrz opis wyzej) to przesuniecie WPROWADZA NACHODZENIE - kolka i
   krzyzak zaczynaja sie nakladac o ok. 33px. Zrobione mimo to, bo o to
   wprost poproszono - jesli to nie byl zamierzony efekt, cofnij to
   przesuniecie albo zwieksz z powrotem odleglosc od krzyzaka. */
.joy-circle-tnt {
  right:280.6px;
  bottom:286.6px
}
.joy-circle-bomb {
  right:195.5px;
  bottom:333.7px
}
.joy-circle-air {
  right:98.7px;
  bottom:343.2px
}
.joy-circle-multi {
  right:6.0px;
  bottom:313.7px
}
.joy-extra.hidden {
  display:none
}
/* Ten sam powod co .joystick.paused wyzej (patrz komentarz tam). */
.joy-extra.paused {
  display:none
}
/* Ten sam powod co .joystick.under-window wyzej (patrz komentarz tam). */
.joy-extra.under-window {
  z-index:400;
  pointer-events:none
}
.joy-extra.under-window .joy-circle {
  pointer-events:none !important
}
/* Ten sam powod co .joystick.menu-blocked (patrz komentarz tam) - sklep/
   questy potrafia wyjechac w prawy dolny rog i te przyciski stoja w tym
   samym rogu co krzyzak. ⚠️ .joy-circle ma WLASNY, jawny pointer-events:auto
   (nizej) - dokladnie jak .joy-btn path w joysticku - wiec samo "none" na
   rodzicu go NIE zablokuje (jawna deklaracja na potomku wygrywa z
   odziedziczonym "none"). Stad DWIE reguly, nie jedna: `!important` na
   .joy-circle wymusza wylaczenie mimo jego wlasnego auto. */
.joy-extra.menu-blocked {
  pointer-events:none
}
.joy-extra.menu-blocked .joy-circle {
  pointer-events:none !important
}
.joy-circle {
  position:absolute;
  pointer-events:auto;
  box-sizing:border-box;
  width:72px;
  height:72px;
  border-radius:50%;
  /* ⚠️ border-width/style OSOBNO, NIE skrotem `border:1px solid` - ten
     skrot bez podanego koloru ustawia border-color na currentColor (czarny),
     a ta regula stoi w pliku PO generowanym bloku z border-color, wiec by go
     nadpisywala z powrotem na czarno. Zweryfikowane: z samym skrotem
     getComputedStyle().borderColor wracalo rgb(0,0,0) mimo generowanej
     regoly ustawiajacej rgba(255,255,255,ALPHA).
     ⚠️ CALY piksel (1px), NIE 1.5px - Chrome i tak zaokragla border-width
     do najblizszego calego piksela przy renderowaniu (zweryfikowane:
     1.5px dawalo getComputedStyle().borderWidth=="1px"), wiec 1.5px byloby
     tu myloce - wygladaloby na 1px, a OBWODKA_PX w dev/split-joystick.js
     (SVG, bez zaokraglania) musialby i tak celowac w 1px, zeby wyszlo to
     samo. Oba miejsca trzymaja sie wiec jawnie calego piksela. */
  border-width:1px;
  border-style:solid;
  display:flex;
  align-items:center;
  justify-content:center;
  filter:drop-shadow(0 0 10px #000);
  transition:filter .1s ease, background .1s ease, border-color .1s ease;
  cursor:pointer;
  touch-action:none;
  -webkit-tap-highlight-color:transparent
}
.joy-circle.pressed {
  filter:none
}
/* Slot bez przypisanego narzedzia (zakladka HUD) - przygaszony, zeby bylo
   widac ze nic sie nie stanie po dotknieciu. */
.joy-circle.empty {
  opacity:.35
}
.joy-circle-icon {
  width:40px;
  height:40px;
  object-fit:contain;
  pointer-events:none
}
/* Liczba posiadanych sztuk (dynamit/bomba/powietrze) w prawym dolnym rogu
   kolka - na zyczenie, "x" gdy zero. Tresc ustawia app.js
   (updateJoyCircleCounts(), wolane co klatke z handleHeldKeys() - te same
   liczniki co gra juz sprawdza przy uzyciu itemu: tntEls/bombEls dla
   dynamitu/bomby, countCargoItem('air') dla powietrza, bo to kanister z
   ladowni, nie poziom zbiornika). Kontur tekstu (text-shadow, cztery
   kierunki) zamiast pojedynczego cienia - liczba musi byc czytelna na
   dowolnym tle (jasna ikona, ciemna ikona, wypelnienie kolka). */
/* Kolejne +5px w gore, +5px w lewo na zyczenie (drugi raz - bylo juz
   przesuniete raz z 4/2 na 9/7) - "right"/"bottom" rosnie = element
   przesuwa sie odpowiednio w lewo/w gore (to odleglosci OD tych krawedzi). */
.joy-circle-count {
  position:absolute;
  right:14px;
  bottom:12px;
  font-size:13px;
  color:#fff;
  text-shadow:1px 1px 0 #000, -1px 1px 0 #000, 1px -1px 0 #000, -1px -1px 0 #000;
  pointer-events:none
}
/* ⚠️ Trzeci przycisk (multi) NIE MA jeszcze przypisanej akcji - na razie
   czysto informacyjny podglad ikon (TNT/boost/paliwo/teleport + odkurzacz),
   bez wlasnego listenera w app.js. `cursor:default` sygnalizuje to wizualnie,
   zeby nie wygladal jak klikalny bomba/powietrze obok. Jesli kiedys dostanie
   akcje, ten override trzeba zdjac. */
.joy-circle-multi {
  cursor:default
}
/* Trzeci przycisk: pomniejszona ikona odkurzacza rud na srodku (o 20%
   mniejsza niz wczesniej - 24px*0.8=19.2px) i SZESC mini-ikon dookola niej
   (TNT/boost/superbomba/paliwo/teleport/bomba - dwie ostatnie, superbomba
   i bomba, dolozone na zyczenie), kazda o 10% wieksza niz oryginalne 16px
   (16*1.1=17.6px). ⚠️ Bomba i superbomba dostaja TEN SAM rozmiar co
   pozostale cztery (na zyczenie), nie oryginalny 16px. */
.joy-circle-icon-center {
  width:19.2px;
  height:19.2px
}
.joy-mini {
  position:absolute;
  width:17.6px;
  height:17.6px;
  object-fit:contain;
  pointer-events:none
}
/* Szesc pozycji rozlozonych ROWNO co 60° na okregu o promieniu 22px od
   srodka kola (72x72, srodek 36,36) - kolejnosc katowa (zgodnie z ruchem
   wskazowek zegara od gory) odzwierciedla to, "miedzy czym a czym" mialy
   stanac nowe ikony: TNT(0°) - boost(60°) - superbomba(120°, miedzy boostem
   i paliwem) - paliwo(180°) - teleport(240°) - bomba(300°, miedzy
   teleportem i TNT) - z powrotem do TNT. Orientacja startowa (TNT na gorze)
   dowolna - nie ma znaczenia funkcjonalnego, wybrana dla symetrii z
   poprzednia wersja ukladu. Wspolrzedne policzone raz (left/top = srodek
   ikony - polowa jej boku), nie ma tu dynamicznego JS. */
.joy-mini-tnt {
  left:27.2px;
  top:5.2px
}
.joy-mini-boost {
  left:46.25px;
  top:16.2px
}
.joy-mini-superbomb {
  left:46.25px;
  top:38.2px
}
.joy-mini-fuel {
  left:27.2px;
  top:49.2px
}
.joy-mini-teleport {
  left:8.15px;
  top:38.2px
}
.joy-mini-bomb {
  left:8.15px;
  top:16.2px
}

/* ===================== APLIKACJA NATYWNA (ANDROID) =====================
   Klase ustawia native.js po wykryciu Capacitora - w przegladarce nie ma jej
   wcale, wiec te reguly nie dotykaja wersji web.
   overscroll-behavior gasi "odbicie" strony przy przeciagnieciu palcem po
   kanwie; .joystick/.joy-circle/#ui-canvas maja juz swoje touch-action:none. */
body.native-app {
  overflow:hidden;
  overscroll-behavior:none;
  -webkit-user-select:none;
  user-select:none;
  -webkit-touch-callout:none
}

/* ===================== SRODKOWY PRZYCISK KRZYZAKA =====================
   Kolko w srodku krzyzaka, wygladem identyczne z kolkami wyboru narzedzia
   (.joy-circle wyzej - te same tlo/obwodka/cien/stan .pressed). Odpowiada za
   POTWIERDZENIE wyboru, czyli to, co w wersji przegladarkowej robi Enter albo
   spacja: kupno w sklepie, oplacenie misji, wejscie w kiosk.
   ⚠️ Siedzi WEWNATRZ .joystick (nie w .joy-extra), zeby jechalo razem z
   krzyzakiem przy kazdej zmianie jego pozycji.
   ⚠️ left/top + translate zamiast right/bottom - pozostale kolka sa
   pozycjonowane od prawego dolnego rogu ekranu, a to ma byc w SRODKU krzyzaka.
   right/bottom:auto kasuje wartosci z regul .joy-circle-* dla pewnosci. */
.joy-circle-center {
  left:50%;
  top:50%;
  right:auto;
  bottom:auto;
  transform:translate(-50%,-50%);
  width:64px;
  height:64px;
  z-index:2
}
/* Kropka w srodku - czytelny znak "wybierz", bez odwolania do klawiatury
   (w wersji APK zadnych informacji o klawiszach). */
.joy-center-dot {
  display:block;
  width:22px;
  height:22px;
  border-radius:50%;
  background:rgba(255,255,255,0.85)
}
.joy-circle-center.pressed .joy-center-dot {
  background:#fff
}

/* Cztery kolka NAD krzyzakiem powiekszone o 10% (na zyczenie).
   ⚠️ scale(), a NIE zmiana width/height. Trzy powody:
   1) kolka sa pozycjonowane przez right/bottom, wiec wzrost pudelka przesunalby
      ich srodki - a maja zostac DOKLADNIE tam, gdzie sa;
   2) .joy-circle-multi ma w srodku szesc mini-ikon rozlozonych na okregu o
      promieniu liczonym od srodka pudelka 72x72 - zmiana rozmiaru rozjechalaby
      cala te szostke;
   3) scale powieksza tez ikone i obwodke, wiec przycisk rosnie jako calosc.
   ⚠️ Selektor obejmuje WYLACZNIE dzieci .joy-extra - srodkowy przycisk
   krzyzaka (.joy-circle-center) ma wlasny transform:translate() i scale tutaj
   by go skasowalo. */
.joy-extra .joy-circle {
  transform:scale(1.1);
  transform-origin:center
}

/* ===================== HUD DESKTOP: dwie kolumny kolek przy prawej krawedzi
   W przegladarce HUD dotykowy to juz NIE krzyzak + luk czterech kolek (te
   zostaja WYLACZNIE w aplikacji natywnej, gdzie nie ma klawiatury), tylko
   dwie kolumny okraglych przyciskow przy prawej krawedzi ekranu - po jednym
   na KAZDE narzedzie z cargoKeyItems, z ikona, licznikiem sztuk i klawiszem
   klawiatury w srodku kolka. Zawartosc buduje buildDesktopHud() w app.js.
   ⚠️ Kontener ma pointer-events:none (dokladnie ta sama pulapka co przy
   .joy-extra - pusty div z z-index:600 nad #ui-canvas lapalby klikniecia na
   calym swoim boksie); klikalne sa same kolka (.joy-circle ma jawne
   pointer-events:auto).
   ⚠️ Prawa krawedz jest wolna: pasek mapy przeniesiono na lewo
   (prawyPasekSzerokosc() zwraca dzis 0). Gdyby kiedys wrocil, ten HUD trzeba
   odsunac o jego szerokosc. */
/* ⚠️ JEDNA kolumna, wysokosc i rozmiar kolek liczy JS (na zyczenie:
   "wszystkie kolka w jednej kolumnie, zeby na gorze nie wchodzil na trwajace
   misje, a na dole na osiagniecia"). syncDesktopHudLayout() w app.js ustawia
   inline "top", "height" i zmienna "--joy-size"; wszystkie wymiary ponizej
   licza sie z niej przez calc(), zeby ikona, klawisz i licznik skalowaly sie
   RAZEM z kolkiem, bez drugiego miejsca do aktualizacji.
   Wartosci w regule sa awaryjne - obowiazuja do pierwszego przebiegu
   drawUiCanvas() (albo gdyby JS nie zdazyl policzyc pasa). */
.joy-desktop {
  --joy-size:50px;
  position:absolute;
  right:10px;
  top:80px;
  bottom:auto;
  height:auto;
  z-index:600;
  pointer-events:none;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:calc(var(--joy-size) * 0.12)
}
/* ⚠️ position:relative (nie absolute z .joy-circle) - w gridzie pozycje daje
   sam grid, a relative jest potrzebne licznikowi/klawiszowi w srodku. */
/* Wszystkie wymiary sa UTAMKAMI --joy-size, zeby skalowanie kolumny (patrz
   wyzej) nie wymagalo dotykania kazdej reguly osobno. Ulamki pochodza z
   ostatniego, recznie dobranego ukladu (kolko 67.6 px): ikona 40.56 = 0.6,
   klawisz 11.7 = 0.173, licznik 13 = 0.192.
   ⚠️ Zmiana width/height, a NIE transform:scale jak przy .joy-extra .joy-circle
   (patrz komentarz tam) - tamte kolka sa pozycjonowane recznie policzonym
   right/bottom na luku, wiec skala byla jedynym sposobem, zeby nie ruszyc
   srodkow; tutaj uklada je grid, ktory sam rezerwuje miejsce - scale
   zostawiloby stare komorki siatki i kolka zaczelyby na siebie nachodzic.
   ⚠️ Wysokosc calej kolumny: 8 wierszy * 67.6 + 7 * 8 gap = 596.8px.
   Powiekszajac dalej, sprawdz te sume - HUD jest wysrodkowany w pionie
   (translateY(-50%)), wiec w oknie nizszym niz ta suma zacznie sie przycinac
   u gory i u dolu. */
.joy-desktop .joy-circle {
  position:relative;
  flex:0 0 auto;
  width:var(--joy-size);
  height:var(--joy-size);
  flex-direction:column;
  gap:0
}
.joy-desktop .joy-circle-icon {
  width:calc(var(--joy-size) * 0.6);
  height:calc(var(--joy-size) * 0.6)
}
/* Klawisz klawiatury w dolnej czesci kolka - ten sam zolty co [KEY] w sklepie
   i w zakladce HUD na kanwie. */
/* ⚠️ font-family/font-size OSOBNO, NIE skrotem `font:` - skrot nie przyjmuje
   calc() w rozmiarze i cala deklaracja bylaby odrzucona. */
.joy-desk-key {
  font-family:Silkscreen, sans-serif;
  font-size:calc(var(--joy-size) * 0.346);
  font-weight:normal;
  color:#ffe066;
  line-height:1;
  text-shadow:1px 1px 0 #000, -1px 1px 0 #000, 1px -1px 0 #000, -1px -1px 0 #000;
  pointer-events:none
}
/* Znak klawisza SPACJI (pocisk lasera - jedyne narzedzie bez litery).
   Rysowany ramka, a nie napisem: Silkscreen nie ma glifu U+2423, wiec
   przegladarka spadlaby na inny kroj niz reszta HUD-u, a slowo SPACE nie
   miesci sie w kolku. Ksztalt jak na klawiaturze: dwie nozki i podstawa.
   Wymiary - ulamki --joy-size, tak samo jak reszta tego HUD-u. */
.joy-desk-key-space {
  box-sizing:border-box;
  width:calc(var(--joy-size) * 0.4);
  height:calc(var(--joy-size) * 0.16);
  border:calc(var(--joy-size) * 0.04) solid #ffe066;
  border-top:0;
  filter:drop-shadow(1px 1px 0 #000) drop-shadow(-1px -1px 0 #000)
}
/* Licznik sztuk wraca do GORNEGO prawego rogu - w tym kolku dolny rog zajmuje
   juz klawisz (patrz wyzej), a .joy-circle-count ma right/bottom policzone pod
   kolka 72px z luku nad krzyzakiem. */
.joy-desktop .joy-circle-count {
  right:calc(var(--joy-size) * 0.096);
  top:calc(var(--joy-size) * 0.038);
  bottom:auto;
  font-size:calc(var(--joy-size) * 0.384)
}
.joy-desktop.hidden {
  display:none
}
/* Kolko narzedzia, ktorego gracz nie ma - nie pokazujemy go wcale. */
.joy-desktop .joy-circle.gone {
  display:none
}
/* Te same trzy stany co .joy-extra wyzej (patrz komentarze tam). */
.joy-desktop.paused {
  display:none
}
.joy-desktop.under-window {
  z-index:400;
  pointer-events:none
}
.joy-desktop.under-window .joy-circle {
  pointer-events:none !important
}
.joy-desktop.menu-blocked {
  pointer-events:none
}
.joy-desktop.menu-blocked .joy-circle {
  pointer-events:none !important
}

/* Pole nazwy do najlepszych wynikow (hiscores). PRAWDZIWY <input> nad kanwa UI -
   klawiatura ekranowa na telefonie/APK otwiera sie tylko dla prawdziwego pola.
   Pozycje/rozmiar ustawia syncHiscoreInput() w app.js (piksele kanwy = CSS
   wewnatrz .game-wrapper). z-index nad #ui-canvas (500) i HUD-em (600);
   pointer-events jawnie, bo .game-wrapper ma none. */
.game-wrapper>.hiscore-name {
  position:absolute;
  z-index:700;
  box-sizing:border-box;
  margin:0;
  padding:0 8px;
  border:0;
  border-radius:3px;
  background:#000;
  color:#ffe066;
  font-family:'Silkscreen', sans-serif;
  outline:none;
  pointer-events:auto;
  user-select:text;
  -webkit-user-select:text;
}
