Files
prawicowy-dashboard/smolensk.html
T
2026-04-29 19:04:25 +02:00

2743 lines
124 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEJM KOMBAT: Suwerenność vs Kondominium</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#000;display:flex;justify-content:center;align-items:center;min-height:100vh;overflow:hidden;font-family:Arial,sans-serif}
canvas{display:block;image-rendering:auto;cursor:none}
</style>
</head>
<body>
<canvas id="gc" width="1280" height="720"></canvas>
<script>
/* ================================================================
SEJM KOMBAT: SUWERENNOŚĆ vs KONDOMINIUM
Pełnoprawna bijatyka 2D jeden plik HTML5 + Canvas + JS
Walka Polski suwerennej przeciwko kondominium rosyjsko-niemieckiemu!
================================================================ */
// ===================== CANVAS I KONTEKST =====================
const canvas = document.getElementById('gc');
const ctx = canvas.getContext('2d');
const W = 1280, H = 720;
const GROUND = 540;
const GRAVITY = 0.7;
// ===================== STANY GRY =====================
let gameState = 'MENU'; // MENU, SELECT, FIGHT, VICTORY, PAUSE
let selectCursor = {col:0, row:0, side:0}; // side: 0=good, 1=bad
let selectedPlayer = null;
let selectedEnemy = null;
let player1 = null;
let player2 = null;
let ai = null;
let roundTimer = 99;
let roundTimerAccum = 0;
let shakeX = 0, shakeY = 0;
let particles = [];
let floatingTexts = [];
let menuPropIndex = 0;
let menuPropTimer = 0;
let musicStarted = false;
let fightResult = ''; // 'P1WIN', 'P2WIN', 'DRAW'
let victoryTimer = 0;
let victorySpeechDone = false;
let enterCooldown = 0;
let pauseAvailable = true;
let lastPreviewedChar = null;
// ===================== LOSOWE WSPARCIE: JAN PAWEŁ II =====================
let jp2 = {
active: false,
img: null,
x: W / 2,
y: 120,
timer: 0,
duration: 420,
cooldown: 0,
nextSpawn: 180,
text: '',
introPulse: 0
};
// ===================== INTRO I EFEKTY BOSSÓW =====================
let bossIntro = {
active: false,
char: null,
timer: 0,
duration: 165,
announced: false
};
const BOSS_NAMES = ['Karol Nawrocki','Władimir Putin','Angela Merkel','Lech Kaczyński'];
// Odchudzona grafika Jana Pawła II osadzona bezpośrednio w HTML jako base64 WebP.
// Dzięki temu gra nie pobiera tej grafiki z internetu i działa także offline.
const JP2_BASE64_IMG = 'data:image/webp;base64,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';
// ===================== NAPISY PROPAGANDOWE =====================
const PROPAGANDA = [
"Polska suwerenna kontra lokaje Brukseli i Moskwy!",
"PiS broni Polski Platforma i Lewica sprzedają ją Niemcom i Putinowi!",
"Koniec z kolonialnym kondominium!",
"Suwerenność górą!",
"Zdrajcy narodu na kolana!",
"Polska niepodległa i suwerenna!",
"Kondominium rosyjsko-niemieckie musi upaść!",
"Bronimy granic, tradycji i wolności!",
"Platforma i Lewica to agenci obcych mocarstw!",
"Jeszcze Polska nie zginęła bo PiS czuwa!",
"Nie oddamy suwerenności Brukseli ani Moskwie!",
"500+ to siła polskich rodzin!",
"Tusk = Merkel + Putin matematyka zdrady!",
];
const HIT_GOOD = ["Suwerenność!","Polska górą!","Patriota uderza!","Za wolność!","Za Polskę!","Precz z kondominium!","Cios wolności!"];
const HIT_BAD = ["Zdrajca!","Niemiecka agenda!","Lokaj Brukseli!","Agent Moskwy!","Sługa Ursuli!","Pachołek!","Kolaborant!"];
const SPECIAL_GOOD_TXT = ["USTAWA O SUWERENNOŚCI!","MARSZ NIEPODLEGŁOŚCI!","500+ UPPERCUT!","PRAWDA HISTORYCZNA!","SMOLEŃSK STRIKE!"];
const SPECIAL_BAD_TXT = ["BRUKSELKA LASER!","MERKEL KISS OF DEATH!","GAZPROM STRIKE!","PUTIN BEAR HUG!","VON DER LEYEN TAX!"];
const VICTORY_GOOD = [
"Polska suwerenna zwyciężyła! Kondominium w odwrocie!",
"Lokaj Brukseli/Moskwy pokonany!",
"Suwerenność obroniona!",
"Patriota zwycięża Polska wolna!",
"Koniec z kolonialnym jarzmem!",
];
const VICTORY_BAD = [
"Kondominium tymczasowo górą... ale Polska się nie podda!",
"Zdrajcy wygrali bitwę, ale nie wojnę!",
"Ciemna noc nad Polską... ale świt nadejdzie!",
];
// ===================== DANE POSTACI =====================
// Frakcja DOBRA PiS i patrioci obrońcy suwerenności
const GOOD_CHARS = [
{name:"Jarosław Kaczyński", nick:"Prezes", sejmId:196, special:"Cios Prezesa", hp:110, atk:14, def:9, spd:5, color:"#1e3a8a", fatality:"Dekret Suwerenności"},
{name:"Mateusz Morawiecki", nick:"Premier", sejmId:287, special:"500+ Uppercut", hp:105, atk:12, def:8, spd:7, color:"#1e40af", fatality:"Tarcza Antyinflacyjna"},
{name:"Mariusz Błaszczak", nick:"Minister Obrony", sejmId:25, special:"Marsz Niepodległości", hp:115, atk:13, def:10, spd:5, color:"#1d4ed8", fatality:"Mobilizacja Patriotów"},
{name:"Antoni Macierewicz", nick:"Komisja Prawdy", sejmId:270, special:"Prawda o Smoleńsku", hp:100, atk:15, def:7, spd:4, color:"#2563eb", fatality:"Raport Końcowy"},
{name:"Zbigniew Ziobro", nick:"Twarda Ręka", sejmId:456, special:"Ustawa o Suwerenności", hp:100, atk:14, def:8, spd:6, color:"#3b82f6", fatality:"Prokuratorski Cios"},
{name:"Jacek Sasin", nick:"Pan Jacek", sejmId:363, special:"Kopertowy Cios", hp:100, atk:11, def:7, spd:7, color:"#1e3a8a", fatality:"Głosowanie Korespondencyjne"},
{name:"Elżbieta Witek", nick:"Pani Marszałek", sejmId:438, special:"Młotek Marszałka", hp:95, atk:10, def:9, spd:8, color:"#1d4ed8", fatality:"Zamknięcie Obrad"},
{name:"Przemysław Czarnek", nick:"Min. Edukacji", sejmId:63, special:"Lekcja Patriotyzmu", hp:105, atk:13, def:8, spd:6, color:"#2563eb", fatality:"Reforma Edukacji"},
{name:"Ryszard Terlecki", nick:"Twardziel", sejmId:414, special:"Bicz na Lewicę", hp:100, atk:12, def:9, spd:5, color:"#3b82f6", fatality:"Dyscyplina Klubowa"},
{name:"Joanna Lichocka", nick:"Gest Prawdy", sejmId:252, special:"Palec Sprawiedliwości", hp:90, atk:11, def:7, spd:9, color:"#1e40af", fatality:"Środkowy Gest"},
{name:"Marek Kuchciński", nick:"Pilot Sejmu", sejmId:207, special:"Lot Patrioty", hp:100, atk:10, def:8, spd:7, color:"#1e3a8a", fatality:"Rządowy Samolot"},
{name:"Marcin Horała", nick:"Pan CPK", sejmId:155, special:"Megalotnisko", hp:100, atk:11, def:7, spd:8, color:"#1d4ed8", fatality:"Centralny Port"},
{name:"Andrzej Adamczyk", nick:"Budowniczy", sejmId:1, special:"Autostrada Wolności", hp:105, atk:10, def:9, spd:6, color:"#2563eb", fatality:"Via Suwerena"},
{name:"Barbara Bartuś", nick:"Pani Barbara", sejmId:14, special:"Cios Małopolski", hp:90, atk:10, def:8, spd:8, color:"#3b82f6", fatality:"Góralski Prym"},
{name:"Marek Ast", nick:"Strażnik Prawa", sejmId:9, special:"Paragraf Wolności", hp:100, atk:11, def:9, spd:6, color:"#1e40af", fatality:"Artykuł Suwerenności"},
];
// Frakcja ZŁA KO i Lewica zdrajcy, słudzy Merkel i Putina
const BAD_CHARS = [
{name:"Donald Tusk", nick:"Lokaj Brukseli", sejmId:421, special:"Brukselka Laser", hp:105, atk:12, def:8, spd:7, color:"#dc2626", fatality:"Eurorozkaz"},
{name:"Borys Budka", nick:"Burzycel", sejmId:37, special:"Demolka Konstytucji", hp:100, atk:11, def:7, spd:8, color:"#b91c1c", fatality:"Wniosek o Wotum"},
{name:"Włodzimierz Czarzasty", nick:"Czerwony Włodek", sejmId:70, special:"Komunistyczny Uścisk", hp:100, atk:12, def:8, spd:6, color:"#991b1b", fatality:"Powrót Komuny"},
{name:"Grzegorz Schetyna", nick:"Knur Platformy", sejmId:370, special:"Cios Aparatczyka", hp:105, atk:13, def:9, spd:5, color:"#7f1d1d", fatality:"Partyjny Spisek"},
{name:"Ewa Kopacz", nick:"Pani Kopacz", sejmId:222, special:"Kopniak Zdrajczyni", hp:95, atk:10, def:7, spd:8, color:"#dc2626", fatality:"Ekshumacja Prawdy"},
{name:"Barbara Nowacka", nick:"Nowaczka", sejmId:310, special:"Gender Laser", hp:90, atk:11, def:6, spd:9, color:"#b91c1c", fatality:"Ideologia w Szkołach"},
{name:"Dariusz Joński", nick:"Kontroler", sejmId:170, special:"Fałszywy Audyt", hp:100, atk:10, def:8, spd:7, color:"#991b1b", fatality:"Raport Kłamstw"},
{name:"Izabela Leszczyna", nick:"Leszczynka", sejmId:248, special:"Podatek von der Leyen", hp:95, atk:11, def:7, spd:8, color:"#7f1d1d", fatality:"Składka Zdrowotna"},
{name:"Tomasz Siemoniak", nick:"Agent Platformy", sejmId:380, special:"Szpiegowski Cios", hp:105, atk:12, def:9, spd:6, color:"#dc2626", fatality:"Tajna Teczka"},
{name:"Adam Szłapka", nick:"Szłapa", sejmId:397, special:"Unijny Przytup", hp:95, atk:10, def:7, spd:9, color:"#b91c1c", fatality:"Brukselski Dyktando"},
{name:"Krzysztof Gawkowski", nick:"Cyfrowy Zdrajca", sejmId:134, special:"Cenzura Internetu", hp:100, atk:11, def:8, spd:7, color:"#991b1b", fatality:"Cyfrowa Inwigilacja"},
{name:"Agnieszka Dziemianowicz-Bąk", nick:"Bąk Lewicy", sejmId:98, special:"Socjalistyczne Żądło", hp:90, atk:10, def:6, spd:9, color:"#7f1d1d", fatality:"Lewicowa Utopia"},
{name:"Urszula Augustyn", nick:"Pani Ula", sejmId:10, special:"Brukselski Uścisk", hp:95, atk:10, def:7, spd:8, color:"#dc2626", fatality:"List do Brukseli"},
{name:"Piotr Adamowicz", nick:"Brat Platformy", sejmId:2, special:"Gdański Cios", hp:100, atk:11, def:8, spd:7, color:"#b91c1c", fatality:"Gdański Wiatr"},
{name:"Bartosz Arłukowicz", nick:"Doktor Zdrada", sejmId:8, special:"Recepta na Zdradę", hp:95, atk:10, def:7, spd:8, color:"#991b1b", fatality:"Dawka Kondominium"},
];
// Postacie specjalne LEGENDY i BOSSY
const SPECIAL_GOOD = [
{name:"Karol Nawrocki", nick:"Prezydent RP", type:"LEGENDA",
photoUrl:"https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Karol_Nawrocki_-_prezydent.jpg/220px-Karol_Nawrocki_-_prezydent.jpg",
special:"Prawda Historyczna", hp:140, atk:16, def:12, spd:8, color:"#fbbf24",
fatality:"Dekret Prezydencki Polska Niepodległa Na Wieki!", isSpecial:true},
{name:"Lech Kaczyński", nick:"Bohater Smoleński", type:"LEGENDA",
photoUrl:"https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Lech_Kaczynski_by_Kubik_02.JPG/220px-Lech_Kaczynski_by_Kubik_02.JPG",
special:"Smoleńsk Strike", hp:150, atk:17, def:13, spd:7, color:"#f59e0b",
fatality:"Duch Smoleńska Prawda Zwycięża!", isSpecial:true},
];
const SPECIAL_BAD = [
{name:"Angela Merkel", nick:"Władczyni UE", type:"BOSS",
photoUrl:"https://commons.wikimedia.org/wiki/Special:Redirect/file/Angela%20Merkel.%20Tallinn%20Digital%20Summit.jpg?width=220",
altPhotoUrls:["https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Angela_Merkel._Tallinn_Digital_Summit.jpg/220px-Angela_Merkel._Tallinn_Digital_Summit.jpg"],
special:"Merkel Kiss of Death", hp:160, atk:16, def:14, spd:6, color:"#facc15",
fatality:"Czwarty Rzesza Unia pod Berlinem!", isSpecial:true},
{name:"Władimir Putin", nick:"Car Rosji", type:"ULTIMATE BOSS",
photoUrl:"https://commons.wikimedia.org/wiki/Special:Redirect/file/Vladimir%20Putin%202020.jpg?width=220",
altPhotoUrls:["https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Vladimir_Putin_2020.jpg/220px-Vladimir_Putin_2020.jpg"],
special:"Putin Bear Hug", hp:180, atk:18, def:12, spd:7, color:"#ef4444",
fatality:"Gazprom Dominacja Kondominium Totalne!", isSpecial:true},
];
// Łączymy dane do łatwego dostępu
const ALL_GOOD = [...GOOD_CHARS, ...SPECIAL_GOOD];
const ALL_BAD = [...BAD_CHARS, ...SPECIAL_BAD];
// Przygotuj zdjęcia, zapasowe portrety i opisy postaci
// WAŻNE: nie używamy już starych, ręcznie wpisanych numerów sejmId jako źródła prawdy,
// bo identyfikatory posłów różnią się między kadencjami i potrafiły zwracać zdjęcie innej osoby.
const SEJM_TERMS_TO_SEARCH = [10, 9, 8, 7];
const sejmMpCache = {};
const PARTY_BY_NAME = {
"Jarosław Kaczyński":"Prawo i Sprawiedliwość", "Mateusz Morawiecki":"Prawo i Sprawiedliwość", "Mariusz Błaszczak":"Prawo i Sprawiedliwość",
"Antoni Macierewicz":"Prawo i Sprawiedliwość", "Zbigniew Ziobro":"Suwerenna Polska / Zjednoczona Prawica", "Jacek Sasin":"Prawo i Sprawiedliwość",
"Elżbieta Witek":"Prawo i Sprawiedliwość", "Przemysław Czarnek":"Prawo i Sprawiedliwość", "Ryszard Terlecki":"Prawo i Sprawiedliwość",
"Joanna Lichocka":"Prawo i Sprawiedliwość", "Marek Kuchciński":"Prawo i Sprawiedliwość", "Marcin Horała":"Prawo i Sprawiedliwość",
"Andrzej Adamczyk":"Prawo i Sprawiedliwość", "Barbara Bartuś":"Prawo i Sprawiedliwość", "Marek Ast":"Prawo i Sprawiedliwość",
"Karol Nawrocki":"postać specjalna obozu patriotycznego", "Lech Kaczyński":"Prawo i Sprawiedliwość",
"Donald Tusk":"Platforma Obywatelska / Koalicja Obywatelska", "Borys Budka":"Platforma Obywatelska / Koalicja Obywatelska",
"Włodzimierz Czarzasty":"Nowa Lewica", "Grzegorz Schetyna":"Platforma Obywatelska / Koalicja Obywatelska", "Ewa Kopacz":"Platforma Obywatelska / Koalicja Obywatelska",
"Barbara Nowacka":"Inicjatywa Polska / Koalicja Obywatelska", "Dariusz Joński":"Koalicja Obywatelska", "Izabela Leszczyna":"Platforma Obywatelska / Koalicja Obywatelska",
"Tomasz Siemoniak":"Platforma Obywatelska / Koalicja Obywatelska", "Adam Szłapka":"Nowoczesna / Koalicja Obywatelska", "Krzysztof Gawkowski":"Nowa Lewica",
"Agnieszka Dziemianowicz-Bąk":"Nowa Lewica", "Urszula Augustyn":"Platforma Obywatelska / Koalicja Obywatelska", "Piotr Adamowicz":"Koalicja Obywatelska",
"Bartosz Arłukowicz":"Platforma Obywatelska / Koalicja Obywatelska", "Angela Merkel":"CDU, Niemcy", "Władimir Putin":"władze Federacji Rosyjskiej"
};
function normName(s) {
return (s || '')
.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.replace(/ł/g, 'l')
.replace(/[^a-z\s-]/g, '')
.replace(/\s+/g, ' ')
.trim();
}
function makeFallbackPortrait(charData) {
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="220" viewBox="0 0 220 220">
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="${charData.faction === 'good' ? '#1e40af' : '#991b1b'}"/>
<stop offset="100%" stop-color="${charData.faction === 'good' ? '#60a5fa' : '#ef4444'}"/>
</linearGradient></defs>
<rect width="220" height="220" fill="url(#g)"/>
<circle cx="110" cy="82" r="46" fill="#fcd34d"/>
<rect x="52" y="130" width="116" height="90" rx="22" fill="${charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d'}"/>
<text x="110" y="92" text-anchor="middle" font-family="Arial" font-size="36" font-weight="700" fill="#111">${charData.initials}</text>
<text x="110" y="188" text-anchor="middle" font-family="Arial" font-size="16" font-weight="700" fill="#fff">${charData.nick}</text>
</svg>`;
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
}
function makeCharacterBio(c) {
const party = PARTY_BY_NAME[c.name] || (c.faction === 'good' ? 'obóz prawicy' : 'opozycja w grze');
if (c.name === 'Angela Merkel') return `${c.name}. Była kanclerz Niemiec i specjalny boss w grze. Reprezentuje niemiecką politykę europejską. Jej atak specjalny to ${c.special}.`;
if (c.name === 'Władimir Putin') return `${c.name}. Przywódca Rosji i finałowy boss. W grze symbolizuje presję ze Wschodu. Jego atak specjalny to ${c.special}.`;
if (c.name === 'Lech Kaczyński') return `${c.name}. Były prezydent Rzeczypospolitej Polskiej, związany z obozem Prawa i Sprawiedliwości. W grze występuje jako legenda, a jego atak specjalny to ${c.special}.`;
if (c.name === 'Karol Nawrocki') return `${c.name}. Historyk i postać specjalna w grze. Reprezentuje motyw pamięci historycznej i państwowości. Jego atak specjalny to ${c.special}.`;
const factionText = c.faction === 'good' ? 'W tej grze stoi po stronie obrońców suwerenności.' : 'W tej grze walczy po stronie kondominium.';
return `${c.name}. Polityk związany z: ${party}. ${factionText} Pseudonim wojownika: ${c.nick}. Atak specjalny: ${c.special}.`;
}
function prepareCharacter(c, faction) {
c.initials = c.name.split(' ').map(w=>w[0]).join('');
c.faction = faction;
c.photo = null;
c.photoLoaded = false;
c.photoUrls = [];
if (c.photoUrl) c.photoUrls.push(c.photoUrl);
if (Array.isArray(c.altPhotoUrls)) c.photoUrls.push(...c.altPhotoUrls);
c.fallbackPhotoUrl = makeFallbackPortrait(c);
c.bio = makeCharacterBio(c);
}
ALL_GOOD.forEach(c => prepareCharacter(c, 'good'));
ALL_BAD.forEach(c => prepareCharacter(c, 'bad'));
const EMBEDDED_BOSS_PHOTOS = {
'Karol Nawrocki': 'data:image/webp;base64,UklGRu4KAABXRUJQVlA4IOIKAAAQRwCdASqqAKoAPp1EnEolo6KrqzHsIXATiWUnWqEBUhUllOct2h3T8h9LXlCPszYPs3N/bK3znO7w8kag4gPrm/crKgT6Epyx5ZiSE9oiELs7Oqu8X+Yf9tfppaRueLUSaE/lkxhoIV1MErxx7LW8djAA5k6Xzo31qaLEetMI+JLx4XHqLbkMhKzYxtpvlF6KnHSJmswcpMs73rKcpP6PJ3HLrGCjnpJWTwPrs4lfKq4D41FTt2XkN7RyJyDO4XV+iOltsv2t0P3it7IhUhg0ZBCaiHw8vBPv8Crsq0MpBG+DtxhNq8Ck5wbhH+lNBEVUbXQvY5dRKiugEIUN5GoLkRMQrGMFO/BTxerkndoQHLWQaNzC3KsyJo+BOyeUKXx5XVynr5OJYy8hO4Q9SG4SqSpOt1mVrXwtlrt4RbjSTzkAvfFCEZdkxBHslvcZnuJ+88iOLLz8nu/DMl0GeFqWdi10Exw68/pPnt/hRliPkO2IlhVDa0L3HFqsDRubsbXXDvMN10TLf9MNtaZ9f8jA1fpJBMxCMUwAD27dkWWCDJemEK5Ud1sCxddm6GIxOmpCUjupaGHpvaiNBukXfEVrMJMvP7htgTD3snVl49zLwZoq5ezpNz9yKg0hsa2TMtwPdWOY95u7Slr6JpsF7K7BWUKAbISgzWysq+SdfmC+UlcFWAropihbcQlMB4vjyDHM3jMWoRh5S1LEG/F7tStGJFkeXM/GlzD36G6Xnk4SKifJzXlzHLKLVOzyzHr1VBIAAP74wXgk/Lp8CagZyaQJ2CNzQt3uy2TT37kAt+Lk+znous3+bnDFZm4DQl/K0Xu2clKyR2XbCu9G8WqfHLuLwFdUl26xrkm+vU5QVyq2F92QkxO3Z2RpQcr4YxyXatSUyo6w154vndNrVKif0v4xADpWwcYHXVKk5NKOQ2uHLbqoZ0DbN8tmvwzdaWY5FSR+64t4sdMM+ERjiMR/81qJxNPZW51Nflip7qTQ4zIUnnmBPZq4zePohKUeMVpQAgl4Uxre7i7sHleKEK0Zylgn2A4nNwbrgzeYoj/T0IOGlE0zsIkKZKjYfCrEcKisxPESWN0sPNQNRLzHFgxAfRIqDnkM8gv9OpC0415DIC3bleNTsq1SyCDv+5+60C8ChKsytotW4iqOHs+bw51BDsG27t3udTe81x5oXIMLwi+6hU36L1OYwm3TOR1CfZhiqdqN9ZajJi1BkquOrBToNYSbHU5w+AYUDrz3APeWEvLf/UmTC+tlHBr28medHv8UruhyETZMxAa5AqjDzaBwB8eX4YhJ56tya/3bxt8zsdgcCMsaOrMSNNphGxF+D6dF1HImFXH5yYMtCTmpdpd57/YWNeHS9e3flRMucvzdhVPjdK6TJVu2guUoF/QsPdxnEh8tYQGuwXV/j46yEVYQlPHFr8hfsy+Y3InUUgtNWJSi0nwyl2NIQ5adlYFyWXW9qv29Z9OlQ3oRg5q3Elc8vuGAG4IaDFjmk43KP9z6J49gIK24yUsmATNM3L64vPb+7Uq8G0HUwQJHh4iJUV5zwh2Myuvy1iQaJVzzHU1GFsRNlXlvcArETRCX9X1EY/sxhW20qzPMt/xEQ9ZyD5veJOY05QzpgCc8GnS0IGftF+dHjrYfYnHff0YGlc+KPtZcd6m1eEu4A1pN/zcJGJAsyfz8O8jsrCzDGxQHFSkbFT7aId6QH+I+D0ylwEBmzO8tIocYz9DJfngnkDt4qRS+fNgXSDXAaNSL5Q+useCpXRaQaQ86G5CI0QyQA7RLmC9OeyleEEuae0JsPyVZXr08fmxwno/Sqx5eH96EySEWdNhDoMVsNBSHUHpbynUBwZL/Hl4RKahsScxUGyf8nmeAl6QhRJdfo/lyqm9gKlPYHRz2933WCJXEiaUQNL0nN3y0cOY1InSF3CzM/QV9MCgm4SxyEAX7o3Y98X4xRn1Au/h6U6UI0N5CmNYrGEyfuJ/Y3O/bbRSOCJQZQpUq7j8lUb5n6q/b2SWNrjhg+awyz3sc1vSM+A6+A3cRVV3cPHB87zJHM55WS9JmGudPG77LmKHWKg3Bp7ztEkqGzKVHIXc7x/nsiUJXlh7VnX8LeCu6IwVyM+n7QiVm6k5kyjuEjCGZH5F2H3IoFSEg7qMSW98BkTccwlVwanSXZ2+tirgvjSwel9jEZLRmgHGdN5HprTE5z5+ANV+PuJQ1Ep71B7Gu+4qcuqJ/i0y0HwI1pIg+V1FH1/gcF9RLyIrz+7En2KfejBaGL7YMEpxkD38R3JdIfKpO81tBTC8HEGM2XuB7/bg+VzRE56rWwWVDnOSTf8Y4IgwxHo4F2nG//gExlsENGlCDWbgvk12qQ3JHkSAxi9Dip4Apz1hmUCvxPSTaBvm8gFH87NLucYaGUHzxKIW8xRA9hlFVpQn3U3sYUYBz0l0DN+jM0sYryueIIqKT2VJiWPdQ6mbTpVOet/1G5/i9Fd3zC3fjHcZb3fFQcfo90cSbHJw8Z1xVkcDIlPf6d69SobBTJ2or/AmhtNmF5Wk+DGdjKP5QgCr2idQ9UVqe+m0/A1y/iz7YmctEVo6UFoZAAqR2q8axejBxz980eKZOhobO7jfSHG3qnjKj72/QDdCBpGAtW5s3QKUO4Y8IIEH22dvboPfWJNu1/OJIR63muqDZW32eaQ8R0bruXMlSwHXgmxlkJKNS4ijO7CMT2dJeeeyvJca31/By/w3c/447BAHBeij8k+K/gglTYA6X7qzwAfTFJDw6iq2/bUgvModDEkI98NH65qkXGLpKOt1EZVNS2ZjztBiNxz9+SEG9KWwb65LUwQBUHn+m6/sp45d6xHglHcLDXqnOKS7d3pKufdVx3aJj1iQ13FDtYcX1MmrlpfwwyS/nSi3IY1/kSUvxawAseX6Jez+QhCBEZgR0VPmW8ka8A7PKejMd6GYLBJpGhLyVbvvoJffdLDsgVZ2dwLnTeYsufOg8N0Qydar8wCmT5LAHlslng0683vSWz23A5KFmKdZbKlJdhsrm+XUCLbzc3yaFVG7AwzD9CaZQa1VIt04V3faVAMzTv0JUVrtRolK0L7fS1xOk9a4RfsjP4cB5inFawFBrMTnqDfHKO/zft6DQneZEvr4azv4gm6AE2Qms0E/4kYc7BgG5vmPwOCXOv/+zp89EsdE1ZjQ6jzrjb/w0KnXmqlnEvxLPS5mNWhgCyGA7TYdxYUDTFczZlQqZzkM6M3/ygzSA5DRSUUsxk6vdEsivj8M8ucQKFZqLCYI+5yPFEUAHK2qqe7ptn7oV1Cv8jERmW1+c63z1R76/59XpGKoQ/z9SAE4vzTFYqnnRBe0z8oK9am+rcXOg7IS3srrJcy+ZcqqTK9Wi/jSnateexo48bK4pdsnIlaVZs4j7ftv75hAjzl89TaBJl4IA3RKS6d2tDasI4vTDC6GPE5mwrG9/w7vu7K7j79GpyaMd75RMQ3Z5kapbWQAzV4nb8dRjepjrqSYMyc/a/kd2lyjhWGkV7qMOsFu9NPYAGcuoKULiAykALQrpIFZOqVSu76e+jf28vCU34gLGh1+kGmI9axWxTnMcPPa4b7lwNnwdp2jFFzP6/I+IVqUdYD4hTttJLymlIkDIn5PMmsORI7307j++izB4QCDqmFeICUW8D6xrGC9HnbEUAzcg1J5VwUOtdNr9xKm8z1NlLVwbaiO/+zPJi0QwGhqKygAAAA==',
'Władimir Putin': 'data:image/webp;base64,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',
'Angela Merkel': 'data:image/webp;base64,UklGRioNAABXRUJQVlA4IB4NAABQTgCdASqqAKoAPp1Cm0mlo6IkKhKNwLATiWMAvnwh15II0gRNKq3bR/sG6mL3mMyp+x6VoSKLROl4nJ28cXmPCeoqI+hrMRxavqU4SHNlv6mx+GkPFCAho2nYShDKwU+6kUs1sQmHuq+ZPaBITdhl+mDyPPM2EUt+LeIhzgSzsvN3+I8QTInsIWEP6cZQSGCZy5Ncd31lgxfXZSvw5eSZKNq5tYfuvJ/58F/xJpLHBjyGS6elrLvSqfNC+hOGT7Ad/2HTN0BOa4D2qItm06j83dim/+9SDCsDfPU3GDYZtoEzauoD95FlhFDR7slRla8yFTiU0cFALX4u7kR1YGqxCDa0GmfhqOrxFKwIy7DO2IcGM7Qp6EsjBWHv2c1vozYpTHpz/C+p4Gg+8MlWLBDeoieLduRU7+eza+unXe0vRFq+xj5j1+kfp1Uhrzpx9TfU76Ukg4uXlfVAqx1CEinpnEyPOzmGIE/SN7of8uvWNWSP12uflB8mVJoT3XPQwu9b37WKHcKdaMNgaeODOj7s7ClDMQCr6ye23CT0vWKrHnvvO8+QtybfqgDcpRFDLc8BcFKzvxNpnsvl3bCQSE4WwM86FyynhV73JPILwB1Bf03Yaiv9fhzmyB9lt2DCXzfz02ADfevKz6mIu+r6RfapYFkFidOz8cAy0Cy68vmAJsaM5qJN5QB7zW73tUGDbXioXVhKkQszCittpYHMN3TkZwUGNPm0SlQW9/DvUUumcTPUa4M4qRJymSaApjSXMj6B8rMzO1hbCI9M+s7jnW/0+BpellrYB4lsX5GNtrStwLyQjT/g13RVUAkEtG4BnGerqRhjvf0aPtzZAAD+6/75yJBW7/F2a01oH69ShOvmWdCtwO/FD+bfZ1AHJpuUvco50OMKpwe8maNuNyStHcc+IEnnISnw4Rb2w8M8i4EwMNplPU/IiaqLBncNgyciL9+0giVNDwcT8YpGwTMh/veZyFLt/FTf+/F+idBY0lQgUVvP9taofD8KFa6FtFLSAdZvbBaqyDdfEIlw5DQssawHKD/zKz1D1NkGrqY0NknP4d4nVrUkr3r6B/1C7Da8G3Uyz+ymQE6Z/E9FbsfsFKmjImSlh6AMirppf1dTcu5sdx22tKnvFSw1dQ67hnjsG6Gu0YM2lqLRvUYxud8eWVorC9GQ/6mwGk1LFZcL//VaybzDz1HR2wtgAr9S90OMwTeXjqn3NjPjLJKDJNHnb518UATE1w0Te3I+HGub2eZFGRA5n56hc/PsXqUFOxU1useiyY+GQ15p0KnRRYdpyXRYYrMnTDb2XvOiad2KGdIquDhauHIfiKjUMoo3PK4SYhvJU6KpN29fJ3ccbdO3sU4qVR8tcW1zmorraxW/sNRXgvDJhWr7iuALjIGz1Jd/Nq23K4uL+EyQqJLnpwFgcnz6C4oPFW4S5AskBJ/a75MG31WsMptpJN3cNwBA23hxAlAgwmo0oddJ0S2TbhCNC/dhAcHupOILmbQdxxLMS0bVrUiWsznzeMQRp83N6byhCvJiffepOJNKJvIGgVCeUQ/g07QfkU3YYt1iND0ilAADG0Pp6hvanfD1xB/WTjxltU9bSaiNCFOAlSVJFskQ+HCFBo1pFtB/hydTj2EpV+ymh85tQ0Q3q+UL0TioKHb2eqT7FjHxqG496aY7LQU5NzKiVXzXBSd0uuGVyri0mtM/+C9WE3farz0dDc1a3nacHWidzF/AwBoywD6CPMl5wkVVL8VI+IPdqTc+jBHOy249Uz+JWIqwJnJr0Ei87Kq/oh0LoaReLX1bsC5Rgej/zcPLqE6GkH+/QFT1ohDAqFQ8Vi2nJ3vxkujRFxcS1KDBF7/NwJ4cPUnHjno608a13+Z//bWRn5ANDPOufrtoXiVBaz4PWgsf7CXh5jVxH846raFW1MngnIedpBrJq32dfYgjkoIiEiNLnaPPNnXpC0yE8ZPDPTuShhoBHHWpGLEOPeRHlvHhiYKkPPfnMGFbu5lF9MLtlQTAnlYMzLzFW/yxfah+uPoml1ggEZFWsB4KQ7Fzks2VPJyh4cI2CpifqUE6rWETZ9Fze3nc8ErC+uv2NmMHw9JU896vFs2doO0i59dcKWqr3c2C28zb/vEr2TcyoR3017ddjfHmorxje88xOe52C2HrS19lYZw99qGKgxQBukuUOgtZtwVcMG0pjYZbXRMhqJDmdeUApZU0vi3UZOwttwhl+DRRX63xSOYFz5ug7PG/fALtLI4a3c4e3yGZvZ8yF5LSdNDs8+eCIzuj1u3Xjo5YIggRlZhMpqvsWsawhr+LFzaPpRIqGgCcUFOySJWSLs/0Y26kRSpSCxL1WmRRXmf2ZArdMvtU0eGRBXCYBqdnWtoXvvNWi9GMbFW5cmKai/gpTb6US7jGRhlOyNWD+b59FYWTvQk1rguxQX+opIASdj5FQ/5MALpdUMIyiFBJeMTKQqJoQks6jwWpKDN3fI0cJY8Dn1ocahwdmoUwAz6/p7zvxGA9xJ1oASF7Rq+ZxuTi+BFy5wNEwYkBDIPaHZb40g4MV7uzBqKoLB9fp10QDv1+T+aEQA+dGUcYZ/T723AYOCAmbEznPF3KSEBwqX4Y1RuVqXA8FBlx2j9pYpLyA7XqIR1fuUjKUoR791WF19trJ5YNBF52deABpFNODOM+s3lNotuVbwYdY0oLP8SJR0QxyL9JAHTknlgzlEAliWjdU5Twue1hwsM3F0aHu49TCkcb+CTczgjntAlqd9faZz8ElKZEdvoHagpwWGEy5aFScRdltmHhX0RgwslPWFQIjo+uXHlPbkhK7t0cKOCRqRJXPdflolWJN3kWRJjtLIxBqAiAqt576ATGgqWNrIvy0F1MpiLOLMZl6oNg2wsX/9Eb+uh7Cw/ioEzJgFQ6YObt13Tko86mQH8sRd5Q19nuNCmJQWFcPFgVtWK6eM8dqAPi3+dg9iADYe7ly6kM06X9lmaph73UJe3IAca5OjeNy5RIvm6EvSGnfeDltmyE8H3dY+QvqgJPFKhs7F7MeooGoah+/Ey9wySgmLoxa7FzSlE6VN6MyQiTQ6Ar+muOsjsbJvdwtQ76ojTWWqhT/FOl/u5sMfaO3XsWqh1+rJv7ucjuvqB8xnjhDVzxg8/1Yo87+T4+xDTOR4iC+pXXqryMWYn4OAMuLwOYIP6ZoBTprPeZ1nURAgdpcCKkE5RAKTtTZpiksGU1YRSMBhbl5G/G5Qco8kOFP4At3hPnLGDkFaEf9z1kmVqFOJXpptuO5Amt5eg6HHKgHv9DrifLehWFHArAXRVO9rpaTYJ8lSRHL89dcEXlsidQcmWJvcYjS4z+W4pL7IyGptLubue2jgyg93c80rEdbgSusdTzsU216tD1DNJyY2dFSXf6qdcVc8ga8tqyzoJdLl3r53dzepdqwSywUmFI1N3sk+y8mcQD5JqeDX55qsLCrUiOe53h1c8+kDGStZpidh493gD5qMnwpdw2YfpvOxXkJoKHMaKb8O/Cq527LvEXNIHgdXTvlKLJcUyVvlH+xQWGLn662ZNMNYqXynSxvMSgMrMzZrV+xhmjUeYVTIFR5BIYF4c30U5WIN27CRwqxIvrJQtJzofMBq9++vPej9vo2ZfROKVb9PzaajTSzWHIUZ4JGXuuewnyE2p39Wdc/3nky/XPgmW3Y+8qVJ7Jlc/0nnIBrbGnocV4JXcTlSONXY5uwiUdnuxMz5glEvdVcP6IprWWCoY40tBI+wI+ZRX7TTvdVLeV+jr06OA8wXwBdHEcfLa88LS3lONXqZp4FOaqIDGhXLQmBdpB8xrI5He2235NWp+nva1CgmBJg3iD/sWJAKmDU4kd5ntkoi9GCveY5qCg21iOyJtdIbBfApy6V8j1ef63CBPykbEnEzQxtk7nMaisxPhyOYfFHJji0KT7LKpnRc+UesNBMrFEDD2Itu8ZUJGvvQG6mNS6pc6f+8y7p/oD+Tc5KL2g+/RI9v/1Y2yX95zZOxlOw0Q/wGdJitRvs+vRhBnlcY3M5DyraHWWGC9n1L03MoQzxip6SVhXuJ/jqK6Dn+u0GFKoLhksjLtZxfxxqL+8G9/rHow1h7/qNZPHNZf4DOdAg8so0l3HHNxU/KRt6eFHX9dfaw42jhewta9ezpvua2zR1yUqJUM32rxKZZL2tMYW8VclRI5kw+HYBZCE16uYQl5ew9c/tTcbr/UMEAz/fOFNbAvcZuQg2rc5xnOcnqwkzarPi0ao+bu4grzh9JAlYmwWVkyCcgg68DCerWuvj76vf+VXB6sSsH97UV491KxssbOQ3VY73kSXOH5DzIGitDplWGy+QPWJvGyOUsxu26gjYwNx0UjWKj8JsPpqsmAq3HZoRNSjKipXLSsbMyB8KiEyUxlOowYBNpR/VzRRd02/FtAHnPSWryglPLDiAL7IMhmmvZRBntL4fPA1Y2ishA5OKMlkM97UOW7Jb7gVNAzY2AAA',
'Lech Kaczyński': 'data:image/webp;base64,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'
};
function applyEmbeddedBossPhotos() {
[...ALL_GOOD, ...ALL_BAD].forEach(c => {
if (EMBEDDED_BOSS_PHOTOS[c.name]) {
c.photoUrl = EMBEDDED_BOSS_PHOTOS[c.name];
c.photoUrls = [EMBEDDED_BOSS_PHOTOS[c.name], ...(c.photoUrls || [])];
c.embeddedBossPhoto = true;
}
});
}
applyEmbeddedBossPhotos();
async function fetchSejmList(term) {
if (!sejmMpCache[term]) {
sejmMpCache[term] = fetch(`https://api.sejm.gov.pl/sejm/term${term}/MP`)
.then(r => r.ok ? r.json() : [])
.catch(() => []);
}
return sejmMpCache[term];
}
async function resolveSejmPhotoUrlByName(c) {
if (!c.sejmId) return null;
const target = normName(c.name);
for (const term of SEJM_TERMS_TO_SEARCH) {
const list = await fetchSejmList(term);
const hit = list.find(mp => normName(`${mp.firstName || ''} ${mp.lastName || ''}`) === target);
if (hit && (hit.id || hit.num)) return `https://api.sejm.gov.pl/sejm/term${term}/MP/${hit.id || hit.num}/photo`;
}
return null;
}
function tryLoadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
async function loadCharacterPhoto(c) {
const urls = [];
const sejmUrl = await resolveSejmPhotoUrlByName(c);
if (sejmUrl) urls.push(sejmUrl);
urls.push(...c.photoUrls);
urls.push(c.fallbackPhotoUrl);
for (const url of [...new Set(urls)]) {
try {
const img = await tryLoadImage(url);
c.photo = img;
c.photoLoaded = true;
c.photoUrl = url;
return;
} catch(e) {}
}
c.photoLoaded = false;
}
function loadPhotos() {
[...ALL_GOOD, ...ALL_BAD].forEach(c => loadCharacterPhoto(c));
}
loadPhotos();
function loadJP2Photo() {
const img = new Image();
img.onload = () => { jp2.img = img; };
img.src = JP2_BASE64_IMG;
}
loadJP2Photo();
// ===================== SYSTEM DŹWIĘKU =====================
// ===================== SYSTEM DŹWIĘKU =====================
let audioCtx = null;
function ensureAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
// Generowanie dźwięku ciosu
function playHitSound(type) {
ensureAudio();
const t = audioCtx.currentTime;
// Szum (noise) dla efektu uderzenia
const bufSize = audioCtx.sampleRate * 0.08;
const buf = audioCtx.createBuffer(1, bufSize, audioCtx.sampleRate);
const data = buf.getChannelData(0);
for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i/bufSize);
const noise = audioCtx.createBufferSource();
noise.buffer = buf;
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(type === 'special' ? 0.6 : type === 'heavy' ? 0.4 : 0.25, t);
gain.gain.exponentialRampToValueAtTime(0.001, t + (type === 'special' ? 0.3 : 0.15));
const filt = audioCtx.createBiquadFilter();
filt.type = 'lowpass';
filt.frequency.value = type === 'special' ? 2000 : type === 'heavy' ? 1200 : 800;
noise.connect(filt);
filt.connect(gain);
gain.connect(audioCtx.destination);
noise.start(t);
noise.stop(t + 0.3);
// Ton uderzenia
const osc = audioCtx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime(type === 'special' ? 200 : type === 'heavy' ? 120 : 80, t);
osc.frequency.exponentialRampToValueAtTime(40, t + 0.15);
const og = audioCtx.createGain();
og.gain.setValueAtTime(0.3, t);
og.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
osc.connect(og);
og.connect(audioCtx.destination);
osc.start(t);
osc.stop(t + 0.2);
}
// Dźwięk bloku
function playBlockSound() {
ensureAudio();
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(300, t);
osc.frequency.exponentialRampToValueAtTime(100, t + 0.1);
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.15, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.12);
}
// Dźwięk KO
function playKOSound() {
ensureAudio();
const t = audioCtx.currentTime;
for (let i = 0; i < 5; i++) {
const osc = audioCtx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(400 - i*60, t + i*0.12);
osc.frequency.exponentialRampToValueAtTime(50, t + i*0.12 + 0.2);
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.15, t + i*0.12);
g.gain.exponentialRampToValueAtTime(0.001, t + i*0.12 + 0.25);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t + i*0.12); osc.stop(t + i*0.12 + 0.3);
}
}
// Dźwięk specjalny
function playSpecialSound() {
ensureAudio();
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, t);
osc.frequency.exponentialRampToValueAtTime(800, t + 0.2);
osc.frequency.exponentialRampToValueAtTime(200, t + 0.5);
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.3, t);
g.gain.linearRampToValueAtTime(0.4, t + 0.2);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.6);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.65);
}
// Mazurek Dąbrowskiego melodia patriotyczna w tle
const MAZUREK = [
349,349,392,440, 440,392,349,330, 294,294,330,349, 349,330,294,0,
294,294,330,349, 349,330,294,262, 233,233,262,294, 294,262,233,0,
349,349,392,440, 440,392,349,330, 294,294,330,349, 349,330,294,0,
523,523,466,440, 440,466,523,587, 523,523,466,440, 440,466,523,0,
];
let mazurekPlaying = false;
let mazurekTimeout = null;
function playMazurek() {
if (!audioCtx || mazurekPlaying) return;
mazurekPlaying = true;
let idx = 0;
function playNote() {
if (!mazurekPlaying) return;
const freq = MAZUREK[idx % MAZUREK.length];
if (freq > 0) {
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'triangle';
osc.frequency.value = freq;
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.08, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.28);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.3);
}
idx++;
mazurekTimeout = setTimeout(playNote, 280);
}
playNote();
}
function stopMazurek() { mazurekPlaying = false; clearTimeout(mazurekTimeout); }
// Dźwięk wyboru menu
function playMenuSelect() {
ensureAudio();
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'square';
osc.frequency.setValueAtTime(440, t);
osc.frequency.setValueAtTime(660, t + 0.05);
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.1, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.2);
}
function playMenuMove() {
ensureAudio();
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
osc.type = 'sine';
osc.frequency.value = 330;
const g = audioCtx.createGain();
g.gain.setValueAtTime(0.06, t);
g.gain.exponentialRampToValueAtTime(0.001, t + 0.06);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.08);
}
// Prosty syntetyczny efekt chóru przy wejściu Jana Pawła II.
function playChoirSound() {
ensureAudio();
if (!audioCtx) return;
const t = audioCtx.currentTime;
const master = audioCtx.createGain();
master.gain.setValueAtTime(0.0001, t);
master.gain.exponentialRampToValueAtTime(0.18, t + 0.35);
master.gain.exponentialRampToValueAtTime(0.001, t + 2.8);
master.connect(audioCtx.destination);
const freqs = [196, 246.94, 293.66, 392, 493.88];
freqs.forEach((freq, i) => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
const filter = audioCtx.createBiquadFilter();
osc.type = i % 2 ? 'triangle' : 'sine';
osc.frequency.setValueAtTime(freq, t);
osc.detune.setValueAtTime((i - 2) * 6, t);
gain.gain.setValueAtTime(0.0001, t);
gain.gain.exponentialRampToValueAtTime(0.12, t + 0.45 + i * 0.04);
gain.gain.exponentialRampToValueAtTime(0.001, t + 2.6);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1300, t);
osc.connect(filter);
filter.connect(gain);
gain.connect(master);
osc.start(t + i * 0.03);
osc.stop(t + 2.9);
});
}
// Announcer głos (syntezator mowy, jeśli dostępny)
function announce(text) {
if ('speechSynthesis' in window) {
const u = new SpeechSynthesisUtterance(text);
u.lang = 'pl-PL'; u.rate = 1.05; u.pitch = 0.85; u.volume = 0.8;
speechSynthesis.cancel();
speechSynthesis.speak(u);
}
}
function announceCharacter(charData, prefix = 'Wybrano') {
announce(`${prefix}: ${charData.bio}`);
}
// ===================== KLAWIATURA =====================
const keys = {};
const justPressed = {};
document.addEventListener('keydown', e => {
if (!keys[e.code]) justPressed[e.code] = true;
keys[e.code] = true;
e.preventDefault();
ensureAudio();
});
document.addEventListener('keyup', e => { keys[e.code] = false; });
function consumeKey(code) {
if (justPressed[code]) { justPressed[code] = false; return true; }
return false;
}
// ===================== CZĄSTECZKI I EFEKTY =====================
function spawnParticles(x, y, color, count, speed) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const spd = Math.random() * speed + 1;
particles.push({
x, y,
vx: Math.cos(angle) * spd,
vy: Math.sin(angle) * spd - 2,
life: 1,
decay: 0.02 + Math.random() * 0.03,
size: 2 + Math.random() * 4,
color
});
}
}
function spawnHitSpark(x, y, facing) {
for (let i = 0; i < 8; i++) {
const angle = -Math.PI/4 + Math.random() * Math.PI/2;
const spd = 3 + Math.random() * 6;
particles.push({
x, y,
vx: Math.cos(angle) * spd * facing,
vy: Math.sin(angle) * spd - 3,
life: 1,
decay: 0.05 + Math.random() * 0.05,
size: 2 + Math.random() * 3,
color: `hsl(${40 + Math.random()*30}, 100%, ${50+Math.random()*40}%)`
});
}
}
function addFloatingText(x, y, text, color, size) {
floatingTexts.push({x, y, text, color, size: size||24, life:1, vy:-2});
}
function updateParticles() {
for (let i = particles.length-1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx; p.y += p.vy; p.vy += 0.1;
p.life -= p.decay;
if (p.life <= 0) particles.splice(i, 1);
}
for (let i = floatingTexts.length-1; i >= 0; i--) {
const t = floatingTexts[i];
t.y += t.vy; t.vy *= 0.97;
t.life -= 0.015;
if (t.life <= 0) floatingTexts.splice(i, 1);
}
}
function drawParticles() {
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
});
ctx.globalAlpha = 1;
floatingTexts.forEach(t => {
ctx.globalAlpha = t.life;
ctx.font = `bold ${t.size}px Arial`;
ctx.textAlign = 'center';
ctx.strokeStyle = '#000';
ctx.lineWidth = 3;
ctx.strokeText(t.text, t.x, t.y);
ctx.fillStyle = t.color;
ctx.fillText(t.text, t.x, t.y);
});
ctx.globalAlpha = 1;
}
// ===================== KLASA WOJOWNIKA =====================
class Fighter {
constructor(charData, x, facing) {
this.data = charData;
this.x = x;
this.y = GROUND;
this.vx = 0;
this.vy = 0;
this.facing = facing; // 1=prawo, -1=lewo
this.hp = charData.hp;
this.maxHp = charData.hp;
this.power = 0;
this.maxPower = 100;
this.state = 'idle'; // idle, walk, jump, crouch, lpunch, hpunch, lkick, hkick, special, block, hit, ko
this.stateTimer = 0;
this.animFrame = 0;
this.combo = 0;
this.comboTimer = 0;
this.attackHitThisMove = false;
this.bodyW = 50;
this.bodyH = 110;
this.isGrounded = true;
this.blockTimer = 0;
this.parryTimer = 0;
this.counterReady = false;
this.counterTimer = 0;
this.lastBlockedAttacker = null;
// Kolory postaci
this.bodyColor = charData.faction === 'good' ? '#2563eb' : '#dc2626';
this.accentColor = charData.faction === 'good' ? '#fff' : '#1a1a2e';
this.trousersColor = charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d';
}
canAct() {
return ['idle','walk','jump','crouch'].includes(this.state);
}
startAttack(type) {
if (!this.canAct()) return;
if (type === 'special' && this.power < this.maxPower) return;
const attacks = {
lpunch: {timer:14, dmg:5 + this.data.atk*0.3, range:65, hitFrame:4, type:'light'},
hpunch: {timer:22, dmg:10 + this.data.atk*0.5, range:75, hitFrame:7, type:'heavy'},
lkick: {timer:16, dmg:7 + this.data.atk*0.4, range:85, hitFrame:5, type:'light'},
hkick: {timer:26, dmg:13 + this.data.atk*0.6, range:95, hitFrame:8, type:'heavy'},
special: {timer:35, dmg:22 + this.data.atk*0.8, range:130, hitFrame:12, type:'special'},
};
const a = attacks[type];
this.state = type;
this.stateTimer = a.timer;
this.attackHitThisMove = false;
this.attackData = a;
if (type === 'special') {
this.power = 0;
playSpecialSound();
const txts = this.data.faction === 'good' ? SPECIAL_GOOD_TXT : SPECIAL_BAD_TXT;
addFloatingText(this.x, this.y - 140, txts[Math.floor(Math.random()*txts.length)],
this.data.faction === 'good' ? '#fbbf24' : '#ef4444', 30);
addFloatingText(this.x, this.y - 170, this.data.special,
'#fff', 22);
}
}
block() {
if (this.state === 'ko') return;
if (this.state === 'block') return;
if (!this.canAct()) return;
this.state = 'block';
this.blockTimer = 0;
this.parryTimer = 14; // krótko po wejściu w blok działa perfect block / parry
}
unblock() {
if (this.state === 'block') {
this.state = this.isGrounded ? 'idle' : 'jump';
}
this.blockTimer = 0;
this.parryTimer = 0;
}
performCounter(target) {
if (!this.counterReady || this.state === 'ko') return false;
this.counterReady = false;
this.counterTimer = 0;
this.unblock();
this.facing = target && target.x > this.x ? 1 : -1;
this.state = 'hpunch';
this.stateTimer = 18;
this.attackHitThisMove = false;
this.attackData = {timer:18, dmg:16 + this.data.atk*0.75, range:110, hitFrame:3, type:'counter'};
addFloatingText(this.x, this.y - 145, 'COUNTERATTACK!', '#fbbf24', 26);
playSpecialSound();
return true;
}
getHitbox() {
return {x: this.x - this.bodyW/2, y: this.y - this.bodyH, w: this.bodyW, h: this.bodyH};
}
getAttackBox() {
if (!this.attackData || this.attackHitThisMove) return null;
const elapsed = (this.attackData.timer - this.stateTimer);
const hitStart = this.attackData.hitFrame;
const hitEnd = hitStart + 5;
if (elapsed >= hitStart && elapsed <= hitEnd) {
const range = this.attackData.range;
return {
x: this.facing === 1 ? this.x + 10 : this.x - 10 - range,
y: this.y - this.bodyH * 0.7,
w: range,
h: 40
};
}
return null;
}
takeDamage(amount, attacker) {
let actualDmg = amount;
const defMult = 1 - (this.data.def * 0.02);
actualDmg *= defMult;
if (this.state === 'block') {
const perfect = this.parryTimer > 0;
if (perfect) {
actualDmg = 0;
this.counterReady = true;
this.counterTimer = 55;
this.lastBlockedAttacker = attacker;
attacker.vx = -attacker.facing * 8;
attacker.state = 'hit';
attacker.stateTimer = 14;
attacker.power = Math.max(0, attacker.power - 8);
this.power = Math.min(this.maxPower, this.power + 18);
playBlockSound();
playSpecialSound();
addFloatingText(this.x, this.y - 135, 'PERFECT BLOCK!', '#fbbf24', 24);
addFloatingText(attacker.x, attacker.y - 120, 'PARRY!', '#60a5fa', 22);
spawnParticles(this.x, this.y - 70, '#fbbf24', 18, 7);
spawnParticles(attacker.x, attacker.y - 70, '#60a5fa', 10, 5);
shakeX = (Math.random() - 0.5) * 12;
shakeY = (Math.random() - 0.5) * 8;
} else {
actualDmg *= 0.15;
this.counterReady = true;
this.counterTimer = 25;
this.lastBlockedAttacker = attacker;
playBlockSound();
addFloatingText(this.x, this.y - 120, 'BLOCK!', '#88f', 20);
spawnParticles(this.x + this.facing * -20, this.y - 60, '#4488ff', 5, 3);
}
} else {
this.state = 'hit';
this.stateTimer = 12;
this.vx = this.facing * -3;
// Efekty
const hitX = this.x + this.facing * -15;
const hitY = this.y - this.bodyH * 0.5;
spawnHitSpark(hitX, hitY, attacker.facing);
// Tekst uderzenia
const txts = attacker.data.faction === 'good' ? HIT_GOOD : HIT_BAD;
if (Math.random() > 0.5) {
addFloatingText(hitX, hitY - 30, txts[Math.floor(Math.random()*txts.length)],
attacker.data.faction === 'good' ? '#60a5fa' : '#f87171', 18);
}
playHitSound(attacker.attackData ? attacker.attackData.type : 'light');
}
actualDmg = Math.max(0, Math.round(actualDmg));
this.hp = Math.max(0, this.hp - actualDmg);
this.power = Math.min(this.maxPower, this.power + actualDmg * 0.8);
attacker.power = Math.min(attacker.maxPower, attacker.power + actualDmg * 0.5);
// Screen shake
shakeX = (Math.random() - 0.5) * actualDmg * 0.8;
shakeY = (Math.random() - 0.5) * actualDmg * 0.5;
// Combo
attacker.combo++;
attacker.comboTimer = 60;
if (attacker.combo > 1) {
addFloatingText(attacker.x, attacker.y - 130, `${attacker.combo}x COMBO!`, '#ffd700', 26);
}
// KO check
if (this.hp <= 0) {
this.state = 'ko';
this.stateTimer = 999;
this.vy = -8;
this.vx = this.facing * -5;
playKOSound();
}
}
update() {
// Fizyka
if (!this.isGrounded) {
this.vy += GRAVITY;
}
this.x += this.vx;
this.y += this.vy;
if (this.y >= GROUND) {
this.y = GROUND;
this.vy = 0;
this.isGrounded = true;
if (this.state === 'jump') this.state = 'idle';
} else {
this.isGrounded = false;
}
// Granice sceny
this.x = Math.max(40, Math.min(W - 40, this.x));
// Tarcie
this.vx *= 0.85;
// Timer stanu
if (this.stateTimer > 0) {
this.stateTimer--;
if (this.stateTimer <= 0 && this.state !== 'ko' && this.state !== 'block') {
this.state = this.isGrounded ? 'idle' : 'jump';
}
}
// Combo timer
if (this.comboTimer > 0) this.comboTimer--;
else this.combo = 0;
// Blok, parry i counter timery
if (this.state === 'block') this.blockTimer++;
if (this.parryTimer > 0) this.parryTimer--;
if (this.counterTimer > 0) {
this.counterTimer--;
if (this.counterTimer <= 0) this.counterReady = false;
}
// Animacja
this.animFrame += 0.15;
// Efekt specjalny cząsteczki
if (this.state === 'special' && Math.random() > 0.5) {
const c = this.data.faction === 'good' ? '#60a5fa' : '#ef4444';
spawnParticles(this.x + this.facing * 30, this.y - 60, c, 1, 4);
}
}
draw(ctx) {
ctx.save();
const bx = this.x;
const by = this.y;
const f = this.facing;
const anim = Math.sin(this.animFrame * 3);
const anim2 = Math.sin(this.animFrame * 6);
// Cień
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.beginPath();
ctx.ellipse(bx, GROUND + 5, 30, 8, 0, 0, Math.PI * 2);
ctx.fill();
// KO leżenie na ziemi
if (this.state === 'ko' && this.isGrounded) {
ctx.save();
ctx.translate(bx, by);
ctx.rotate(f * Math.PI / 2);
this.drawBody(ctx, 0, 0, 0, 0, 0, 0, 0.5);
ctx.restore();
ctx.restore();
return;
}
// Hit wstrząs
let offsetX = 0, offsetY = 0;
if (this.state === 'hit') {
offsetX = (Math.random()-0.5) * 4;
offsetY = (Math.random()-0.5) * 2;
}
// Poświata specjalna
if (this.state === 'special') {
ctx.shadowColor = this.data.faction === 'good' ? '#3b82f6' : '#ef4444';
ctx.shadowBlur = 30;
}
// Rysowanie ciała
const punchExt = ['lpunch','hpunch'].includes(this.state) ?
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
const kickExt = ['lkick','hkick'].includes(this.state) ?
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
const specialExt = this.state === 'special' ?
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
const blockArms = this.state === 'block' ? 1 : 0;
const walkAnim = this.state === 'walk' ? anim : 0;
this.drawBody(ctx, bx + offsetX, by + offsetY, f, punchExt, kickExt, specialExt, 1, blockArms, walkAnim);
if (this.state === 'block') {
ctx.save();
ctx.globalAlpha = this.parryTimer > 0 ? 0.85 : 0.38;
ctx.strokeStyle = this.parryTimer > 0 ? '#fbbf24' : '#60a5fa';
ctx.lineWidth = this.parryTimer > 0 ? 5 : 3;
ctx.shadowColor = this.parryTimer > 0 ? '#fbbf24' : '#60a5fa';
ctx.shadowBlur = this.parryTimer > 0 ? 22 : 10;
ctx.beginPath();
ctx.arc(bx + f * 25, by - 70, 34, -Math.PI/2, Math.PI/2);
ctx.stroke();
ctx.restore();
}
if (this.counterReady) {
ctx.save();
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#fbbf24';
ctx.fillText('COUNTER READY', bx, by - 135);
ctx.restore();
}
ctx.shadowBlur = 0;
ctx.restore();
}
drawBody(ctx, bx, by, f, punchExt, kickExt, specialExt, alpha, blockArms, walkAnim) {
ctx.globalAlpha = alpha || 1;
blockArms = blockArms || 0;
walkAnim = walkAnim || 0;
// Nogi
ctx.strokeStyle = this.trousersColor;
ctx.lineWidth = 8;
ctx.lineCap = 'round';
// Lewa noga
ctx.beginPath();
ctx.moveTo(bx - 10, by - 40);
const legOff1 = walkAnim * 10 + kickExt * 40 * f;
ctx.lineTo(bx - 12 + legOff1, by);
ctx.stroke();
// Prawa noga
ctx.beginPath();
ctx.moveTo(bx + 10, by - 40);
ctx.lineTo(bx + 12 - walkAnim * 10, by);
ctx.stroke();
// Buty
ctx.fillStyle = '#111';
ctx.fillRect(bx - 17 + legOff1, by - 5, 14, 6);
ctx.fillRect(bx + 3 - walkAnim * 10, by - 5, 14, 6);
// Tułów
ctx.fillStyle = this.bodyColor;
const torsoY = by - this.bodyH + 25;
ctx.fillRect(bx - 18, torsoY, 36, 50);
// Krawat/akcent
ctx.fillStyle = this.data.faction === 'good' ? '#dc2626' : '#1e3a8a';
ctx.fillRect(bx - 2, torsoY + 5, 4, 30);
// Ramiona i ręce
ctx.strokeStyle = this.bodyColor;
ctx.lineWidth = 7;
// Tylna ręka
ctx.beginPath();
ctx.moveTo(bx - f * 15, torsoY + 12);
if (blockArms) {
ctx.lineTo(bx + f * 5, torsoY + 20);
} else {
ctx.lineTo(bx - f * 25, torsoY + 35 + Math.sin(this.animFrame*2)*3);
}
ctx.stroke();
// Przednia ręka (atakująca)
ctx.beginPath();
ctx.moveTo(bx + f * 15, torsoY + 12);
if (blockArms) {
ctx.lineTo(bx + f * 10, torsoY + 5);
} else if (punchExt > 0) {
ctx.lineTo(bx + f * (20 + punchExt * 55), torsoY + 15);
// Pięść
ctx.fillStyle = '#fcd34d';
ctx.beginPath();
ctx.arc(bx + f * (20 + punchExt * 55), torsoY + 15, 7, 0, Math.PI*2);
ctx.fill();
} else if (specialExt > 0) {
ctx.lineTo(bx + f * (20 + specialExt * 70), torsoY + 5);
// Energia specjalna
ctx.fillStyle = this.data.faction === 'good' ? '#60a5fa' : '#f87171';
ctx.beginPath();
ctx.arc(bx + f * (20 + specialExt * 70), torsoY + 5, 12 + specialExt * 8, 0, Math.PI*2);
ctx.globalAlpha = 0.6;
ctx.fill();
ctx.globalAlpha = alpha || 1;
} else {
ctx.lineTo(bx + f * 25, torsoY + 35 + Math.sin(this.animFrame*2+1)*3);
}
ctx.stroke();
// Głowa
const headY = torsoY - 18;
ctx.fillStyle = '#fcd34d';
ctx.beginPath();
ctx.arc(bx, headY, 18, 0, Math.PI * 2);
ctx.fill();
// Zdjęcie na głowie (jeśli załadowane)
if (this.data.photoLoaded && this.data.photo) {
ctx.save();
ctx.beginPath();
ctx.arc(bx, headY, 17, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(this.data.photo, bx - 17, headY - 17, 34, 34);
ctx.restore();
} else {
// Inicjały na głowie
ctx.fillStyle = '#000';
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.data.initials, bx, headY);
ctx.textBaseline = 'alphabetic';
}
// Oczy (w kierunku przeciwnika)
if (!this.data.photoLoaded) {
ctx.fillStyle = '#000';
ctx.fillRect(bx + f * 5 - 2, headY - 4, 4, 4);
ctx.fillRect(bx + f * 5 + 6 * f, headY - 4, 4, 4);
}
// Pas/opaska frakcyjna
if (this.data.faction === 'good') {
// Biało-czerwona opaska
ctx.fillStyle = '#fff';
ctx.fillRect(bx - 20, torsoY - 2, 40, 3);
ctx.fillStyle = '#dc2626';
ctx.fillRect(bx - 20, torsoY + 1, 40, 3);
} else {
// Czarno-czerwona opaska
ctx.fillStyle = '#000';
ctx.fillRect(bx - 20, torsoY - 2, 40, 3);
ctx.fillStyle = '#facc15';
ctx.fillRect(bx - 20, torsoY + 1, 40, 3);
}
ctx.globalAlpha = 1;
}
}
// ===================== SZTUCZNA INTELIGENCJA =====================
class AIController {
constructor(fighter, target) {
this.fighter = fighter;
this.target = target;
this.actionTimer = 0;
this.action = 'approach';
this.difficulty = 0.6; // 0-1, wyższa = trudniejsza
}
update() {
const f = this.fighter;
const t = this.target;
if (f.state === 'ko' || t.state === 'ko') return;
const dist = Math.abs(f.x - t.x);
const facingTarget = (t.x > f.x) ? 1 : -1;
f.facing = facingTarget;
this.actionTimer--;
if (this.actionTimer <= 0) {
this.chooseAction(dist);
}
if (f.counterReady && dist < 130 && Math.random() < 0.08) {
f.performCounter(t);
return;
}
if (this.action !== 'block' && f.state === 'block') {
f.unblock();
}
// Wykonywanie akcji
switch(this.action) {
case 'approach':
if (dist > 80) {
f.vx += facingTarget * 2.5 * (this.fighter.data.spd / 10);
if (f.canAct()) f.state = 'walk';
}
break;
case 'retreat':
f.vx -= facingTarget * 2 * (this.fighter.data.spd / 10);
if (f.canAct()) f.state = 'walk';
break;
case 'attack_l':
if (dist < 90) f.startAttack('lpunch');
else this.action = 'approach';
break;
case 'attack_h':
if (dist < 100) f.startAttack('hpunch');
else this.action = 'approach';
break;
case 'kick_l':
if (dist < 110) f.startAttack('lkick');
else this.action = 'approach';
break;
case 'kick_h':
if (dist < 120) f.startAttack('hkick');
else this.action = 'approach';
break;
case 'special':
if (dist < 150) f.startAttack('special');
else this.action = 'approach';
break;
case 'block':
f.block();
break;
case 'jump':
if (f.isGrounded && f.canAct()) {
f.vy = -13;
f.state = 'jump';
}
this.action = 'approach';
break;
case 'idle':
if (f.canAct()) f.state = 'idle';
break;
}
}
chooseAction(dist) {
const f = this.fighter;
const hpRatio = f.hp / f.maxHp;
const r = Math.random();
const d = this.difficulty;
if (['lpunch','hpunch','lkick','hkick','special'].includes(this.target.state) && dist < 145 && r < 0.55 * d) {
this.action = 'block';
this.actionTimer = 12 + Math.random() * 10;
return;
}
if (f.power >= f.maxPower && dist < 160 && r < 0.3 * d) {
this.action = 'special';
this.actionTimer = 5;
return;
}
if (dist > 200) {
this.action = r < 0.7 ? 'approach' : (r < 0.85 ? 'jump' : 'idle');
this.actionTimer = 20 + Math.random() * 30;
} else if (dist > 100) {
if (r < 0.4 * d) this.action = 'approach';
else if (r < 0.6) this.action = 'kick_h';
else if (r < 0.75) this.action = 'kick_l';
else if (r < 0.9) this.action = 'block';
else this.action = 'retreat';
this.actionTimer = 15 + Math.random() * 25;
} else {
// Blisko
if (hpRatio < 0.3 && r < 0.35) {
this.action = r < 0.2 ? 'retreat' : 'block';
} else if (r < 0.25) this.action = 'attack_l';
else if (r < 0.45) this.action = 'attack_h';
else if (r < 0.6) this.action = 'kick_l';
else if (r < 0.75) this.action = 'kick_h';
else if (r < 0.85) this.action = 'block';
else this.action = 'retreat';
this.actionTimer = 12 + Math.random() * 20;
}
// Bossy i legendy są trudniejsze
if (f.data.isSpecial) {
this.actionTimer *= 0.7;
this.difficulty = 0.85;
}
}
}
// ===================== RYSOWANIE TŁA =====================
function drawSejmBackground() {
// Gradient niebo/ściana
const grad = ctx.createLinearGradient(0, 0, 0, H);
grad.addColorStop(0, '#1a1a2e');
grad.addColorStop(0.4, '#16213e');
grad.addColorStop(0.7, '#0f3460');
grad.addColorStop(1, '#1a1a2e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Kolumny sejmowe
for (let i = 0; i < 6; i++) {
const cx = 60 + i * (W - 120) / 5;
ctx.fillStyle = '#2a2a4a';
ctx.fillRect(cx - 12, 50, 24, GROUND - 50);
ctx.fillStyle = '#3a3a5a';
ctx.fillRect(cx - 15, 45, 30, 15);
ctx.fillRect(cx - 15, GROUND - 5, 30, 15);
}
// Flaga Polski (wielka, w tle)
ctx.globalAlpha = 0.12;
ctx.fillStyle = '#fff';
ctx.fillRect(W/2 - 200, 80, 400, 100);
ctx.fillStyle = '#dc2626';
ctx.fillRect(W/2 - 200, 180, 400, 100);
ctx.globalAlpha = 1;
// Orzeł (uproszczony)
ctx.globalAlpha = 0.08;
ctx.fillStyle = '#fff';
ctx.font = '120px serif';
ctx.textAlign = 'center';
ctx.fillText('🦅', W/2, 200);
ctx.globalAlpha = 1;
// Podłoga sejmowa
const floorGrad = ctx.createLinearGradient(0, GROUND, 0, H);
floorGrad.addColorStop(0, '#4a3728');
floorGrad.addColorStop(0.3, '#3d2d1e');
floorGrad.addColorStop(1, '#2a1f14');
ctx.fillStyle = floorGrad;
ctx.fillRect(0, GROUND, W, H - GROUND);
// Linie podłogi
ctx.strokeStyle = '#5a4738';
ctx.lineWidth = 1;
for (let i = 0; i < 8; i++) {
ctx.beginPath();
ctx.moveTo(0, GROUND + 20 + i * 22);
ctx.lineTo(W, GROUND + 20 + i * 22);
ctx.stroke();
}
// Napis u góry
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = 'rgba(255,255,255,0.15)';
ctx.fillText('SEJM RZECZYPOSPOLITEJ POLSKIEJ SALA POSIEDZEŃ', W/2, GROUND + 15);
// Scrollujący napis propagandowy na dole
const propText = PROPAGANDA[Math.floor(Date.now() / 4000) % PROPAGANDA.length];
ctx.font = 'bold 13px Arial';
ctx.fillStyle = 'rgba(255,200,50,0.25)';
ctx.fillText(propText, W/2, H - 10);
}
// ===================== RYSOWANIE PORTRETÓW =====================
function drawPortrait(x, y, size, charData, selected, hovered) {
// Obramowanie
ctx.strokeStyle = selected ? '#fbbf24' : (hovered ? '#fff' : (charData.faction === 'good' ? '#3b82f6' : '#ef4444'));
ctx.lineWidth = selected ? 3 : (hovered ? 2 : 1);
ctx.strokeRect(x, y, size, size);
// Tło
ctx.fillStyle = charData.faction === 'good' ? 'rgba(30,58,138,0.8)' : 'rgba(127,29,29,0.8)';
ctx.fillRect(x+1, y+1, size-2, size-2);
// Zdjęcie lub inicjały
if (charData.photoLoaded && charData.photo) {
ctx.save();
ctx.beginPath();
ctx.rect(x+3, y+3, size-6, size-6);
ctx.clip();
const imgSize = Math.max(size-6, size-6);
ctx.drawImage(charData.photo, x+3, y+3, imgSize, imgSize);
ctx.restore();
} else {
ctx.fillStyle = charData.faction === 'good' ? '#1e40af' : '#991b1b';
ctx.fillRect(x+3, y+3, size-6, size-6);
ctx.fillStyle = '#fff';
ctx.font = `bold ${Math.floor(size*0.35)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(charData.initials, x + size/2, y + size/2);
}
// Specjalna ramka
if (charData.isSpecial) {
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 2;
ctx.strokeRect(x-1, y-1, size+2, size+2);
// Label
ctx.fillStyle = charData.type === 'ULTIMATE BOSS' ? '#ef4444' : '#fbbf24';
ctx.font = 'bold 8px Arial';
ctx.textAlign = 'center';
ctx.fillText(charData.type, x + size/2, y + 10);
}
// Zaznaczenie
if (selected) {
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 3;
ctx.strokeRect(x-2, y-2, size+4, size+4);
// Animowana ramka
const pulse = Math.sin(Date.now() / 200) * 0.3 + 0.7;
ctx.strokeStyle = `rgba(251,191,36,${pulse})`;
ctx.lineWidth = 2;
ctx.strokeRect(x-4, y-4, size+8, size+8);
}
}
// ===================== EKRAN MENU =====================
function drawMenu() {
// Ciemne tło z efektem
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, W, H);
// Animowane tło flaga
const t = Date.now() / 1000;
ctx.globalAlpha = 0.08;
for (let i = 0; i < 5; i++) {
ctx.fillStyle = i % 2 === 0 ? '#fff' : '#dc2626';
ctx.fillRect(0, 120 + i * 100 + Math.sin(t + i) * 10, W, 100);
}
ctx.globalAlpha = 1;
// Tytuł
ctx.textAlign = 'center';
// Cień
ctx.fillStyle = '#000';
ctx.font = 'bold 72px Arial';
ctx.fillText('SEJM KOMBAT', W/2 + 3, 163);
// Gradient na tekście
const titleGrad = ctx.createLinearGradient(W/2 - 300, 100, W/2 + 300, 200);
titleGrad.addColorStop(0, '#ef4444');
titleGrad.addColorStop(0.5, '#fbbf24');
titleGrad.addColorStop(1, '#ef4444');
ctx.fillStyle = titleGrad;
ctx.font = 'bold 72px Arial';
ctx.fillText('SEJM KOMBAT', W/2, 160);
// Podtytuł
ctx.font = 'bold 28px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText('SUWERENNOŚĆ vs KONDOMINIUM', W/2, 210);
// Opis
ctx.font = '18px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText('Walka Polski suwerennej przeciwko obcemu kondominium rosyjsko-niemieckiemu!', W/2, 260);
// Frakcje
ctx.font = 'bold 20px Arial';
// Dobra strona
ctx.fillStyle = '#3b82f6';
ctx.fillText('🇵🇱 OBROŃCY SUWERENNOŚCI 🇵🇱', W/4, 320);
ctx.font = '14px Arial';
ctx.fillStyle = '#60a5fa';
ctx.fillText('PiS • Patrioci • Polska Wolna', W/4, 345);
ctx.fillText('Kaczyński • Morawiecki • Nawrocki • Lech Kaczyński', W/4, 365);
// Zła strona
ctx.font = 'bold 20px Arial';
ctx.fillStyle = '#ef4444';
ctx.fillText('☠ KONDOMINIUM ZDRAJCÓW ☠', W*3/4, 320);
ctx.font = '14px Arial';
ctx.fillStyle = '#f87171';
ctx.fillText('Platforma • Lewica • Słudzy Merkel i Putina', W*3/4, 345);
ctx.fillText('Tusk • Czarzasty • Merkel • Putin', W*3/4, 365);
// VS
ctx.font = 'bold 36px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText('VS', W/2, 340);
// Propaganda (zmienia się co kilka sekund)
const propIdx = Math.floor(Date.now() / 3000) % PROPAGANDA.length;
ctx.font = 'bold 16px Arial';
const pulse = Math.sin(Date.now() / 500) * 0.3 + 0.7;
ctx.fillStyle = `rgba(251,191,36,${pulse})`;
ctx.fillText(`« ${PROPAGANDA[propIdx]} »`, W/2, 420);
// Sterowanie
ctx.font = '14px Arial';
ctx.fillStyle = '#64748b';
ctx.fillText('STEROWANIE: Strzałki = ruch/skok/kucanie | Q/W = cios lekki/ciężki', W/2, 480);
ctx.fillText('A/S = kopnięcie lekkie/ciężkie | Z = blok | X = cios specjalny (pełna moc)', W/2, 500);
ctx.fillText('P = pauza | ESC = menu', W/2, 520);
// Start
const startPulse = Math.sin(Date.now() / 300) * 0.4 + 0.6;
ctx.font = `bold 28px Arial`;
ctx.fillStyle = `rgba(255,255,255,${startPulse})`;
ctx.fillText('>>> NACIŚNIJ ENTER ABY ROZPOCZĄĆ <<<', W/2, 590);
// Stopka
ctx.font = '12px Arial';
ctx.fillStyle = '#334155';
ctx.fillText('Gra satyryczna • Wszystkie postacie fikcyjne (w pewnym sensie)', W/2, 680);
ctx.fillText('SEJM KOMBAT © 2024 Suwerenność górą!', W/2, 700);
}
function updateMenu() {
if (consumeKey('Enter') || consumeKey('Space')) {
gameState = 'SELECT';
selectCursor = {col:0, row:0, side:0};
selectedPlayer = null;
selectedEnemy = null;
enterCooldown = 15;
playMenuSelect();
announce('Wybierz swojego wojownika!');
}
}
// ===================== EKRAN WYBORU POSTACI =====================
function getSelectGrid() {
// 5 kolumn x 3 rzędy regularnych + 1 rząd specjalnych na każdej stronie
const grid = {good: [], bad: []};
for (let i = 0; i < GOOD_CHARS.length; i++) {
grid.good.push({char: GOOD_CHARS[i], col: i % 5, row: Math.floor(i/5)});
}
SPECIAL_GOOD.forEach((c, i) => {
grid.good.push({char: c, col: i * 2 + 0.5, row: 3, isSpecial: true});
});
for (let i = 0; i < BAD_CHARS.length; i++) {
grid.bad.push({char: BAD_CHARS[i], col: i % 5, row: Math.floor(i/5)});
}
SPECIAL_BAD.forEach((c, i) => {
grid.bad.push({char: c, col: i * 2 + 0.5, row: 3, isSpecial: true});
});
return grid;
}
function getSelectedChar() {
const s = selectCursor;
const list = s.side === 0 ? ALL_GOOD : ALL_BAD;
const regularCount = s.side === 0 ? GOOD_CHARS.length : BAD_CHARS.length;
const specialList = s.side === 0 ? SPECIAL_GOOD : SPECIAL_BAD;
if (s.row < 3) {
const idx = s.row * 5 + s.col;
if (idx < regularCount) return list[idx];
} else if (s.row === 3) {
const sIdx = Math.floor(s.col / 3);
if (sIdx < specialList.length) return specialList[sIdx];
}
return list[0];
}
function drawSelect() {
// Tło
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, W, H);
// Tytuł
ctx.textAlign = 'center';
ctx.font = 'bold 32px Arial';
const tGrad = ctx.createLinearGradient(W/2-200, 0, W/2+200, 0);
tGrad.addColorStop(0, '#3b82f6');
tGrad.addColorStop(0.5, '#fbbf24');
tGrad.addColorStop(1, '#ef4444');
ctx.fillStyle = tGrad;
ctx.fillText('WYBIERZ SWOJEGO WOJOWNIKA', W/2, 35);
// Propaganda
ctx.font = 'bold 12px Arial';
ctx.fillStyle = '#fbbf24';
const prop = PROPAGANDA[Math.floor(Date.now()/4000) % PROPAGANDA.length];
ctx.fillText(prop, W/2, 55);
// Lewa strona DOBRZY (Suwerenność)
const leftX = 20;
const gridY = 75;
const cellW = 105;
const cellH = 90;
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#3b82f6';
ctx.fillText('🇵🇱 OBROŃCY SUWERENNOŚCI (PiS) 🇵🇱', leftX + 265, gridY - 5);
// Rysuj siatkę dobrych
for (let i = 0; i < GOOD_CHARS.length; i++) {
const col = i % 5;
const row = Math.floor(i / 5);
const cx = leftX + col * cellW + 10;
const cy = gridY + row * cellH + 5;
const isHovered = selectCursor.side === 0 && selectCursor.row === row && selectCursor.col === col;
const isSelected = selectedPlayer === GOOD_CHARS[i];
drawPortrait(cx, cy, 60, GOOD_CHARS[i], isSelected, isHovered);
// Imię
ctx.font = '9px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = isHovered ? '#fbbf24' : '#94a3b8';
const shortName = GOOD_CHARS[i].name.split(' ').slice(-1)[0];
ctx.fillText(shortName, cx + 30, cy + 73);
ctx.fillText(GOOD_CHARS[i].nick, cx + 30, cy + 83);
}
// Specjalne dobre
for (let i = 0; i < SPECIAL_GOOD.length; i++) {
const cx = leftX + 70 + i * 200;
const cy = gridY + 3 * cellH + 15;
const isHovered = selectCursor.side === 0 && selectCursor.row === 3 && Math.floor(selectCursor.col/3) === i;
const isSelected = selectedPlayer === SPECIAL_GOOD[i];
drawPortrait(cx, cy, 75, SPECIAL_GOOD[i], isSelected, isHovered);
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#fbbf24';
ctx.fillText(SPECIAL_GOOD[i].type, cx + 37, cy + 85);
ctx.fillStyle = isHovered ? '#fbbf24' : '#e2e8f0';
ctx.font = '9px Arial';
ctx.fillText(SPECIAL_GOOD[i].name, cx + 37, cy + 96);
}
// Prawa strona ŹLI (Kondominium)
const rightX = W/2 + 100;
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#ef4444';
ctx.fillText('☠ KONDOMINIUM ZDRAJCÓW (KO/Lewica) ☠', rightX + 265, gridY - 5);
for (let i = 0; i < BAD_CHARS.length; i++) {
const col = i % 5;
const row = Math.floor(i / 5);
const cx = rightX + col * cellW + 10;
const cy = gridY + row * cellH + 5;
const isHovered = selectCursor.side === 1 && selectCursor.row === row && selectCursor.col === col;
const isSelected = selectedEnemy === BAD_CHARS[i];
drawPortrait(cx, cy, 60, BAD_CHARS[i], isSelected, isHovered);
ctx.font = '9px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = isHovered ? '#fbbf24' : '#94a3b8';
const shortName = BAD_CHARS[i].name.split(' ').slice(-1)[0];
ctx.fillText(shortName, cx + 30, cy + 73);
ctx.fillText(BAD_CHARS[i].nick, cx + 30, cy + 83);
}
// Specjalne złe
for (let i = 0; i < SPECIAL_BAD.length; i++) {
const cx = rightX + 70 + i * 200;
const cy = gridY + 3 * cellH + 15;
const isHovered = selectCursor.side === 1 && selectCursor.row === 3 && Math.floor(selectCursor.col/3) === i;
const isSelected = selectedEnemy === SPECIAL_BAD[i];
drawPortrait(cx, cy, 75, SPECIAL_BAD[i], isSelected, isHovered);
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = SPECIAL_BAD[i].type === 'ULTIMATE BOSS' ? '#ef4444' : '#fbbf24';
ctx.fillText(SPECIAL_BAD[i].type, cx + 37, cy + 85);
ctx.fillStyle = isHovered ? '#fbbf24' : '#e2e8f0';
ctx.font = '9px Arial';
ctx.fillText(SPECIAL_BAD[i].name, cx + 37, cy + 96);
}
// Panel centralny VS
const centerX = W/2 - 55;
ctx.fillStyle = 'rgba(30,30,50,0.8)';
ctx.fillRect(centerX - 15, gridY, 140, 350);
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 1;
ctx.strokeRect(centerX - 15, gridY, 140, 350);
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#fbbf24';
ctx.fillText('VS', centerX + 55, gridY + 190);
// Wybrana postać info
const selChar = getSelectedChar();
if (selChar) {
ctx.font = 'bold 11px Arial';
ctx.fillStyle = '#fff';
ctx.fillText(selChar.name, centerX + 55, gridY + 30);
ctx.font = '10px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText(`"${selChar.nick}"`, centerX + 55, gridY + 45);
// Statystyki
ctx.font = '10px Arial';
ctx.textAlign = 'left';
const sx = centerX;
ctx.fillStyle = '#ef4444'; ctx.fillText(`ATK: ${selChar.atk}`, sx, gridY + 65);
ctx.fillStyle = '#3b82f6'; ctx.fillText(`DEF: ${selChar.def}`, sx, gridY + 78);
ctx.fillStyle = '#22c55e'; ctx.fillText(`SPD: ${selChar.spd}`, sx, gridY + 91);
ctx.fillStyle = '#fbbf24'; ctx.fillText(`HP: ${selChar.hp}`, sx, gridY + 104);
ctx.fillStyle = '#e879f9'; ctx.fillText(`SP: ${selChar.special}`, sx, gridY + 117);
ctx.textAlign = 'center';
// Wybrany gracz (dół)
if (selectedPlayer) {
ctx.font = 'bold 12px Arial';
ctx.fillStyle = '#3b82f6';
ctx.fillText('GRACZ:', centerX + 55, gridY + 250);
ctx.fillStyle = '#fff';
ctx.font = '10px Arial';
ctx.fillText(selectedPlayer.name, centerX + 55, gridY + 265);
}
if (selectedEnemy) {
ctx.font = 'bold 12px Arial';
ctx.fillStyle = '#ef4444';
ctx.fillText('WRÓG:', centerX + 55, gridY + 290);
ctx.fillStyle = '#fff';
ctx.font = '10px Arial';
ctx.fillText(selectedEnemy.name, centerX + 55, gridY + 305);
}
}
// Instrukcja
ctx.textAlign = 'center';
ctx.font = '13px Arial';
ctx.fillStyle = '#64748b';
ctx.fillText('Strzałki = wybór | TAB = zmiana frakcji | ENTER = zatwierdź i posłuchaj opisu | ESC = menu', W/2, H - 40);
ctx.font = 'bold 13px Arial';
ctx.fillStyle = '#fbbf24';
if (!selectedPlayer) {
ctx.fillText('Wybierz swoją postać (z dowolnej frakcji)!', W/2, H - 18);
} else if (!selectedEnemy) {
ctx.fillText('AI wybiera przeciwnika z przeciwnej frakcji...', W/2, H - 18);
} else {
const sp = Math.sin(Date.now()/300) * 0.4 + 0.6;
ctx.fillStyle = `rgba(251,191,36,${sp})`;
ctx.fillText('NACIŚNIJ ENTER ABY WALCZYĆ!', W/2, H - 18);
}
}
function updateSelect() {
if (enterCooldown > 0) { enterCooldown--; return; }
const s = selectCursor;
let movedCursor = false;
// Nawigacja kursorem
if (consumeKey('ArrowRight')) {
const oldCol = s.col;
s.col = Math.min(4, s.col + 1);
movedCursor = movedCursor || oldCol !== s.col;
playMenuMove();
}
if (consumeKey('ArrowLeft')) {
const oldCol = s.col;
s.col = Math.max(0, s.col - 1);
movedCursor = movedCursor || oldCol !== s.col;
playMenuMove();
}
if (consumeKey('ArrowDown')) {
const oldRow = s.row;
s.row = Math.min(3, s.row + 1);
movedCursor = movedCursor || oldRow !== s.row;
playMenuMove();
}
if (consumeKey('ArrowUp')) {
const oldRow = s.row;
s.row = Math.max(0, s.row - 1);
movedCursor = movedCursor || oldRow !== s.row;
playMenuMove();
}
// TAB zmienia stronę
if (consumeKey('Tab')) {
s.side = 1 - s.side;
movedCursor = true;
playMenuMove();
}
if (movedCursor) lastPreviewedChar = getSelectedChar();
// ESC wraca do menu
if (consumeKey('Escape')) {
gameState = 'MENU';
return;
}
// ENTER zatwierdza wybór
if (consumeKey('Enter') || consumeKey('Space')) {
if (!selectedPlayer) {
selectedPlayer = getSelectedChar();
playMenuSelect();
announceCharacter(selectedPlayer, 'Wybrano wojownika');
// AI automatycznie wybiera z przeciwnej frakcji
setTimeout(() => {
const enemyList = selectedPlayer.faction === 'good' ? ALL_BAD : ALL_GOOD;
selectedEnemy = enemyList[Math.floor(Math.random() * enemyList.length)];
playMenuSelect();
announceCharacter(selectedEnemy, 'Przeciwnik');
}, 500);
} else if (selectedEnemy) {
// Rozpocznij walkę!
startFight();
}
}
}
// ===================== JAN PAWEŁ II LOSOWE WSPARCIE =====================
const JP2_TEXTS = [
"Nie lękajcie się! Walczcie dalej!",
"Musicie od siebie wymagać, nawet gdy inni od was nie wymagają.",
"Niech zstąpi Duch Twój i odnowi oblicze tej ziemi.",
"Brońcie prawdy, wolności i Ojczyzny.",
"Odwagi! Suwerenność potrzebuje wytrwałości.",
"Polska potrzebuje ludzi sumienia. Do walki!"
];
function randomJP2Text() {
return JP2_TEXTS[Math.floor(Math.random() * JP2_TEXTS.length)];
}
function resetJP2() {
jp2.active = false;
jp2.timer = 0;
jp2.cooldown = 0;
// Gwarantowany pierwszy spawn po ok. 3 sekundach realnej walki.
// Kolejne wejścia są planowane licznikowo, więc nie ma już ryzyka, że RNG nigdy go nie pokaże.
jp2.nextSpawn = 180;
jp2.text = '';
jp2.introPulse = 0;
}
function buffGoodFighter(f) {
if (!f || !f.data || f.data.faction !== 'good' || f.state === 'ko') return;
f.power = Math.min(f.maxPower, f.power + 25);
f.hp = Math.min(f.maxHp, f.hp + 8);
spawnParticles(f.x, f.y - 80, '#fbbf24', 18, 5);
addFloatingText(f.x, f.y - 145, '+ DUCH WALKI', '#fbbf24', 22);
}
function spawnJP2() {
jp2.active = true;
jp2.timer = jp2.duration;
jp2.cooldown = 0;
// Następne wejście po 814 sekundach walki, zawsze licznikowo.
jp2.nextSpawn = 480 + Math.floor(Math.random() * 360);
jp2.x = 180 + Math.random() * (W - 360);
jp2.y = 105 + Math.random() * 70;
jp2.text = randomJP2Text();
jp2.introPulse = 1;
buffGoodFighter(player1);
buffGoodFighter(player2);
playChoirSound();
addFloatingText(jp2.x, jp2.y + 115, 'JAN PAWEŁ II ZSTĘPUJE Z NIEBA!', '#fff', 20);
announce('Jan Paweł drugi mówi: ' + jp2.text);
}
function updateJP2() {
if (gameState !== 'FIGHT' || fightResult) return;
if (jp2.active) {
jp2.timer--;
jp2.introPulse = Math.max(0, jp2.introPulse - 0.018);
if (jp2.timer <= 0) {
jp2.active = false;
jp2.cooldown = 0;
}
return;
}
jp2.cooldown++;
// Gwarantowany spawn: licznik, nie prawdopodobieństwo na klatkę.
if (jp2.cooldown >= jp2.nextSpawn) spawnJP2();
}
function drawJP2() {
if (!jp2.active) return;
const elapsed = jp2.duration - jp2.timer;
const fadeIn = Math.min(1, elapsed / 80);
const fadeOut = Math.min(1, jp2.timer / 70);
const alpha = Math.min(fadeIn, fadeOut);
// Wejście z nieba: start wysoko nad kadrem, potem zjazd + zoom + delikatne unoszenie.
const easeOut = 1 - Math.pow(1 - fadeIn, 4);
const float = Math.sin(Date.now() / 320) * 8;
const heavenStartY = -135;
const drawY = heavenStartY + (jp2.y - heavenStartY) * easeOut + float;
const scale = (0.35 + 0.75 * easeOut) * (1 + Math.sin(Date.now() / 180) * 0.045);
const size = 142 * scale;
const halo = size / 2 + 24 + Math.sin(Date.now() / 220) * 8;
ctx.save();
// Krótki biało-złoty rozbłysk na samym początku pojawienia.
if (jp2.introPulse > 0) {
const flash = jp2.introPulse * jp2.introPulse;
ctx.globalAlpha = alpha;
ctx.fillStyle = `rgba(255,255,220,${0.10 * flash})`;
ctx.fillRect(0, 0, W, H);
}
// Miękkie światło na całą scenę, jak reflektor z góry.
ctx.globalAlpha = 0.09 * alpha;
const topLight = ctx.createLinearGradient(jp2.x, 0, jp2.x, GROUND);
topLight.addColorStop(0, 'rgba(255,255,255,0.9)');
topLight.addColorStop(0.35, 'rgba(251,191,36,0.22)');
topLight.addColorStop(1, 'rgba(251,191,36,0)');
ctx.fillStyle = topLight;
ctx.beginPath();
ctx.moveTo(jp2.x - 95, 0);
ctx.lineTo(jp2.x + 95, 0);
ctx.lineTo(jp2.x + 235, GROUND);
ctx.lineTo(jp2.x - 235, GROUND);
ctx.closePath();
ctx.fill();
// Promienie za postacią.
ctx.save();
ctx.translate(jp2.x, drawY);
ctx.rotate(Date.now() / 2600);
for (let i = 0; i < 14; i++) {
ctx.rotate((Math.PI * 2) / 14);
ctx.globalAlpha = 0.09 * alpha;
ctx.fillStyle = '#fff7cc';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(18, -210);
ctx.lineTo(-18, -210);
ctx.closePath();
ctx.fill();
}
ctx.restore();
// Złoty glow / aura.
ctx.globalAlpha = 0.38 * alpha;
const light = ctx.createRadialGradient(jp2.x, drawY, 8, jp2.x, drawY, 260);
light.addColorStop(0, 'rgba(255,255,255,0.95)');
light.addColorStop(0.24, 'rgba(251,191,36,0.55)');
light.addColorStop(0.62, 'rgba(251,191,36,0.18)');
light.addColorStop(1, 'rgba(251,191,36,0)');
ctx.fillStyle = light;
ctx.beginPath();
ctx.arc(jp2.x, drawY, 260, 0, Math.PI * 2);
ctx.fill();
// Aureola.
ctx.globalAlpha = alpha;
ctx.shadowColor = '#fff7ed';
ctx.shadowBlur = 45;
ctx.strokeStyle = 'rgba(255,255,255,0.95)';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(jp2.x, drawY, halo, 0, Math.PI * 2);
ctx.stroke();
ctx.strokeStyle = 'rgba(251,191,36,0.95)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(jp2.x, drawY, halo + 8, 0, Math.PI * 2);
ctx.stroke();
// Główna grafika osadzona w base64, bez pobierania pliku.
if (jp2.img) {
ctx.save();
ctx.shadowColor = '#fff7ed';
ctx.shadowBlur = 32;
ctx.drawImage(jp2.img, jp2.x - size/2, drawY - size/2, size, size);
ctx.restore();
} else {
ctx.fillStyle = '#fbbf24';
ctx.beginPath();
ctx.arc(jp2.x, drawY, size/2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#111';
ctx.font = 'bold 28px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('JP2', jp2.x, drawY);
ctx.textBaseline = 'alphabetic';
}
ctx.shadowBlur = 0;
ctx.font = 'bold 18px Arial';
ctx.textAlign = 'center';
ctx.strokeStyle = '#000';
ctx.lineWidth = 4;
ctx.strokeText('JAN PAWEŁ II', jp2.x, drawY + size/2 + 30);
ctx.fillStyle = '#fff';
ctx.fillText('JAN PAWEŁ II', jp2.x, drawY + size/2 + 30);
ctx.font = 'bold 16px Arial';
const msgY = drawY + size/2 + 56;
ctx.strokeText('„' + jp2.text + '”', jp2.x, msgY);
ctx.fillStyle = '#fbbf24';
ctx.fillText('„' + jp2.text + '”', jp2.x, msgY);
ctx.restore();
}
// ===================== INTRO BOSSÓW I SPECJALNE ANIMACJE =====================
function isBossCharacter(c) {
return !!(c && BOSS_NAMES.includes(c.name));
}
function getBossForIntro() {
if (isBossCharacter(selectedEnemy)) return selectedEnemy;
if (isBossCharacter(selectedPlayer)) return selectedPlayer;
return null;
}
function resetBossIntro() {
bossIntro.active = false;
bossIntro.char = null;
bossIntro.timer = 0;
bossIntro.announced = false;
}
function startBossIntro(charData) {
if (!charData) { resetBossIntro(); return; }
bossIntro.active = true;
bossIntro.char = charData;
bossIntro.timer = bossIntro.duration;
bossIntro.announced = true;
shakeX = 0; shakeY = 0;
addFloatingText(W/2, H/2 - 115, 'BOSS FIGHT', '#ef4444', 54);
announce('Boss fight! Nadchodzi ' + charData.name + '. ' + (charData.bio || 'Przygotuj się do walki.'));
}
function updateBossIntro() {
if (!bossIntro.active) return false;
bossIntro.timer--;
const c = bossIntro.char;
if (c) {
const color = c.name === 'Angela Merkel' ? '#facc15' : (c.name === 'Władimir Putin' ? '#ef4444' : '#60a5fa');
if (Math.random() < 0.42) spawnParticles(W/2 + (Math.random()-0.5)*260, H/2 + (Math.random()-0.5)*160, color, 1, 5);
}
shakeX = (Math.random() - 0.5) * 3;
shakeY = (Math.random() - 0.5) * 2;
if (bossIntro.timer <= 0) {
bossIntro.active = false;
shakeX = 0; shakeY = 0;
addFloatingText(W/2, H/2 - 50, 'WALCZ!', '#fbbf24', 58);
}
return true;
}
function drawBossPortraitCentered(charData, cx, cy, size) {
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, size/2, 0, Math.PI * 2);
ctx.clip();
if (charData && charData.photoLoaded && charData.photo) {
ctx.drawImage(charData.photo, cx - size/2, cy - size/2, size, size);
} else if (charData && charData.photo) {
ctx.drawImage(charData.photo, cx - size/2, cy - size/2, size, size);
} else {
ctx.fillStyle = '#111827';
ctx.fillRect(cx - size/2, cy - size/2, size, size);
ctx.fillStyle = '#fff';
ctx.font = 'bold 44px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(charData ? charData.initials : 'BOSS', cx, cy);
ctx.textBaseline = 'alphabetic';
}
ctx.restore();
}
function drawBossIntro() {
if (!bossIntro.active || !bossIntro.char) return;
const c = bossIntro.char;
const elapsed = bossIntro.duration - bossIntro.timer;
const fadeIn = Math.min(1, elapsed / 38);
const fadeOut = Math.min(1, bossIntro.timer / 38);
const alpha = Math.min(fadeIn, fadeOut);
const zoom = 0.58 + 0.48 * (1 - Math.pow(1 - fadeIn, 3)) + Math.sin(Date.now()/90) * 0.018;
const size = 250 * zoom;
const color = c.name === 'Angela Merkel' ? '#facc15' : (c.name === 'Władimir Putin' ? '#ef4444' : '#3b82f6');
ctx.save();
ctx.globalAlpha = 0.72 * alpha;
ctx.fillStyle = '#030712';
ctx.fillRect(0, 0, W, H);
ctx.save();
ctx.translate(W/2, H/2);
ctx.rotate(Date.now()/900);
for (let i = 0; i < 18; i++) {
ctx.rotate(Math.PI * 2 / 18);
ctx.globalAlpha = 0.12 * alpha;
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-28, -680);
ctx.lineTo(28, -680);
ctx.closePath();
ctx.fill();
}
ctx.restore();
ctx.globalAlpha = alpha;
ctx.shadowColor = color;
ctx.shadowBlur = 55;
ctx.strokeStyle = color;
ctx.lineWidth = 8;
ctx.beginPath();
ctx.arc(W/2, H/2 - 20, size/2 + 14, 0, Math.PI * 2);
ctx.stroke();
drawBossPortraitCentered(c, W/2, H/2 - 20, size);
ctx.shadowBlur = 0;
const pulse = 1 + Math.sin(Date.now()/95) * 0.055;
ctx.save();
ctx.translate(W/2, 130);
ctx.scale(pulse, pulse);
ctx.textAlign = 'center';
ctx.font = 'bold 76px Arial Black, Arial';
ctx.lineWidth = 8;
ctx.strokeStyle = '#000';
ctx.strokeText('BOSS FIGHT', 0, 0);
ctx.fillStyle = '#ef4444';
ctx.fillText('BOSS FIGHT', 0, 0);
ctx.restore();
ctx.textAlign = 'center';
ctx.font = 'bold 36px Arial';
ctx.lineWidth = 5;
ctx.strokeStyle = '#000';
ctx.strokeText(c.name, W/2, H/2 + size/2 + 55);
ctx.fillStyle = '#fff';
ctx.fillText(c.name, W/2, H/2 + size/2 + 55);
ctx.font = 'bold 22px Arial';
ctx.strokeText('SPECJALNY WOJOWNIK: ' + c.special, W/2, H/2 + size/2 + 88);
ctx.fillStyle = '#fbbf24';
ctx.fillText('SPECJALNY WOJOWNIK: ' + c.special, W/2, H/2 + size/2 + 88);
ctx.restore();
}
function drawPutinEffect(f) {
const t = Date.now()/1000;
ctx.save();
ctx.globalAlpha = 0.78;
ctx.shadowColor = '#ef4444';
ctx.shadowBlur = 28;
ctx.strokeStyle = 'rgba(239,68,68,0.9)';
ctx.lineWidth = 4;
for (let i = 0; i < 3; i++) {
const r = 38 + i * 16 + Math.sin(t*5+i) * 5;
ctx.beginPath();
ctx.arc(f.x, f.y - 68, r, 0, Math.PI * 2);
ctx.stroke();
}
ctx.lineWidth = 5;
for (let i = 0; i < 4; i++) {
const off = -48 + i * 32 + Math.sin(t*6+i)*6;
ctx.beginPath();
ctx.moveTo(f.x + off, f.y - 140);
ctx.lineTo(f.x + off + 22, f.y - 40);
ctx.stroke();
}
ctx.font = 'bold 26px serif';
ctx.textAlign = 'center';
ctx.fillStyle = 'rgba(239,68,68,0.85)';
ctx.fillText('🐻', f.x + Math.sin(t*2)*55, f.y - 138 + Math.cos(t*3)*10);
ctx.restore();
}
function drawMerkelEffect(f) {
const t = Date.now()/1000;
ctx.save();
ctx.globalAlpha = 0.86;
ctx.shadowColor = '#facc15';
ctx.shadowBlur = 30;
ctx.strokeStyle = 'rgba(250,204,21,0.95)';
ctx.lineWidth = 3;
const cx = f.x, cy = f.y - 82;
ctx.beginPath();
ctx.arc(cx, cy, 64 + Math.sin(t*4)*6, 0, Math.PI*2);
ctx.stroke();
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#facc15';
for (let i = 0; i < 12; i++) {
const a = t*1.7 + i * Math.PI*2/12;
ctx.fillText('★', cx + Math.cos(a)*74, cy + Math.sin(a)*74);
}
ctx.lineWidth = 2;
for (let i = 0; i < 5; i++) {
const a = t*3 + i;
ctx.beginPath();
ctx.moveTo(cx + Math.cos(a)*35, cy + Math.sin(a)*35);
ctx.lineTo(cx + Math.cos(a+0.8)*95, cy + Math.sin(a+0.8)*95);
ctx.stroke();
}
ctx.font = 'bold 22px Arial';
ctx.fillText('€', cx + Math.sin(t*2)*48, cy - 86 + Math.cos(t*3)*8);
ctx.restore();
}
function drawSpecialBossEffects() {
if (!player1 || !player2 || fightResult) return;
[player1, player2].forEach(f => {
if (!f || !f.data || f.state === 'ko') return;
if (f.data.name === 'Władimir Putin') drawPutinEffect(f);
if (f.data.name === 'Angela Merkel') drawMerkelEffect(f);
});
}
// ===================== ROZPOCZĘCIE WALKI =====================
function startFight() {
gameState = 'FIGHT';
player1 = new Fighter(selectedPlayer, 250, 1);
player2 = new Fighter(selectedEnemy, W - 250, -1);
ai = new AIController(player2, player1);
roundTimer = 99;
roundTimerAccum = 0;
particles = [];
floatingTexts = [];
fightResult = '';
victoryTimer = 0;
victorySpeechDone = false;
shakeX = 0; shakeY = 0;
resetJP2();
const introBoss = getBossForIntro();
if (introBoss) startBossIntro(introBoss);
else resetBossIntro();
playMenuSelect();
ensureAudio();
if (!mazurekPlaying) playMazurek();
// Ogłoszenie
addFloatingText(W/2, H/2 - 50, 'WALKA!', '#fbbf24', 60);
const goodName = selectedPlayer.faction === 'good' ? selectedPlayer.name : selectedEnemy.name;
const badName = selectedPlayer.faction === 'bad' ? selectedPlayer.name : selectedEnemy.name;
addFloatingText(W/2, H/2 + 20, `${goodName} vs ${badName}`, '#fff', 22);
addFloatingText(W/2, H/2 + 50, 'Suwerenność kontra Kondominium!', '#fbbf24', 16);
announce('Walka! Suwerenność kontra Kondominium!');
}
// ===================== LOGIKA WALKI =====================
function updateFight() {
if (fightResult) {
victoryTimer++;
if (victoryTimer > 180 && (consumeKey('Enter') || consumeKey('Space'))) {
gameState = 'VICTORY';
victoryTimer = 0;
victorySpeechDone = false;
}
// Nadal aktualizuj cząsteczki
updateParticles();
// Screen shake wygasza się
shakeX *= 0.9; shakeY *= 0.9;
return;
}
// Intro bossa zatrzymuje rundę i sterowanie na kilka sekund.
if (bossIntro.active) {
updateBossIntro();
updateParticles();
shakeX *= 0.88; shakeY *= 0.88;
return;
}
// Timer rundy
roundTimerAccum++;
if (roundTimerAccum >= 60) {
roundTimerAccum = 0;
roundTimer--;
if (roundTimer <= 0) {
// Czas się skończył
if (player1.hp > player2.hp) endFight('P1WIN');
else if (player2.hp > player1.hp) endFight('P2WIN');
else endFight('DRAW');
return;
}
}
// Sterowanie gracza (Player 1)
if (player1.state !== 'ko') {
const attackPressed = !!(justPressed['KeyQ'] || justPressed['KeyW'] || justPressed['KeyA'] || justPressed['KeyS'] || justPressed['KeyX']);
// Blok jest obsługiwany poza canAct(), żeby puszczenie Z zawsze odblokowywało postać.
if (keys['KeyZ']) {
player1.block();
if (attackPressed && player1.counterReady) {
player1.performCounter(player2);
justPressed['KeyQ']=justPressed['KeyW']=justPressed['KeyA']=justPressed['KeyS']=justPressed['KeyX']=false;
}
} else {
if (player1.state === 'block') player1.unblock();
if (attackPressed && player1.counterReady) {
player1.performCounter(player2);
justPressed['KeyQ']=justPressed['KeyW']=justPressed['KeyA']=justPressed['KeyS']=justPressed['KeyX']=false;
}
}
if (player1.canAct()) {
// Ruch
if (keys['ArrowLeft']) {
player1.vx -= 2.5 * (player1.data.spd / 8);
player1.state = 'walk';
} else if (keys['ArrowRight']) {
player1.vx += 2.5 * (player1.data.spd / 8);
player1.state = 'walk';
} else if (player1.state === 'walk' && player1.isGrounded) {
player1.state = 'idle';
}
// Skok
if (keys['ArrowUp'] && player1.isGrounded) {
player1.vy = -14;
player1.state = 'jump';
player1.isGrounded = false;
}
// Kucanie
if (keys['ArrowDown'] && player1.isGrounded) {
player1.state = 'crouch';
}
// Ataki (Q, W, A, S, X) — tylko gdy nie trzymasz bloku i nie wykonano countera
if (!keys['KeyZ'] && !player1.counterReady) {
if (justPressed['KeyQ']) { justPressed['KeyQ'] = false; player1.startAttack('lpunch'); }
else if (justPressed['KeyW']) { justPressed['KeyW'] = false; player1.startAttack('hpunch'); }
else if (justPressed['KeyA']) { justPressed['KeyA'] = false; player1.startAttack('lkick'); }
else if (justPressed['KeyS']) { justPressed['KeyS'] = false; player1.startAttack('hkick'); }
else if (justPressed['KeyX']) { justPressed['KeyX'] = false; player1.startAttack('special'); }
}
}
}
// Facing gracze zwróceni do siebie
if (player1.x < player2.x) {
player1.facing = 1;
player2.facing = -1;
} else {
player1.facing = -1;
player2.facing = 1;
}
// AI steruje graczem 2
ai.update();
// Aktualizacja wojowników
player1.update();
player2.update();
// Kolizje ataków
checkAttackCollision(player1, player2);
checkAttackCollision(player2, player1);
// Kolizja ciał (push away)
const dx = player1.x - player2.x;
const dist = Math.abs(dx);
if (dist < 50) {
const push = (50 - dist) / 2;
const dir = dx > 0 ? 1 : -1;
player1.x += dir * push * 0.5;
player2.x -= dir * push * 0.5;
}
// Sprawdź KO
if (player1.state === 'ko' && player1.isGrounded && !fightResult) {
endFight('P2WIN');
}
if (player2.state === 'ko' && player2.isGrounded && !fightResult) {
endFight('P1WIN');
}
// Losowe wsparcie Jana Pawła II
updateJP2();
// Efekty
updateParticles();
shakeX *= 0.85;
shakeY *= 0.85;
}
function checkAttackCollision(attacker, defender) {
const atkBox = attacker.getAttackBox();
if (!atkBox) return;
const defBox = defender.getHitbox();
// AABB kolizja
if (atkBox.x < defBox.x + defBox.w && atkBox.x + atkBox.w > defBox.x &&
atkBox.y < defBox.y + defBox.h && atkBox.y + atkBox.h > defBox.y) {
attacker.attackHitThisMove = true;
let dmg = attacker.attackData.dmg;
// Combo bonus
if (attacker.combo > 1) dmg *= (1 + attacker.combo * 0.1);
defender.takeDamage(dmg, attacker);
}
}
function endFight(result) {
fightResult = result;
victoryTimer = 0;
stopMazurek();
playKOSound();
// Wielki napis
if (result === 'P1WIN') {
addFloatingText(W/2, H/2 - 60, 'K.O.!', '#fbbf24', 72);
const isGoodWin = selectedPlayer.faction === 'good';
const vTxt = isGoodWin ? VICTORY_GOOD[Math.floor(Math.random()*VICTORY_GOOD.length)]
: VICTORY_BAD[Math.floor(Math.random()*VICTORY_BAD.length)];
addFloatingText(W/2, H/2 + 10, selectedPlayer.name + ' WYGRYWA!', '#fff', 28);
addFloatingText(W/2, H/2 + 50, vTxt, '#fbbf24', 18);
if (isGoodWin) announce('Suwerenność zwycięża! Lokaj pokonany!');
else announce('Kondominium tymczasowo górą!');
} else if (result === 'P2WIN') {
addFloatingText(W/2, H/2 - 60, 'K.O.!', '#ef4444', 72);
const isGoodWin = selectedEnemy.faction === 'good';
const vTxt = isGoodWin ? VICTORY_GOOD[Math.floor(Math.random()*VICTORY_GOOD.length)]
: VICTORY_BAD[Math.floor(Math.random()*VICTORY_BAD.length)];
addFloatingText(W/2, H/2 + 10, selectedEnemy.name + ' WYGRYWA!', '#fff', 28);
addFloatingText(W/2, H/2 + 50, vTxt, '#fbbf24', 18);
} else {
addFloatingText(W/2, H/2, 'REMIS!', '#fbbf24', 60);
}
// Fatality check
const winner = result === 'P1WIN' ? player1 : (result === 'P2WIN' ? player2 : null);
if (winner && winner.data.fatality) {
setTimeout(() => {
addFloatingText(W/2, H/2 + 90, '💀 FATALITY: ' + winner.data.fatality + ' 💀', '#ef4444', 20);
}, 1500);
}
}
// ===================== RYSOWANIE WALKI =====================
function drawFight() {
ctx.save();
ctx.translate(shakeX, shakeY);
// Tło sejmowe
drawSejmBackground();
// Specjalne animacje bossów: Putin/Merkel
drawSpecialBossEffects();
// Losowe wsparcie Jana Pawła II
drawJP2();
// Wojownicy
player2.draw(ctx);
player1.draw(ctx);
// Cząsteczki i tekst
drawParticles();
// HUD (health bary, power, timer)
drawHUD();
// Napis "WALKA!" na początku
if (roundTimerAccum < 10 && roundTimer === 99 && !fightResult) {
const alpha = Math.max(0, 1 - roundTimerAccum / 10);
ctx.globalAlpha = alpha;
ctx.font = 'bold 60px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#fbbf24';
ctx.fillText('WALCZ!', W/2, H/2);
ctx.globalAlpha = 1;
}
// Wynik walki kontynuacja
if (fightResult && victoryTimer > 120) {
const alpha = Math.min(1, (victoryTimer - 120) / 60);
ctx.globalAlpha = alpha * (Math.sin(Date.now()/300) * 0.3 + 0.7);
ctx.font = 'bold 18px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#fbbf24';
ctx.fillText('NACIŚNIJ ENTER ABY KONTYNUOWAĆ', W/2, H - 40);
ctx.globalAlpha = 1;
}
// Pełnoekranowe intro bossa zawsze na wierzchu.
drawBossIntro();
ctx.restore();
}
function drawHUD() {
const barW = 400;
const barH = 28;
const barY = 20;
// ===== HEALTH BAR GRACZA 1 (lewa strona) =====
const p1x = 30;
// Tło
ctx.fillStyle = '#1e1e2e';
ctx.fillRect(p1x, barY, barW, barH);
ctx.strokeStyle = player1.data.faction === 'good' ? '#3b82f6' : '#ef4444';
ctx.lineWidth = 2;
ctx.strokeRect(p1x, barY, barW, barH);
// Zdrowie
const hp1ratio = Math.max(0, player1.hp / player1.maxHp);
const hp1Color = player1.data.faction === 'good' ? '#3b82f6' : '#ef4444';
const hp1Grad = ctx.createLinearGradient(p1x, barY, p1x + barW * hp1ratio, barY);
hp1Grad.addColorStop(0, hp1Color);
hp1Grad.addColorStop(1, player1.data.faction === 'good' ? '#60a5fa' : '#f87171');
ctx.fillStyle = hp1Grad;
ctx.fillRect(p1x + 2, barY + 2, (barW - 4) * hp1ratio, barH - 4);
// Label
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = '#fff';
ctx.fillText(player1.data.faction === 'good' ? '★ SUWERENNOŚĆ ★' : '✗ KONDOMINIUM ✗', p1x + 5, barY + 18);
// HP number
ctx.textAlign = 'right';
ctx.fillText(`${Math.ceil(player1.hp)}/${player1.maxHp}`, p1x + barW - 5, barY + 18);
// Imię
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = '#fff';
ctx.fillText(player1.data.name, p1x, barY + barH + 16);
ctx.font = '11px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText(`"${player1.data.nick}"`, p1x, barY + barH + 30);
// Power bar P1
const pwrY = barY + barH + 35;
ctx.fillStyle = '#1e1e2e';
ctx.fillRect(p1x, pwrY, barW * 0.6, 10);
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 1;
ctx.strokeRect(p1x, pwrY, barW * 0.6, 10);
const pwr1ratio = player1.power / player1.maxPower;
ctx.fillStyle = pwr1ratio >= 1 ? '#fbbf24' : '#92400e';
ctx.fillRect(p1x + 1, pwrY + 1, (barW * 0.6 - 2) * pwr1ratio, 8);
ctx.font = '8px Arial';
ctx.fillStyle = '#fbbf24';
ctx.textAlign = 'left';
ctx.fillText(pwr1ratio >= 1 ? '★ SPECJALNY GOTOWY! ★' : 'MOC SPECJALNA', p1x + 5, pwrY + 8);
// Combo P1
if (player1.combo > 1) {
ctx.font = 'bold 18px Arial';
ctx.fillStyle = '#fbbf24';
ctx.textAlign = 'left';
ctx.fillText(`${player1.combo}x COMBO!`, p1x, pwrY + 28);
}
// ===== HEALTH BAR GRACZA 2 (prawa strona) =====
const p2x = W - 30 - barW;
ctx.fillStyle = '#1e1e2e';
ctx.fillRect(p2x, barY, barW, barH);
ctx.strokeStyle = player2.data.faction === 'good' ? '#3b82f6' : '#ef4444';
ctx.lineWidth = 2;
ctx.strokeRect(p2x, barY, barW, barH);
const hp2ratio = Math.max(0, player2.hp / player2.maxHp);
const hp2Color = player2.data.faction === 'good' ? '#3b82f6' : '#ef4444';
const hp2Grad = ctx.createLinearGradient(p2x + barW, barY, p2x + barW - barW * hp2ratio, barY);
hp2Grad.addColorStop(0, hp2Color);
hp2Grad.addColorStop(1, player2.data.faction === 'good' ? '#60a5fa' : '#f87171');
ctx.fillStyle = hp2Grad;
ctx.fillRect(p2x + 2 + (barW - 4) * (1 - hp2ratio), barY + 2, (barW - 4) * hp2ratio, barH - 4);
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'right';
ctx.fillStyle = '#fff';
ctx.fillText(player2.data.faction === 'good' ? '★ SUWERENNOŚĆ ★' : '✗ KONDOMINIUM ✗', p2x + barW - 5, barY + 18);
ctx.textAlign = 'left';
ctx.fillText(`${Math.ceil(player2.hp)}/${player2.maxHp}`, p2x + 5, barY + 18);
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'right';
ctx.fillStyle = '#fff';
ctx.fillText(player2.data.name, p2x + barW, barY + barH + 16);
ctx.font = '11px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText(`"${player2.data.nick}"`, p2x + barW, barY + barH + 30);
// Power bar P2
ctx.fillStyle = '#1e1e2e';
ctx.fillRect(p2x + barW * 0.4, pwrY, barW * 0.6, 10);
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 1;
ctx.strokeRect(p2x + barW * 0.4, pwrY, barW * 0.6, 10);
const pwr2ratio = player2.power / player2.maxPower;
ctx.fillStyle = pwr2ratio >= 1 ? '#fbbf24' : '#92400e';
ctx.fillRect(p2x + barW * 0.4 + 1, pwrY + 1, (barW * 0.6 - 2) * pwr2ratio, 8);
ctx.font = '8px Arial';
ctx.fillStyle = '#fbbf24';
ctx.textAlign = 'right';
ctx.fillText(pwr2ratio >= 1 ? '★ SPECJALNY GOTOWY! ★' : 'MOC SPECJALNA', p2x + barW - 5, pwrY + 8);
if (player2.combo > 1) {
ctx.font = 'bold 18px Arial';
ctx.fillStyle = '#ef4444';
ctx.textAlign = 'right';
ctx.fillText(`${player2.combo}x COMBO!`, p2x + barW, pwrY + 28);
}
// ===== TIMER (środek) =====
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = roundTimer <= 10 ? '#ef4444' : '#fbbf24';
ctx.fillText(roundTimer.toString(), W/2, barY + 35);
ctx.font = '10px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText('CZAS', W/2, barY + 48);
// Portrety pod timerem
// Portrait P1
const ptSize = 50;
const ptY = barY + 52;
drawMiniPortrait(W/2 - 70, ptY, ptSize, player1.data);
drawMiniPortrait(W/2 + 20, ptY, ptSize, player2.data);
ctx.font = 'bold 20px Arial';
ctx.fillStyle = '#fbbf24';
ctx.textAlign = 'center';
ctx.fillText('VS', W/2, ptY + 32);
// Propaganda u dołu ekranu walki
if (!fightResult) {
const propFight = PROPAGANDA[Math.floor(Date.now()/5000) % PROPAGANDA.length];
ctx.font = 'bold 11px Arial';
ctx.fillStyle = 'rgba(251,191,36,0.4)';
ctx.textAlign = 'center';
ctx.fillText(propFight, W/2, H - 15);
}
}
function drawMiniPortrait(x, y, size, charData) {
ctx.fillStyle = charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d';
ctx.fillRect(x, y, size, size);
ctx.strokeStyle = charData.faction === 'good' ? '#3b82f6' : '#ef4444';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, size, size);
if (charData.photoLoaded && charData.photo) {
ctx.drawImage(charData.photo, x + 2, y + 2, size - 4, size - 4);
} else {
ctx.fillStyle = '#fff';
ctx.font = `bold ${size/3}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(charData.initials, x + size/2, y + size/2);
ctx.textBaseline = 'alphabetic';
}
}
function buildVictorySpeech() {
if (fightResult === 'DRAW') return 'Remis! Walka zakończona bez zwycięzcy.';
const winner = fightResult === 'P1WIN' ? selectedPlayer : selectedEnemy;
if (!winner) return '';
const isGoodWin = winner.faction === 'good';
const lines = [];
lines.push('Zwycięstwo!');
lines.push(`${winner.name}, pseudonim ${winner.nick}.`);
(isGoodWin ? VICTORY_GOOD : VICTORY_BAD).forEach(t => lines.push(t));
if (winner.fatality) lines.push(`Fatality: ${winner.fatality}.`);
lines.push(isGoodWin ? 'Polska suwerenna zwyciężyła. Kondominium w odwrocie.' : 'Ciemna noc nad Polską, ale patrioci nigdy się nie poddają.');
return lines.join(' ');
}
function announceVictoryScreenOnce() {
if (victorySpeechDone) return;
victorySpeechDone = true;
announce(buildVictorySpeech());
}
// ===================== EKRAN ZWYCIĘSTWA =====================
function drawVictory() {
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, W, H);
victoryTimer++;
const alpha = Math.min(1, victoryTimer / 60);
ctx.globalAlpha = alpha;
// Tło z efektami
const winner = fightResult === 'P1WIN' ? selectedPlayer : selectedEnemy;
const isGoodWin = winner && winner.faction === 'good';
if (victoryTimer === 1) announceVictoryScreenOnce();
// Animowane promienie
const t = Date.now() / 1000;
ctx.save();
ctx.translate(W/2, H/2);
for (let i = 0; i < 12; i++) {
ctx.rotate(Math.PI / 6);
ctx.fillStyle = isGoodWin ? `rgba(59,130,246,${0.05 + Math.sin(t + i)*0.03})` : `rgba(239,68,68,${0.05 + Math.sin(t + i)*0.03})`;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-60, -500);
ctx.lineTo(60, -500);
ctx.fill();
}
ctx.restore();
// Tytuł
ctx.textAlign = 'center';
if (fightResult === 'DRAW') {
ctx.font = 'bold 60px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText('REMIS!', W/2, 150);
} else {
ctx.font = 'bold 48px Arial';
ctx.fillStyle = isGoodWin ? '#3b82f6' : '#ef4444';
ctx.fillText('ZWYCIĘSTWO!', W/2, 120);
// Imię zwycięzcy
ctx.font = 'bold 36px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText(winner.name, W/2, 180);
ctx.font = 'bold 22px Arial';
ctx.fillStyle = '#fff';
ctx.fillText(`"${winner.nick}"`, W/2, 215);
// Portret zwycięzcy
drawMiniPortrait(W/2 - 60, 240, 120, winner);
// Propaganda
const vTexts = isGoodWin ? VICTORY_GOOD : VICTORY_BAD;
ctx.font = 'bold 20px Arial';
ctx.fillStyle = '#fbbf24';
for (let i = 0; i < vTexts.length; i++) {
const yy = 400 + i * 30;
const vis = Math.min(1, Math.max(0, (victoryTimer - 60 - i * 20) / 30));
ctx.globalAlpha = vis * alpha;
ctx.fillText(vTexts[i], W/2, yy);
}
ctx.globalAlpha = alpha;
// Fatality
if (winner.fatality && victoryTimer > 120) {
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#ef4444';
const fat = Math.sin(Date.now()/200) * 0.3 + 0.7;
ctx.globalAlpha = fat * alpha;
ctx.fillText(`💀 FATALITY: ${winner.fatality} 💀`, W/2, 560);
ctx.globalAlpha = alpha;
}
// Suwerenna deklaracja
if (isGoodWin) {
ctx.font = 'bold 16px Arial';
ctx.fillStyle = '#60a5fa';
ctx.fillText('🇵🇱 POLSKA SUWERENNA ZWYCIĘŻYŁA! KONDOMINIUM W ODWROCIE! 🇵🇱', W/2, 600);
} else {
ctx.font = 'bold 16px Arial';
ctx.fillStyle = '#f87171';
ctx.fillText('Ciemna noc nad Polską... ale patrioci nigdy się nie poddają!', W/2, 600);
}
}
// Kontynuacja
if (victoryTimer > 90) {
const sp = Math.sin(Date.now()/300) * 0.4 + 0.6;
ctx.globalAlpha = sp;
ctx.font = 'bold 18px Arial';
ctx.fillStyle = '#fff';
ctx.fillText('ENTER = nowa walka | ESC = menu główne', W/2, 670);
}
ctx.globalAlpha = 1;
}
function updateVictory() {
if (consumeKey('Enter') || consumeKey('Space')) {
gameState = 'SELECT';
selectedPlayer = null;
selectedEnemy = null;
selectCursor = {col:0, row:0, side:0};
enterCooldown = 15;
stopMazurek();
}
if (consumeKey('Escape')) {
gameState = 'MENU';
stopMazurek();
}
}
// ===================== PAUZA =====================
function drawPause() {
// Rysuj walkę w tle
drawFight();
// Overlay
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(0, 0, W, H);
// Tekst
ctx.textAlign = 'center';
ctx.font = 'bold 48px Arial';
ctx.fillStyle = '#fbbf24';
ctx.fillText('PAUZA', W/2, H/2 - 30);
ctx.font = '20px Arial';
ctx.fillStyle = '#fff';
ctx.fillText('P = kontynuuj | ESC = menu główne', W/2, H/2 + 20);
// Propaganda
ctx.font = 'bold 14px Arial';
ctx.fillStyle = '#fbbf24';
const prop = PROPAGANDA[Math.floor(Date.now()/2000) % PROPAGANDA.length];
ctx.fillText(prop, W/2, H/2 + 60);
// Sterowanie
ctx.font = '14px Arial';
ctx.fillStyle = '#94a3b8';
ctx.fillText('Strzałki = ruch | Q/W = ciosy | A/S = kopnięcia | Z = blok | X = specjalny', W/2, H/2 + 100);
}
// ===================== GŁÓWNA PĘTLA GRY =====================
let lastTime = performance.now();
function gameLoop(now) {
const dt = Math.min((now - lastTime) / 16.67, 3);
lastTime = now;
// Globalna obsługa klawiszy pauza i wyjście
if (gameState === 'FIGHT' && !fightResult) {
if (consumeKey('KeyP')) {
gameState = 'PAUSE';
} else if (consumeKey('Escape')) {
gameState = 'MENU';
stopMazurek();
}
} else if (gameState === 'PAUSE') {
const escP = justPressed['Escape'];
const pP = justPressed['KeyP'];
consumeKey('Escape');
consumeKey('KeyP');
if (escP) {
gameState = 'MENU';
stopMazurek();
} else if (pP) {
gameState = 'FIGHT';
}
}
// Update
switch (gameState) {
case 'MENU': updateMenu(); break;
case 'SELECT': updateSelect(); break;
case 'FIGHT': updateFight(); break;
case 'VICTORY': updateVictory(); break;
}
// Draw
ctx.clearRect(0, 0, W, H);
switch (gameState) {
case 'MENU': drawMenu(); break;
case 'SELECT': drawSelect(); break;
case 'FIGHT': drawFight(); break;
case 'VICTORY': drawVictory(); break;
case 'PAUSE': drawPause(); break;
}
// Wyczyść justPressed na koniec klatki
Object.keys(justPressed).forEach(k => justPressed[k] = false);
requestAnimationFrame(gameLoop);
}
// ===================== SKALOWANIE CANVAS =====================
function resizeCanvas() {
const ratio = W / H;
let cw = window.innerWidth;
let ch = window.innerHeight;
if (cw / ch > ratio) {
cw = ch * ratio;
} else {
ch = cw / ratio;
}
canvas.style.width = cw + 'px';
canvas.style.height = ch + 'px';
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// ===================== START GRY =====================
console.log('%c SEJM KOMBAT: Suwerenność vs Kondominium! ', 'background:#1e3a8a;color:#fbbf24;font-size:20px;font-weight:bold;');
console.log('%c Polska suwerenna kontra lokaje Brukseli i Moskwy! ', 'background:#dc2626;color:#fff;font-size:14px;');
requestAnimationFrame(gameLoop);
</script>
</body>
</html>