  genCol = 0; genRow = 12;
  generateAhead(0);
  player.x = 3*TILE;
  player.y = 11*TILE - player.h;
  player.vx = player.vy = 0; player.invuln = 40;
  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  wallSpeed = 1.3;
  wallX = -TILE*12;
  endlessStarted = false;
}

  genCol = 0; genRow = 12;
  generateAhead(0);
  player.x = 3*TILE;
  player.y = 11*TILE - player.h;
  player.vx = player.vy = 0; player.invuln = 40;
  camX = 0; camY = 0;
  scrollSpeed = 1.4;
  wallSpeed = (ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium).wallBase;
  wallX = -TILE*12;
  endlessStarted = false;
}

+  wallSpeed = (ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium).wallBase;


<total_tokens>14956839 tokens left</total_tokens>


Edit

function genChunk(){
  const c0 = genCol;
  const r  = genRow;
  const diff = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti
  const fill = (a, b, top) => { for (let c = a; c < b; c++) for (let rr = top; rr < ROWS; rr++) world.setSolid(c, rr); };

  if (genCol < 14){ fill(c0, c0 + 14, r); genCol += 14; return; }   // bezpečný start

  const t = Math.random();

  if (t < 0.20 + diff * 0.12){                        // propast
    let gap = 2 + Math.floor(Math.random() * (1 + Math.round(diff * 3)));
    gap = Math.min(gap, 4);
    if (gap >= 4 || Math.random() < 0.45){            // plošina nad propastí
      const pc = c0 + 1, plen = Math.max(2, gap - 1);
      for (let i = 0; i < plen; i++) world.setSolid(pc + i, r - 3);
      for (let i = 0; i < plen; i++)
        world.coins.push({ x: (pc + i)*TILE + TILE/2, y: (r - 4)*TILE + TILE/2, got: false });
    }
    genCol += gap;

  } else if (t < 0.52){                               // rovinka s mincemi / nepřáteli
    const len = 5 + Math.floor(Math.random() * 6);
    fill(c0, c0 + len, r);
    // počet nepřátel roste s ušlou vzdáleností
    let n = 0;
    if (Math.random() < 0.4 + diff * 0.4) n = 1;
    if (n && Math.random() < diff * 0.6) n = 2;
    if (n === 2 && len < 7) n = 1;
    for (let e = 0; e < n; e++){
      const ec = c0 + 1 + Math.floor((e + 0.5) / n * (len - 2));
      const tier = rollTier(diff);
      world.enemies.push(mkEnemy(ec*TILE + (TILE - ETIER[tier].w) / 2, (r - 3)*TILE, tier));
    }
    if (Math.random() < 0.6)
      for (let i = 0; i < Math.min(4, len - 1); i++)
        world.coins.push({ x: (c0 + 1 + i)*TILE + TILE/2, y: (r - 2)*TILE + TILE/2, got: false });
    genCol += len;

  } else if (t < 0.76){                               // schod nahoru / dolů
    genRow = clamp(r + (Math.random() < 0.5 ? -1 : 1), 8, 13);
    const len = 4 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, genRow);
    if (Math.random() < 0.25 + diff * 0.4){
      const tier = rollTier(diff);
      world.enemies.push(mkEnemy((c0 + (len >> 1))*TILE, (genRow - 3)*TILE, tier));
    }
    genCol += len;

  } else {                                            // plošinková sekce
    const len = 6 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, r);
    for (let k = 0; k < 2; k++){
      const pc = c0 + 2 + k * 3, pr = r - 3 - (k & 1);
      world.setSolid(pc, pr); world.setSolid(pc + 1, pr);
      world.coins.push({ x: pc*TILE + TILE, y: (pr - 1)*TILE + TILE/2, got: false });
    }
    if (Math.random() < 0.3 + diff * 0.4)             // malá příšerka na zemi mezi plošinami
      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, Math.random() < 0.5 + diff * 0.3 ? 1 : 0));
    genCol += len;
  }
}

function genChunk(){
  const c0 = genCol;
  const r  = genRow;
  const prog = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti
  const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;
  const fill = (a, b, top) => { for (let c = a; c < b; c++) for (let rr = top; rr < ROWS; rr++) world.setSolid(c, rr); };

  if (genCol < 14){ fill(c0, c0 + 14, r); genCol += 14; return; }   // bezpečný start

  const t = Math.random();

  if (t < D.gapChance + prog * 0.10){                 // propast
    let gap = 2 + Math.floor(Math.random() * (D.gapMax - 1));
    gap = Math.min(gap, D.gapMax);
    if (gap >= 4 || Math.random() < 0.45){            // plošina nad širší propastí – drží průchodnost
      const pc = c0 + 1, plen = Math.max(2, gap - 1);
      for (let i = 0; i < plen; i++) world.setSolid(pc + i, r - 3);
      for (let i = 0; i < plen; i++)
        world.coins.push({ x: (pc + i)*TILE + TILE/2, y: (r - 4)*TILE + TILE/2, got: false });
    }
    genCol += gap;

  } else if (t < 0.52){                               // rovinka s mincemi / nepřáteli
    const len = 5 + Math.floor(Math.random() * 6);
    fill(c0, c0 + len, r);
    let n = 0;
    if (Math.random() < D.enemy1 + prog * 0.2) n = 1;
    if (n && Math.random() < D.enemy2 + prog * 0.25) n = 2;
    if (n === 2 && len < 7) n = 1;
    for (let e = 0; e < n; e++){
      const ec = c0 + 1 + Math.floor((e + 0.5) / n * (len - 2));
      const tier = rollTier(prog, D.tierBias);
      world.enemies.push(mkEnemy(ec*TILE + (TILE - ETIER[tier].w) / 2, (r - 3)*TILE, tier));
    }
    if (Math.random() < 0.6)
      for (let i = 0; i < Math.min(4, len - 1); i++)
        world.coins.push({ x: (c0 + 1 + i)*TILE + TILE/2, y: (r - 2)*TILE + TILE/2, got: false });
    genCol += len;

  } else if (t < 0.76){                               // schod nahoru / dolů
    genRow = clamp(r + (Math.random() < 0.5 ? -1 : 1), 8, 13);
    const len = 4 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, genRow);
    if (Math.random() < (0.2 + prog * 0.35) * D.enemyMul){
      const tier = rollTier(prog, D.tierBias);
      world.enemies.push(mkEnemy((c0 + (len >> 1))*TILE, (genRow - 3)*TILE, tier));
    }
    genCol += len;

  } else {                                            // plošinková sekce
    const len = 6 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, r);
    for (let k = 0; k < 2; k++){
      const pc = c0 + 2 + k * 3, pr = r - 3 - (k & 1);
      world.setSolid(pc, pr); world.setSolid(pc + 1, pr);
      world.coins.push({ x: pc*TILE + TILE, y: (pr - 1)*TILE + TILE/2, got: false });
    }
    if (Math.random() < (0.25 + prog * 0.35) * D.enemyMul)   // příšerka na zemi mezi plošinami
      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, rollTier(prog, D.tierBias - 0.15)));
    genCol += len;
  }
}


The file /home/tonda/hra.html has been updated successfully. (file state is current in your context — no need to Read it back)

function genChunk(){
  const c0 = genCol;
  const r  = genRow;
  const diff = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti
  const fill = (a, b, top) => { for (let c = a; c < b; c++) for (let rr = top; rr < ROWS; rr++) world.setSolid(c, rr); };

  if (genCol < 14){ fill(c0, c0 + 14, r); genCol += 14; return; }   // bezpečný start

  const t = Math.random();

  if (t < 0.20 + diff * 0.12){                        // propast
    let gap = 2 + Math.floor(Math.random() * (1 + Math.round(diff * 3)));
    gap = Math.min(gap, 4);
    if (gap >= 4 || Math.random() < 0.45){            // plošina nad propastí
      const pc = c0 + 1, plen = Math.max(2, gap - 1);
      for (let i = 0; i < plen; i++) world.setSolid(pc + i, r - 3);
      for (let i = 0; i < plen; i++)
        world.coins.push({ x: (pc + i)*TILE + TILE/2, y: (r - 4)*TILE + TILE/2, got: false });
    }
    genCol += gap;

  } else if (t < 0.52){                               // rovinka s mincemi / nepřáteli
    const len = 5 + Math.floor(Math.random() * 6);
    fill(c0, c0 + len, r);
    // počet nepřátel roste s ušlou vzdáleností
    let n = 0;
    if (Math.random() < 0.4 + diff * 0.4) n = 1;
    if (n && Math.random() < diff * 0.6) n = 2;
    if (n === 2 && len < 7) n = 1;
    for (let e = 0; e < n; e++){
      const ec = c0 + 1 + Math.floor((e + 0.5) / n * (len - 2));
      const tier = rollTier(diff);
      world.enemies.push(mkEnemy(ec*TILE + (TILE - ETIER[tier].w) / 2, (r - 3)*TILE, tier));
    }
    if (Math.random() < 0.6)
      for (let i = 0; i < Math.min(4, len - 1); i++)
        world.coins.push({ x: (c0 + 1 + i)*TILE + TILE/2, y: (r - 2)*TILE + TILE/2, got: false });
    genCol += len;

  } else if (t < 0.76){                               // schod nahoru / dolů
    genRow = clamp(r + (Math.random() < 0.5 ? -1 : 1), 8, 13);
    const len = 4 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, genRow);
    if (Math.random() < 0.25 + diff * 0.4){
      const tier = rollTier(diff);
      world.enemies.push(mkEnemy((c0 + (len >> 1))*TILE, (genRow - 3)*TILE, tier));
    }
    genCol += len;

  } else {                                            // plošinková sekce
    const len = 6 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, r);
    for (let k = 0; k < 2; k++){
      const pc = c0 + 2 + k * 3, pr = r - 3 - (k & 1);
      world.setSolid(pc, pr); world.setSolid(pc + 1, pr);
      world.coins.push({ x: pc*TILE + TILE, y: (pr - 1)*TILE + TILE/2, got: false });
    }
    if (Math.random() < 0.3 + diff * 0.4)             // malá příšerka na zemi mezi plošinami
      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, Math.random() < 0.5 + diff * 0.3 ? 1 : 0));
    genCol += len;
  }
}

function genChunk(){
  const c0 = genCol;
  const r  = genRow;
  const prog = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti
  const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;
  const fill = (a, b, top) => { for (let c = a; c < b; c++) for (let rr = top; rr < ROWS; rr++) world.setSolid(c, rr); };

  if (genCol < 14){ fill(c0, c0 + 14, r); genCol += 14; return; }   // bezpečný start

  const t = Math.random();

  if (t < D.gapChance + prog * 0.10){                 // propast
    let gap = 2 + Math.floor(Math.random() * (D.gapMax - 1));
    gap = Math.min(gap, D.gapMax);
    if (gap >= 4 || Math.random() < 0.45){            // plošina nad širší propastí – drží průchodnost
      const pc = c0 + 1, plen = Math.max(2, gap - 1);
      for (let i = 0; i < plen; i++) world.setSolid(pc + i, r - 3);
      for (let i = 0; i < plen; i++)
        world.coins.push({ x: (pc + i)*TILE + TILE/2, y: (r - 4)*TILE + TILE/2, got: false });
    }
    genCol += gap;

  } else if (t < 0.52){                               // rovinka s mincemi / nepřáteli
    const len = 5 + Math.floor(Math.random() * 6);
    fill(c0, c0 + len, r);
    let n = 0;
    if (Math.random() < D.enemy1 + prog * 0.2) n = 1;
    if (n && Math.random() < D.enemy2 + prog * 0.25) n = 2;
    if (n === 2 && len < 7) n = 1;
    for (let e = 0; e < n; e++){
      const ec = c0 + 1 + Math.floor((e + 0.5) / n * (len - 2));
      const tier = rollTier(prog, D.tierBias);
      world.enemies.push(mkEnemy(ec*TILE + (TILE - ETIER[tier].w) / 2, (r - 3)*TILE, tier));
    }
    if (Math.random() < 0.6)
      for (let i = 0; i < Math.min(4, len - 1); i++)
        world.coins.push({ x: (c0 + 1 + i)*TILE + TILE/2, y: (r - 2)*TILE + TILE/2, got: false });
    genCol += len;

  } else if (t < 0.76){                               // schod nahoru / dolů
    genRow = clamp(r + (Math.random() < 0.5 ? -1 : 1), 8, 13);
    const len = 4 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, genRow);
    if (Math.random() < (0.2 + prog * 0.35) * D.enemyMul){
      const tier = rollTier(prog, D.tierBias);
      world.enemies.push(mkEnemy((c0 + (len >> 1))*TILE, (genRow - 3)*TILE, tier));
    }
    genCol += len;

  } else {                                            // plošinková sekce
    const len = 6 + Math.floor(Math.random() * 4);
    fill(c0, c0 + len, r);
    for (let k = 0; k < 2; k++){
      const pc = c0 + 2 + k * 3, pr = r - 3 - (k & 1);
      world.setSolid(pc, pr); world.setSolid(pc + 1, pr);
      world.coins.push({ x: pc*TILE + TILE, y: (pr - 1)*TILE + TILE/2, got: false });
    }
    if (Math.random() < (0.25 + prog * 0.35) * D.enemyMul)   // příšerka na zemi mezi plošinami
      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, rollTier(prog, D.tierBias - 0.15)));
    genCol += len;
  }
}

-  const diff = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti

+  const prog = Math.min(distance / 500, 1);           // 0 → 1 podle ušlé vzdálenosti

+  const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;

-    let gap = 2 + Math.floor(Math.random() * (1 + Math.round(diff * 3)));

-    if (gap >= 4 || Math.random() < 0.45){            // plošina nad propastí

+  if (t < D.gapChance + prog * 0.10){                 // propast

+    let gap = 2 + Math.floor(Math.random() * (D.gapMax - 1));

+    if (gap >= 4 || Math.random() < 0.45){            // plošina nad širší propastí – drží průchodnost

-    // počet nepřátel roste s ušlou vzdáleností

-    if (n && Math.random() < diff * 0.6) n = 2;

+    if (Math.random() < D.enemy1 + prog * 0.2) n = 1;

+    if (n && Math.random() < D.enemy2 + prog * 0.25) n = 2;

+      const tier = rollTier(prog, D.tierBias);

+    if (Math.random() < (0.2 + prog * 0.35) * D.enemyMul){

-    if (Math.random() < 0.3 + diff * 0.4)             // malá příšerka na zemi mezi plošinami

-      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, Math.random() < 0.5 + diff * 0.3 ? 1 : 0));

+    if (Math.random() < (0.25 + prog * 0.35) * D.enemyMul)   // příšerka na zemi mezi plošinami

+      world.enemies.push(mkEnemy((c0 + len - 2)*TILE, (r - 3)*TILE, rollTier(prog, D.tierBias - 0.15)));


<total_tokens>14953758 tokens left</total_tokens>


taky bych chtěl aby byli velcí nepřátelé byli na dvě zašlápnutí. Taky udělěj u nekonečného módu tři obtížnosti(mometální jediná obtížnost bude střední takže udělej lehčí s méně mezerama s méně a menší…


    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
    wallX += wallSpeed;

    // zeď se plíží zleva doprava a zrychluje – tempo podle obtížnosti
    const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;
    wallSpeed = D.wallBase + Math.min(distance / 280, D.wallRamp);
    wallX += wallSpeed;


    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje
    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);
    wallX += wallSpeed;

    // zeď se plíží zleva doprava a zrychluje – tempo podle obtížnosti
    const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;
    wallSpeed = D.wallBase + Math.min(distance / 280, D.wallRamp);
    wallX += wallSpeed;

-    // zeď se plíží zprava doleva… teda zleva doprava, a zrychluje

-    wallSpeed = 1.3 + Math.min(distance / 280, 3.3);

+    // zeď se plíží zleva doprava a zrychluje – tempo podle obtížnosti

+    const D = ENDLESS_DIFF[endlessDiff] || ENDLESS_DIFF.medium;

+    wallSpeed = D.wallBase + Math.min(distance / 280, D.wallRamp);

const follow = player.x + player.w/2 - W*0.42;


<total_tokens>14953352 tokens left</total_tokens>


let state = 'menu';      // menu | play | paused | shop | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;

let state = 'menu';      // menu | play | paused | shop | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let endlessDiff = 'medium';   // easy | medium | hard
let diffSel = 1;              // index do DIFF_KEYS


let state = 'menu';      // menu | play | paused | shop | over | win
let mode  = 'levels';    // levels | endless
let menuSel = 0;
let endlessDiff = 'medium';   // easy | medium | hard
let diffSel = 1;              // index do DIFF_KEYS

+let endlessDiff = 'medium';   // easy | medium | hard

+let diffSel = 1;              // index do DIFF_KEYS

let world, levelIndex = 0, lives = 3, coinsCollected = 0, score = 0, distance = 0;

let camX = 0, camY = 0, scrollSpeed = 1.4, endlessStarted = false;

let wallX = -400, wallSpeed = 1.3;          // zeď smrti v nekonečném módu


<total_tokens>14952955 tokens left</total_tokens>


698	  it.apply();
699	  S.coin();
700	}
701	function shopContinue(){
702	  loadLevel(levelIndex);   // levelIndex je už zvýšený
703	  state = 'play';
704	}
705
706	/* ------------------------------------------------------------------ */
707	/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
708	/*  unlock = kolik bodů v nekonečném běhu je potřeba k odemčení       */
709	/* ------------------------------------------------------------------ */
710	const SKINS = [
711	  { name: 'Nebe',     color: '#5cc8ff', unlock: 0 },
712	  { name: 'Limetka',  color: '#4ade80', unlock: 250 },
713	  { name: 'Meruňka',  color: '#ffb454', unlock: 600 },
714	  { name: 'Malina',   color: '#ff6ea8', unlock: 1200 },
715	  { name: 'Ametyst',  color: '#b98cff', unlock: 2500 },
716	  { name: 'Zlato',    color: '#ffd45c', unlock: 5000 },
717	];
718
719	function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
720	function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }
721
722	let bestHS = parseInt(loadStr('hs', '0'), 10) || 0;
723	let equippedSkin = clamp(parseInt(loadStr('skin', '0'), 10) || 0, 0, SKINS.length - 1);
724	if (SKINS[equippedSkin].unlock > bestHS) equippedSkin = 0;   // pojistka
725	let skinSel = equippedSkin;
726	let newRecord = false, justUnlocked = [];
727
728	const skinUnlocked = i => SKINS[i].unlock <= bestHS;
729	const escore = () => mode === 'endless' ? score + distance : score;
730
731	function saveBest(){
732	  const s = escore();
733	  const old = bestHS;

  it.apply();
  S.coin();
}
function shopContinue(){
  loadLevel(levelIndex);   // levelIndex je už zvýšený
  state = 'play';
}

/* ------------------------------------------------------------------ */
/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
/*  unlock = kolik bodů v nekonečném běhu je potřeba k odemčení       */
/* ------------------------------------------------------------------ */
const SKINS = [
  { name: 'Nebe',     color: '#5cc8ff', unlock: 0 },
  { name: 'Limetka',  color: '#4ade80', unlock: 250 },
  { name: 'Meruňka',  color: '#ffb454', unlock: 600 },
  { name: 'Malina',   color: '#ff6ea8', unlock: 1200 },
  { name: 'Ametyst',  color: '#b98cff', unlock: 2500 },
  { name: 'Zlato',    color: '#ffd45c', unlock: 5000 },
];

function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }

let bestHS = parseInt(loadStr('hs', '0'), 10) || 0;
let equippedSkin = clamp(parseInt(loadStr('skin', '0'), 10) || 0, 0, SKINS.length - 1);
if (SKINS[equippedSkin].unlock > bestHS) equippedSkin = 0;   // pojistka
let skinSel = equippedSkin;
let newRecord = false, justUnlocked = [];

const skinUnlocked = i => SKINS[i].unlock <= bestHS;
const escore = () => mode === 'endless' ? score + distance : score;

function saveBest(){
  const s = escore();
  const old = bestHS;


<total_tokens>14952718 tokens left</total_tokens>


731	function saveBest(){
732	  const s = escore();
733	  const old = bestHS;
734	  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
735	  justUnlocked = SKINS.filter(k => k.unlock > old && k.unlock <= bestHS).map(k => k.name);
736	}
737
738	function startGame(m){
739	  unlockAudio();
740	  mode = m;
741	  lives = m === 'endless' ? 1 : 3;   // nekonečný běh: jeden život
742	  coinsCollected = 0; score = 0; distance = 0; shake = 0;

function saveBest(){
  const s = escore();
  const old = bestHS;
  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
  justUnlocked = SKINS.filter(k => k.unlock > old && k.unlock <= bestHS).map(k => k.name);
}

function startGame(m){
  unlockAudio();
  mode = m;
  lives = m === 'endless' ? 1 : 3;   // nekonečný běh: jeden život
  coinsCollected = 0; score = 0; distance = 0; shake = 0;


<total_tokens>14951796 tokens left</total_tokens>


/* ------------------------------------------------------------------ */
/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
/*  unlock = kolik bodů v nekonečném běhu je potřeba k odemčení       */
/* ------------------------------------------------------------------ */
const SKINS = [
  { name: 'Nebe',     color: '#5cc8ff', unlock: 0 },
  { name: 'Limetka',  color: '#4ade80', unlock: 250 },
  { name: 'Meruňka',  color: '#ffb454', unlock: 600 },
  { name: 'Malina',   color: '#ff6ea8', unlock: 1200 },
  { name: 'Ametyst',  color: '#b98cff', unlock: 2500 },
  { name: 'Zlato',    color: '#ffd45c', unlock: 5000 },
];

function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }

let bestHS = parseInt(loadStr('hs', '0'), 10) || 0;
let equippedSkin = clamp(parseInt(loadStr('skin', '0'), 10) || 0, 0, SKINS.length - 1);
if (SKINS[equippedSkin].unlock > bestHS) equippedSkin = 0;   // pojistka
let skinSel = equippedSkin;
let newRecord = false, justUnlocked = [];

const skinUnlocked = i => SKINS[i].unlock <= bestHS;
const escore = () => mode === 'endless' ? score + distance : score;

function saveBest(){
  const s = escore();
  const old = bestHS;
  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
  justUnlocked = SKINS.filter(k => k.unlock > old && k.unlock <= bestHS).map(k => k.name);
}

/* ------------------------------------------------------------------ */
/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
/*  Každá obtížnost má vlastní rodinu barev; odstíny se odemykají     */
/*  podle nejdelšího běhu (v metrech) v té obtížnosti.                */
/* ------------------------------------------------------------------ */
const SKINS = [
  { name: 'Nebe',        color: '#5cc8ff', diff: null,     dist: 0 },
  { name: 'Lehká I',     color: '#86efac', diff: 'easy',   dist: 100 },
  { name: 'Lehká II',    color: '#4ade80', diff: 'easy',   dist: 300 },
  { name: 'Lehká III',   color: '#15a34a', diff: 'easy',   dist: 600 },
  { name: 'Střední I',   color: '#7dd3fc', diff: 'medium', dist: 100 },
  { name: 'Střední II',  color: '#38bdf8', diff: 'medium', dist: 300 },
  { name: 'Střední III', color: '#0284c7', diff: 'medium', dist: 600 },
  { name: 'Těžká I',     color: '#fda4af', diff: 'hard',   dist: 100 },
  { name: 'Těžká II',    color: '#f43f5e', diff: 'hard',   dist: 300 },
  { name: 'Těžká III',   color: '#be123c', diff: 'hard',   dist: 600 },
];

function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }
const loadInt = k => parseInt(loadStr(k, '0'), 10) || 0;

let bestHS = loadInt('hs');
const bestDist = { easy: loadInt('dist_easy'), medium: loadInt('dist_medium'), hard: loadInt('dist_hard') };

const skinUnlocked = i => {
  const s = SKINS[i];
  return !s.diff || (bestDist[s.diff] || 0) >= s.dist;
};

let equippedSkin = clamp(loadInt('skin'), 0, SKINS.length - 1);
if (!skinUnlocked(equippedSkin)) equippedSkin = 0;   // pojistka
let skinSel = equippedSkin;
let newRecord = false, justUnlocked = [];

const escore = () => mode === 'endless' ? score + distance : score;

function saveBest(){
  const s = escore();
  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
  const d = endlessDiff, prev = bestDist[d] || 0;
  if (distance > prev){
    bestDist[d] = distance; store('dist_' + d, distance);
    justUnlocked = SKINS.filter(k => k.diff === d && k.dist > prev && k.dist <= distance).map(k => k.name);
  }
}


/* ------------------------------------------------------------------ */
/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
/*  unlock = kolik bodů v nekonečném běhu je potřeba k odemčení       */
/* ------------------------------------------------------------------ */
const SKINS = [
  { name: 'Nebe',     color: '#5cc8ff', unlock: 0 },
  { name: 'Limetka',  color: '#4ade80', unlock: 250 },
  { name: 'Meruňka',  color: '#ffb454', unlock: 600 },
  { name: 'Malina',   color: '#ff6ea8', unlock: 1200 },
  { name: 'Ametyst',  color: '#b98cff', unlock: 2500 },
  { name: 'Zlato',    color: '#ffd45c', unlock: 5000 },
];

function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }

let bestHS = parseInt(loadStr('hs', '0'), 10) || 0;
let equippedSkin = clamp(parseInt(loadStr('skin', '0'), 10) || 0, 0, SKINS.length - 1);
if (SKINS[equippedSkin].unlock > bestHS) equippedSkin = 0;   // pojistka
let skinSel = equippedSkin;
let newRecord = false, justUnlocked = [];

const skinUnlocked = i => SKINS[i].unlock <= bestHS;
const escore = () => mode === 'endless' ? score + distance : score;

function saveBest(){
  const s = escore();
  const old = bestHS;
  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
  justUnlocked = SKINS.filter(k => k.unlock > old && k.unlock <= bestHS).map(k => k.name);
}

/* ------------------------------------------------------------------ */
/*  Rekord + barvy postavy (uloženo v prohlížeči přes localStorage)   */
/*  Každá obtížnost má vlastní rodinu barev; odstíny se odemykají     */
/*  podle nejdelšího běhu (v metrech) v té obtížnosti.                */
/* ------------------------------------------------------------------ */
const SKINS = [
  { name: 'Nebe',        color: '#5cc8ff', diff: null,     dist: 0 },
  { name: 'Lehká I',     color: '#86efac', diff: 'easy',   dist: 100 },
  { name: 'Lehká II',    color: '#4ade80', diff: 'easy',   dist: 300 },
  { name: 'Lehká III',   color: '#15a34a', diff: 'easy',   dist: 600 },
  { name: 'Střední I',   color: '#7dd3fc', diff: 'medium', dist: 100 },
  { name: 'Střední II',  color: '#38bdf8', diff: 'medium', dist: 300 },
  { name: 'Střední III', color: '#0284c7', diff: 'medium', dist: 600 },
  { name: 'Těžká I',     color: '#fda4af', diff: 'hard',   dist: 100 },
  { name: 'Těžká II',    color: '#f43f5e', diff: 'hard',   dist: 300 },
  { name: 'Těžká III',   color: '#be123c', diff: 'hard',   dist: 600 },
];

function store(k, v){ try { localStorage.setItem('hranaty_' + k, String(v)); } catch (e) {} }
function loadStr(k, d){ try { const v = localStorage.getItem('hranaty_' + k); return v == null ? d : v; } catch (e) { return d; } }
const loadInt = k => parseInt(loadStr(k, '0'), 10) || 0;

let bestHS = loadInt('hs');
const bestDist = { easy: loadInt('dist_easy'), medium: loadInt('dist_medium'), hard: loadInt('dist_hard') };

const skinUnlocked = i => {
  const s = SKINS[i];
  return !s.diff || (bestDist[s.diff] || 0) >= s.dist;
};

let equippedSkin = clamp(loadInt('skin'), 0, SKINS.length - 1);
if (!skinUnlocked(equippedSkin)) equippedSkin = 0;   // pojistka
let skinSel = equippedSkin;
let newRecord = false, justUnlocked = [];

const escore = () => mode === 'endless' ? score + distance : score;

function saveBest(){
  const s = escore();
  if (s > bestHS){ bestHS = s; store('hs', bestHS); newRecord = true; }
  const d = endlessDiff, prev = bestDist[d] || 0;
  if (distance > prev){
    bestDist[d] = distance; store('dist_' + d, distance);
    justUnlocked = SKINS.filter(k => k.diff === d && k.dist > prev && k.dist <= distance).map(k => k.name);
  }
}

-/*  unlock = kolik bodů v nekonečném běhu je potřeba k odemčení       */

+/*  Každá obtížnost má vlastní rodinu barev; odstíny se odemykají     */

+/*  podle nejdelšího běhu (v metrech) v té obtížnosti.                */

-  { name: 'Nebe',     color: '#5cc8ff', unlock: 0 },

-  { name: 'Limetka',  color: '#4ade80', unlock: 250 },

-  { name: 'Meruňka',  color: '#ffb454', unlock: 600 },

-  { name: 'Malina',   color: '#ff6ea8', unlock: 1200 },

-  { name: 'Ametyst',  color: '#b98cff', unlock: 2500 },

-  { name: 'Zlato',    color: '#ffd45c', unlock: 5000 },

+  { name: 'Nebe',        color: '#5cc8ff', diff: null,     dist: 0 },

+  { name: 'Lehká I',     color: '#86efac', diff: 'easy',   dist: 100 },

+  { name: 'Lehká II',    color: '#4ade80', diff: 'easy',   dist: 300 },

+  { name: 'Lehká III',   color: '#15a34a', diff: 'easy',   dist: 600 },

+  { name: 'Střední I',   color: '#7dd3fc', diff: 'medium', dist: 100 },

+  { name: 'Střední II',  color: '#38bdf8', diff: 'medium', dist: 300 },

+  { name: 'Střední III', color: '#0284c7', diff: 'medium', dist: 600 },

+  { name: 'Těžká I',     color: '#fda4af', diff: 'hard',   dist: 100 },

+  { name: 'Těžká II',    color: '#f43f5e', diff: 'hard',   dist: 300 },

+  { name: 'Těžká III',   color: '#be123c', diff: 'hard',   dist: 600 },

+const loadInt = k => parseInt(loadStr(k, '0'), 10) || 0;

-let bestHS = parseInt(loadStr('hs', '0'), 10) || 0;

-let equippedSkin = clamp(parseInt(loadStr('skin', '0'), 10) || 0, 0, SKINS.length - 1);

-if (SKINS[equippedSkin].unlock > bestHS) equippedSkin = 0;   // pojistka

+const bestDist = { easy: loadInt('dist_easy'), medium: loadInt('dist_medium'), hard: loadInt('dist_hard') };

+  return !s.diff || (bestDist[s.diff] || 0) >= s.dist;

+let equippedSkin = clamp(loadInt('skin'), 0, SKINS.length - 1);

+if (!skinUnlocked(equippedSkin)) equippedSkin = 0;   // pojistka

-const skinUnlocked = i => SKINS[i].unlock <= bestHS;

-  justUnlocked = SKINS.filter(k => k.unlock > old && k.unlock <= bestHS).map(k => k.name);

+  const d = endlessDiff, prev = bestDist[d] || 0;

+    bestDist[d] = distance; store('dist_' + d, distance);

+    justUnlocked = SKINS.filter(k => k.diff === d && k.dist > prev && k.dist <= distance).map(k => k.name);


<total_tokens>14949595 tokens left</total_tokens>


  if (state === 'menu'){
    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
    if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;
    if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;
    if (skinUnlocked(skinSel) && skinSel !== equippedSkin){
      equippedSkin = skinSel; store('skin', equippedSkin); S.coin();
    }
    if (hit('Digit1')) return startGame('levels');
    if (hit('Digit2')) return startGame('endless');
    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
    return;
  }

  if (state === 'menu'){
    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
    if (menuSel === 1){                       // řádek Nekonečný běh → šipky mění obtížnost
      if (hit('ArrowLeft', 'KeyA'))  diffSel = (diffSel + 2) % 3;
      if (hit('ArrowRight', 'KeyD')) diffSel = (diffSel + 1) % 3;
      endlessDiff = DIFF_KEYS[diffSel];
    } else {                                  // řádek Příběhové levely → šipky mění barvu
      if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;
      if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;
      if (skinUnlocked(skinSel) && skinSel !== equippedSkin){
        equippedSkin = skinSel; store('skin', equippedSkin); S.coin();
      }
    }
    if (hit('Digit1')) return startGame('levels');
    if (hit('Digit2')) return startGame('endless');
    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
    return;
  }


  if (state === 'menu'){
    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
    if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;
    if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;
    if (skinUnlocked(skinSel) && skinSel !== equippedSkin){
      equippedSkin = skinSel; store('skin', equippedSkin); S.coin();
    }
    if (hit('Digit1')) return startGame('levels');
    if (hit('Digit2')) return startGame('endless');
    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
    return;
  }

  if (state === 'menu'){
    if (hit('ArrowUp', 'ArrowDown', 'KeyW', 'KeyS')) menuSel = 1 - menuSel;
    if (menuSel === 1){                       // řádek Nekonečný běh → šipky mění obtížnost
      if (hit('ArrowLeft', 'KeyA'))  diffSel = (diffSel + 2) % 3;
      if (hit('ArrowRight', 'KeyD')) diffSel = (diffSel + 1) % 3;
      endlessDiff = DIFF_KEYS[diffSel];
    } else {                                  // řádek Příběhové levely → šipky mění barvu
      if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;
      if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;
      if (skinUnlocked(skinSel) && skinSel !== equippedSkin){
        equippedSkin = skinSel; store('skin', equippedSkin); S.coin();
      }
    }
    if (hit('Digit1')) return startGame('levels');
    if (hit('Digit2')) return startGame('endless');
    if (hit('Enter', 'Space')) return startGame(menuSel === 0 ? 'levels' : 'endless');
    return;
  }

-    if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;

-    if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;

-    if (skinUnlocked(skinSel) && skinSel !== equippedSkin){

-      equippedSkin = skinSel; store('skin', equippedSkin); S.coin();

+    if (menuSel === 1){                       // řádek Nekonečný běh → šipky mění obtížnost

+      if (hit('ArrowLeft', 'KeyA'))  diffSel = (diffSel + 2) % 3;

+      if (hit('ArrowRight', 'KeyD')) diffSel = (diffSel + 1) % 3;

+      endlessDiff = DIFF_KEYS[diffSel];

+    } else {                                  // řádek Příběhové levely → šipky mění barvu

+      if (hit('ArrowLeft', 'KeyA'))  skinSel = (skinSel - 1 + SKINS.length) % SKINS.length;

+      if (hit('ArrowRight', 'KeyD')) skinSel = (skinSel + 1) % SKINS.length;

+      if (skinUnlocked(skinSel) && skinSel !== equippedSkin){

+        equippedSkin = skinSel; store('skin', equippedSkin); S.coin();


<total_tokens>14948553 tokens left</total_tokens>


1155	    ctx.font = '400 13px system-ui, sans-serif';
1156	    ctx.fillText(it.desc + '     ·     koupeno ' + it.bought + '×', SHOP_X + 18, y + 46);
1157
1158	    ctx.textAlign = 'right';
1159	    ctx.fillStyle = ok ? COL.coin : '#ff6b7d';
1160	    ctx.font = '700 18px system-ui, sans-serif';
1161	    ctx.fillText('● ' + price, SHOP_X + SHOP_W - 18, y + 38);
1162	  });
1163
1164	  const by = shopBtnY();
1165	  ctx.fillStyle = 'rgba(74,222,128,.14)';
1166	  roundRect(W/2 - 120, by, 240, 44, 12); ctx.fill();
1167	  ctx.strokeStyle = COL.flag; ctx.lineWidth = 2;
1168	  roundRect(W/2 - 120, by, 240, 44, 12); ctx.stroke();
1169	  ctx.textAlign = 'center';
1170	  ctx.fillStyle = '#e8ecf5';
1171	  ctx.font = '700 16px system-ui, sans-serif';
1172	  ctx.fillText('Pokračovat  →', W/2, by + 28);
1173
1174	  ctx.fillStyle = '#8b93a7';
1175	  ctx.font = '500 14px system-ui, sans-serif';
1176	  ctx.fillText(
1177	    isTouch ? 'ťukni na položku = koupit  ·  ťukni na „Pokračovat“'
1178	            : '↑ ↓ výběr  ·  Enter / číslo = koupit  ·  Mezerník = pokračovat',
1179	    W/2, H - 34);
1180	  ctx.restore();
1181	}
1182
1183	function drawMenu(){
1184	  ctx.save();
1185	  ctx.fillStyle = 'rgba(8,10,16,.85)';
1186	  ctx.fillRect(0, 0, W, H);
1187	  ctx.textAlign = 'center';
1188
1189	  ctx.fillStyle = '#e8ecf5';
1190	  ctx.font = '800 44px system-ui, sans-serif';
1191	  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 118);
1192	  ctx.fillStyle = '#8b93a7';
1193	  ctx.font = '500 16px system-ui, sans-serif';
1194	  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 90);
1195	  ctx.fillStyle = COL.coin;
1196	  ctx.font = '600 14px system-ui, sans-serif';
1197	  ctx.fillText('Rekord v nekonečném běhu: ' + bestHS, W/2, H/2 - 66);
1198
1199	  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 26, menuSel === 0);
1200	  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 42, menuSel === 1);
1201
1202	  drawSkinPicker(H/2 + 118);
1203
1204	  ctx.fillStyle = '#8b93a7';
1205	  ctx.font = '500 13px system-ui, sans-serif';
1206	  ctx.fillText(
1207	    isTouch ? 'ťukni na režim pro start  ·  ťukni na barvu postavy'
1208	            : '↑ ↓ režim  ·  ← → barva postavy  ·  Enter start  ·  1 / 2',
1209	    W/2, H - 26);
1210	  ctx.restore();
1211	}
1212
1213	function drawSkinPicker(y){
1214	  const n = SKINS.length, sw = 34, gap = 12;
1215	  const total = n * sw + (n - 1) * gap;
1216	  let x = W/2 - total / 2;
1217
1218	  ctx.textAlign = 'center';
1219	  ctx.fillStyle = '#8b93a7';
1220	  ctx.font = '600 12px system-ui, sans-serif';
1221	  ctx.fillText('POSTAVA', W/2, y - 14);
1222
1223	  for (let i = 0; i < n; i++){
1224	    const s = SKINS[i], open = skinUnlocked(i);
1225	    ctx.globalAlpha = open ? 1 : 0.25;
1226	    ctx.fillStyle = s.color;
1227	    roundRect(x, y, sw, sw, 8); ctx.fill();
1228	    ctx.globalAlpha = 1;
1229	    if (i === equippedSkin){
1230	      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
1231	      roundRect(x - 2, y - 2, sw + 4, sw + 4, 9); ctx.stroke();
1232	    }
1233	    if (i === skinSel){
1234	      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
1235	      roundRect(x - 6, y - 6, sw + 12, sw + 12, 12); ctx.stroke();
1236	    }
1237	    if (!open){
1238	      ctx.fillStyle = '#e8ecf5';
1239	      ctx.font = '14px system-ui, sans-serif';
1240	      ctx.fillText('🔒', x + sw/2, y + sw/2 + 5);
1241	    }
1242	    x += sw + gap;
1243	  }
1244
1245	  const s = SKINS[skinSel];
1246	  ctx.fillStyle = '#e8ecf5';
1247	  ctx.font = '600 14px system-ui, sans-serif';
1248	  ctx.fillText(
1249	    skinUnlocked(skinSel)
1250	      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
1251	      : s.name + ' 🔒  ·  potřebuješ ' + s.unlock + ' bodů v nekonečném běhu',
1252	    W/2, y + sw + 22
1253	  );
1254	}

    ctx.font = '400 13px system-ui, sans-serif';
    ctx.fillText(it.desc + '     ·     koupeno ' + it.bought + '×', SHOP_X + 18, y + 46);

    ctx.textAlign = 'right';
    ctx.fillStyle = ok ? COL.coin : '#ff6b7d';
    ctx.font = '700 18px system-ui, sans-serif';
    ctx.fillText('● ' + price, SHOP_X + SHOP_W - 18, y + 38);
  });

  const by = shopBtnY();
  ctx.fillStyle = 'rgba(74,222,128,.14)';
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.fill();
  ctx.strokeStyle = COL.flag; ctx.lineWidth = 2;
  roundRect(W/2 - 120, by, 240, 44, 12); ctx.stroke();
  ctx.textAlign = 'center';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 16px system-ui, sans-serif';
  ctx.fillText('Pokračovat  →', W/2, by + 28);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na položku = koupit  ·  ťukni na „Pokračovat“'
            : '↑ ↓ výběr  ·  Enter / číslo = koupit  ·  Mezerník = pokračovat',
    W/2, H - 34);
  ctx.restore();
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 44px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 118);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 16px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 90);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText('Rekord v nekonečném běhu: ' + bestHS, W/2, H/2 - 66);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 26, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 42, menuSel === 1);

  drawSkinPicker(H/2 + 118);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 13px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim pro start  ·  ťukni na barvu postavy'
            : '↑ ↓ režim  ·  ← → barva postavy  ·  Enter start  ·  1 / 2',
    W/2, H - 26);
  ctx.restore();
}

function drawSkinPicker(y){
  const n = SKINS.length, sw = 34, gap = 12;
  const total = n * sw + (n - 1) * gap;
  let x = W/2 - total / 2;

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('POSTAVA', W/2, y - 14);

  for (let i = 0; i < n; i++){
    const s = SKINS[i], open = skinUnlocked(i);
    ctx.globalAlpha = open ? 1 : 0.25;
    ctx.fillStyle = s.color;
    roundRect(x, y, sw, sw, 8); ctx.fill();
    ctx.globalAlpha = 1;
    if (i === equippedSkin){
      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
      roundRect(x - 2, y - 2, sw + 4, sw + 4, 9); ctx.stroke();
    }
    if (i === skinSel){
      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(x - 6, y - 6, sw + 12, sw + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '14px system-ui, sans-serif';
      ctx.fillText('🔒', x + sw/2, y + sw/2 + 5);
    }
    x += sw + gap;
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + ' 🔒  ·  potřebuješ ' + s.unlock + ' bodů v nekonečném běhu',
    W/2, y + sw + 22
  );
}


<total_tokens>14948307 tokens left</total_tokens>


function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 44px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 118);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 16px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 90);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText('Rekord v nekonečném běhu: ' + bestHS, W/2, H/2 - 66);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 26, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 42, menuSel === 1);

  drawSkinPicker(H/2 + 118);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 13px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim pro start  ·  ťukni na barvu postavy'
            : '↑ ↓ režim  ·  ← → barva postavy  ·  Enter start  ·  1 / 2',
    W/2, H - 26);
  ctx.restore();
}

function drawSkinPicker(y){
  const n = SKINS.length, sw = 34, gap = 12;
  const total = n * sw + (n - 1) * gap;
  let x = W/2 - total / 2;

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('POSTAVA', W/2, y - 14);

  for (let i = 0; i < n; i++){
    const s = SKINS[i], open = skinUnlocked(i);
    ctx.globalAlpha = open ? 1 : 0.25;
    ctx.fillStyle = s.color;
    roundRect(x, y, sw, sw, 8); ctx.fill();
    ctx.globalAlpha = 1;
    if (i === equippedSkin){
      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
      roundRect(x - 2, y - 2, sw + 4, sw + 4, 9); ctx.stroke();
    }
    if (i === skinSel){
      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(x - 6, y - 6, sw + 12, sw + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '14px system-ui, sans-serif';
      ctx.fillText('🔒', x + sw/2, y + sw/2 + 5);
    }
    x += sw + gap;
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + ' 🔒  ·  potřebuješ ' + s.unlock + ' bodů v nekonečném běhu',
    W/2, y + sw + 22
  );
}

const MENU = {
  opt1Y: 226, opt2Y: 280, optW: 470, optH: 48,
  diffY: 330, diffBW: 122, diffH: 30, diffGap: 16,
  skinY: 400, skinSW: 30, skinGap: 8, skinNameY: 450,
};
function diffChipRect(i){
  const total = 3 * MENU.diffBW + 2 * MENU.diffGap;
  return { x: W/2 - total/2 + i * (MENU.diffBW + MENU.diffGap), y: MENU.diffY, w: MENU.diffBW, h: MENU.diffH };
}
function skinChipRect(i){
  const n = SKINS.length, total = n * MENU.skinSW + (n - 1) * MENU.skinGap;
  return { x: W/2 - total/2 + i * (MENU.skinSW + MENU.skinGap), y: MENU.skinY, w: MENU.skinSW, h: MENU.skinSW };
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.9)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 40px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, 128);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, 152);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('Nejdál   ·   Lehká ' + bestDist.easy + ' m    ·    Střední ' + bestDist.medium + ' m    ·    Těžká ' + bestDist.hard + ' m', W/2, 176);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', MENU.opt1Y, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Obtížnost: ' + ENDLESS_DIFF[endlessDiff].name + '   ·   jeden život, hraj na vzdálenost', MENU.opt2Y, menuSel === 1);

  drawDiffPicker();
  drawSkinPicker();

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 12px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim = start   ·   ťukni na obtížnost a barvu'
            : '↑↓ režim   ·   ←→ ' + (menuSel === 1 ? 'obtížnost' : 'barva') + '   ·   Enter = start',
    W/2, H - 16);
  ctx.restore();
}

function drawDiffPicker(){
  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 11px system-ui, sans-serif';
  ctx.fillText('OBTÍŽNOST NEKONEČNÉHO BĚHU', W/2, MENU.diffY - 10);

  DIFF_KEYS.forEach((k, i) => {
    const b = diffChipRect(i), sel = i === diffSel;
    ctx.fillStyle = sel ? 'rgba(92,200,255,.18)' : 'rgba(255,255,255,.05)';
    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.14)';
    ctx.lineWidth = 2; roundRect(b.x, b.y, b.w, b.h, 8); ctx.stroke();
    ctx.fillStyle = sel ? '#e8ecf5' : '#aab2c2';
    ctx.font = '700 14px system-ui, sans-serif';
    ctx.textBaseline = 'middle';
    ctx.fillText(ENDLESS_DIFF[k].name, b.x + b.w/2, b.y + b.h/2 + 1);
    ctx.textBaseline = 'alphabetic';
  });
}

function drawSkinPicker(){
  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 11px system-ui, sans-serif';
  ctx.fillText('BARVA POSTAVY', W/2, MENU.skinY - 10);

  for (let i = 0; i < SKINS.length; i++){
    const b = skinChipRect(i), s = SKINS[i], open = skinUnlocked(i);
    ctx.globalAlpha = open ? 1 : 0.22;
    ctx.fillStyle = s.color;
    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
    ctx.globalAlpha = 1;
    if (i === equippedSkin){
      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
      roundRect(b.x - 2, b.y - 2, b.w + 4, b.h + 4, 9); ctx.stroke();
    }
    if (i === skinSel && menuSel === 0){
      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(b.x - 6, b.y - 6, b.w + 12, b.h + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '12px system-ui, sans-serif';
      ctx.textBaseline = 'middle';
      ctx.fillText('🔒', b.x + b.w/2, b.y + b.h/2 + 1);
      ctx.textBaseline = 'alphabetic';
    }
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 13px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + '  🔒  ·  ' + ENDLESS_DIFF[s.diff].name + ' ' + s.dist + ' m',
    W/2, MENU.skinNameY);
}


function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.85)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 44px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 118);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 16px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 90);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText('Rekord v nekonečném běhu: ' + bestHS, W/2, H/2 - 66);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 26, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 42, menuSel === 1);

  drawSkinPicker(H/2 + 118);

  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 13px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim pro start  ·  ťukni na barvu postavy'
            : '↑ ↓ režim  ·  ← → barva postavy  ·  Enter start  ·  1 / 2',
    W/2, H - 26);
  ctx.restore();
}

function drawSkinPicker(y){
  const n = SKINS.length, sw = 34, gap = 12;
  const total = n * sw + (n - 1) * gap;
  let x = W/2 - total / 2;

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('POSTAVA', W/2, y - 14);

  for (let i = 0; i < n; i++){
    const s = SKINS[i], open = skinUnlocked(i);
    ctx.globalAlpha = open ? 1 : 0.25;
    ctx.fillStyle = s.color;
    roundRect(x, y, sw, sw, 8); ctx.fill();
    ctx.globalAlpha = 1;
    if (i === equippedSkin){
      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
      roundRect(x - 2, y - 2, sw + 4, sw + 4, 9); ctx.stroke();
    }
    if (i === skinSel){
      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(x - 6, y - 6, sw + 12, sw + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '14px system-ui, sans-serif';
      ctx.fillText('🔒', x + sw/2, y + sw/2 + 5);
    }
    x += sw + gap;
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 14px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + ' 🔒  ·  potřebuješ ' + s.unlock + ' bodů v nekonečném běhu',
    W/2, y + sw + 22
  );
}

const MENU = {
  opt1Y: 226, opt2Y: 280, optW: 470, optH: 48,
  diffY: 330, diffBW: 122, diffH: 30, diffGap: 16,
  skinY: 400, skinSW: 30, skinGap: 8, skinNameY: 450,
};
function diffChipRect(i){
  const total = 3 * MENU.diffBW + 2 * MENU.diffGap;
  return { x: W/2 - total/2 + i * (MENU.diffBW + MENU.diffGap), y: MENU.diffY, w: MENU.diffBW, h: MENU.diffH };
}
function skinChipRect(i){
  const n = SKINS.length, total = n * MENU.skinSW + (n - 1) * MENU.skinGap;
  return { x: W/2 - total/2 + i * (MENU.skinSW + MENU.skinGap), y: MENU.skinY, w: MENU.skinSW, h: MENU.skinSW };
}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.9)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 40px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, 128);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, 152);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('Nejdál   ·   Lehká ' + bestDist.easy + ' m    ·    Střední ' + bestDist.medium + ' m    ·    Těžká ' + bestDist.hard + ' m', W/2, 176);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', MENU.opt1Y, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Obtížnost: ' + ENDLESS_DIFF[endlessDiff].name + '   ·   jeden život, hraj na vzdálenost', MENU.opt2Y, menuSel === 1);

  drawDiffPicker();
  drawSkinPicker();

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 12px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim = start   ·   ťukni na obtížnost a barvu'
            : '↑↓ režim   ·   ←→ ' + (menuSel === 1 ? 'obtížnost' : 'barva') + '   ·   Enter = start',
    W/2, H - 16);
  ctx.restore();
}

function drawDiffPicker(){
  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 11px system-ui, sans-serif';
  ctx.fillText('OBTÍŽNOST NEKONEČNÉHO BĚHU', W/2, MENU.diffY - 10);

  DIFF_KEYS.forEach((k, i) => {
    const b = diffChipRect(i), sel = i === diffSel;
    ctx.fillStyle = sel ? 'rgba(92,200,255,.18)' : 'rgba(255,255,255,.05)';
    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.14)';
    ctx.lineWidth = 2; roundRect(b.x, b.y, b.w, b.h, 8); ctx.stroke();
    ctx.fillStyle = sel ? '#e8ecf5' : '#aab2c2';
    ctx.font = '700 14px system-ui, sans-serif';
    ctx.textBaseline = 'middle';
    ctx.fillText(ENDLESS_DIFF[k].name, b.x + b.w/2, b.y + b.h/2 + 1);
    ctx.textBaseline = 'alphabetic';
  });
}

function drawSkinPicker(){
  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 11px system-ui, sans-serif';
  ctx.fillText('BARVA POSTAVY', W/2, MENU.skinY - 10);

  for (let i = 0; i < SKINS.length; i++){
    const b = skinChipRect(i), s = SKINS[i], open = skinUnlocked(i);
    ctx.globalAlpha = open ? 1 : 0.22;
    ctx.fillStyle = s.color;
    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
    ctx.globalAlpha = 1;
    if (i === equippedSkin){
      ctx.strokeStyle = '#e8ecf5'; ctx.lineWidth = 3;
      roundRect(b.x - 2, b.y - 2, b.w + 4, b.h + 4, 9); ctx.stroke();
    }
    if (i === skinSel && menuSel === 0){
      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(b.x - 6, b.y - 6, b.w + 12, b.h + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '12px system-ui, sans-serif';
      ctx.textBaseline = 'middle';
      ctx.fillText('🔒', b.x + b.w/2, b.y + b.h/2 + 1);
      ctx.textBaseline = 'alphabetic';
    }
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 13px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + '  🔒  ·  ' + ENDLESS_DIFF[s.diff].name + ' ' + s.dist + ' m',
    W/2, MENU.skinNameY);
}

+  diffY: 330, diffBW: 122, diffH: 30, diffGap: 16,

+  skinY: 400, skinSW: 30, skinGap: 8, skinNameY: 450,

+  const total = 3 * MENU.diffBW + 2 * MENU.diffGap;

+  return { x: W/2 - total/2 + i * (MENU.diffBW + MENU.diffGap), y: MENU.diffY, w: MENU.diffBW, h: MENU.diffH };

+  const n = SKINS.length, total = n * MENU.skinSW + (n - 1) * MENU.skinGap;

+  return { x: W/2 - total/2 + i * (MENU.skinSW + MENU.skinGap), y: MENU.skinY, w: MENU.skinSW, h: MENU.skinSW };

-  ctx.font = '800 44px system-ui, sans-serif';

-  ctx.fillText('HRANATÝ SVĚT', W/2, H/2 - 118);

+  ctx.font = '800 40px system-ui, sans-serif';

+  ctx.fillText('HRANATÝ SVĚT', W/2, 128);

-  ctx.font = '500 16px system-ui, sans-serif';

-  ctx.fillText('2D plošinovka — vyber si režim', W/2, H/2 - 90);

+  ctx.font = '500 14px system-ui, sans-serif';

+  ctx.fillText('2D plošinovka — vyber si režim', W/2, 152);

-  ctx.font = '600 14px system-ui, sans-serif';

-  ctx.fillText('Rekord v nekonečném běhu: ' + bestHS, W/2, H/2 - 66);

+  ctx.font = '600 12px system-ui, sans-serif';

+  ctx.fillText('Nejdál   ·   Lehká ' + bestDist.easy + ' m    ·    Střední ' + bestDist.medium + ' m    ·    Těžká ' + bestDist.hard + ' m', W/2, 176);

-  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', H/2 - 26, menuSel === 0);

-  menuOption('2', 'Nekonečný běh', 'Náhodná trať, která zrychluje — hraj na skóre', H/2 + 42, menuSel === 1);

+  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', MENU.opt1Y, menuSel === 0);

+  menuOption('2', 'Nekonečný běh', 'Obtížnost: ' + ENDLESS_DIFF[endlessDiff].name + '   ·   jeden život, hraj na vzdálenost', MENU.opt2Y, menuSel === 1);

-  ctx.font = '500 13px system-ui, sans-serif';

+  ctx.font = '500 12px system-ui, sans-serif';

-    isTouch ? 'ťukni na režim pro start  ·  ťukni na barvu postavy'

-            : '↑ ↓ režim  ·  ← → barva postavy  ·  Enter start  ·  1 / 2',

+    isTouch ? 'ťukni na režim = start   ·   ťukni na obtížnost a barvu'

+            : '↑↓ režim   ·   ←→ ' + (menuSel === 1 ? 'obtížnost' : 'barva') + '   ·   Enter = start',

-  const n = SKINS.length, sw = 34, gap = 12;

-  const total = n * sw + (n - 1) * gap;

+  ctx.font = '600 11px system-ui, sans-serif';

+  ctx.fillText('OBTÍŽNOST NEKONEČNÉHO BĚHU', W/2, MENU.diffY - 10);

+    const b = diffChipRect(i), sel = i === diffSel;

+    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();

+    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.14)';

+    ctx.lineWidth = 2; roundRect(b.x, b.y, b.w, b.h, 8); ctx.stroke();

+    ctx.fillStyle = sel ? '#e8ecf5' : '#aab2c2';

+    ctx.font = '700 14px system-ui, sans-serif';

+    ctx.fillText(ENDLESS_DIFF[k].name, b.x + b.w/2, b.y + b.h/2 + 1);

-  ctx.font = '600 12px system-ui, sans-serif';

-  ctx.fillText('POSTAVA', W/2, y - 14);

+  ctx.fillText('BARVA POSTAVY', W/2, MENU.skinY - 10);

-    const s = SKINS[i], open = skinUnlocked(i);

+  for (let i = 0; i < SKINS.length; i++){

+    const b = skinChipRect(i), s = SKINS[i], open = skinUnlocked(i);

-    roundRect(x, y, sw, sw, 8); ctx.fill();

-      roundRect(x - 2, y - 2, sw + 4, sw + 4, 9); ctx.stroke();

+      roundRect(b.x - 2, b.y - 2, b.w + 4, b.h + 4, 9); ctx.stroke();

+    if (i === skinSel && menuSel === 0){

-      roundRect(x - 6, y - 6, sw + 12, sw + 12, 12); ctx.stroke();

-      ctx.font = '14px system-ui, sans-serif';

-      ctx.fillText('🔒', x + sw/2, y + sw/2 + 5);

+      ctx.font = '12px system-ui, sans-serif';

+  ctx.font = '600 13px system-ui, sans-serif';

-      : s.name + ' 🔒  ·  potřebuješ ' + s.unlock + ' bodů v nekonečném běhu',

+      : s.name + '  🔒  ·  ' + ENDLESS_DIFF[s.diff].name + ' ' + s.dist + ' m',

function menuOption(num, title, desc, y, sel){


<total_tokens>14942189 tokens left</total_tokens>


1266	      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
1267	      roundRect(b.x - 6, b.y - 6, b.w + 12, b.h + 12, 12); ctx.stroke();
1268	    }
1269	    if (!open){
1270	      ctx.fillStyle = '#e8ecf5';
1271	      ctx.font = '12px system-ui, sans-serif';
1272	      ctx.textBaseline = 'middle';
1273	      ctx.fillText('🔒', b.x + b.w/2, b.y + b.h/2 + 1);
1274	      ctx.textBaseline = 'alphabetic';
1275	    }
1276	  }
1277
1278	  const s = SKINS[skinSel];
1279	  ctx.fillStyle = '#e8ecf5';
1280	  ctx.font = '600 13px system-ui, sans-serif';
1281	  ctx.fillText(
1282	    skinUnlocked(skinSel)
1283	      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
1284	      : s.name + '  🔒  ·  ' + ENDLESS_DIFF[s.diff].name + ' ' + s.dist + ' m',
1285	    W/2, MENU.skinNameY);
1286	}
1287

      ctx.strokeStyle = COL.player; ctx.lineWidth = 2;
      roundRect(b.x - 6, b.y - 6, b.w + 12, b.h + 12, 12); ctx.stroke();
    }
    if (!open){
      ctx.fillStyle = '#e8ecf5';
      ctx.font = '12px system-ui, sans-serif';
      ctx.textBaseline = 'middle';
      ctx.fillText('🔒', b.x + b.w/2, b.y + b.h/2 + 1);
      ctx.textBaseline = 'alphabetic';
    }
  }

  const s = SKINS[skinSel];
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '600 13px system-ui, sans-serif';
  ctx.fillText(
    skinUnlocked(skinSel)
      ? s.name + (skinSel === equippedSkin ? '  ✓ nasazeno' : '')
      : s.name + '  🔒  ·  ' + ENDLESS_DIFF[s.diff].name + ' ' + s.dist + ' m',
    W/2, MENU.skinNameY);
}


<total_tokens>14941974 tokens left</total_tokens>


1287
1288	function menuOption(num, title, desc, y, sel){
1289	  const w = 470, x = W/2 - w/2, h = 58;
1290	  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
1291	  roundRect(x, y - h/2, w, h, 12); ctx.fill();
1292	  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
1293	  ctx.lineWidth = 2;
1294	  roundRect(x, y - h/2, w, h, 12); ctx.stroke();
1295
1296	  ctx.textAlign = 'left';
1297	  ctx.fillStyle = '#e8ecf5';
1298	  ctx.font = '700 18px system-ui, sans-serif';
1299	  ctx.fillText(num + '.   ' + title, x + 20, y - 3);
1300	  ctx.fillStyle = '#8b93a7';
1301	  ctx.font = '400 13px system-ui, sans-serif';
1302	  ctx.fillText(desc, x + 20, y + 17);
1303	  ctx.textAlign = 'center';
1304	}
1305
1306	/* ------------------------------------------------------------------ */
1307	/*  Myš / dotyk pro menu a obrazovky                                  */
1308	/* ------------------------------------------------------------------ */

function menuOption(num, title, desc, y, sel){
  const w = 470, x = W/2 - w/2, h = 58;
  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
  roundRect(x, y - h/2, w, h, 12); ctx.fill();
  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
  ctx.lineWidth = 2;
  roundRect(x, y - h/2, w, h, 12); ctx.stroke();

  ctx.textAlign = 'left';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 18px system-ui, sans-serif';
  ctx.fillText(num + '.   ' + title, x + 20, y - 3);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '400 13px system-ui, sans-serif';
  ctx.fillText(desc, x + 20, y + 17);
  ctx.textAlign = 'center';
}

/* ------------------------------------------------------------------ */
/*  Myš / dotyk pro menu a obrazovky                                  */
/* ------------------------------------------------------------------ */


<total_tokens>14941356 tokens left</total_tokens>


function menuOption(num, title, desc, y, sel){
  const w = 470, x = W/2 - w/2, h = 58;
  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
  roundRect(x, y - h/2, w, h, 12); ctx.fill();
  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
  ctx.lineWidth = 2;
  roundRect(x, y - h/2, w, h, 12); ctx.stroke();

  ctx.textAlign = 'left';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 18px system-ui, sans-serif';
  ctx.fillText(num + '.   ' + title, x + 20, y - 3);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '400 13px system-ui, sans-serif';
  ctx.fillText(desc, x + 20, y + 17);
  ctx.textAlign = 'center';
}

function menuOption(num, title, desc, y, sel){
  const w = MENU.optW, x = W/2 - w/2, h = MENU.optH;
  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
  roundRect(x, y - h/2, w, h, 12); ctx.fill();
  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
  ctx.lineWidth = 2;
  roundRect(x, y - h/2, w, h, 12); ctx.stroke();

  ctx.textAlign = 'left';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 17px system-ui, sans-serif';
  ctx.fillText(num + '.   ' + title, x + 20, y - 4);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '400 12px system-ui, sans-serif';
  ctx.fillText(desc, x + 20, y + 15);
  ctx.textAlign = 'center';
}


function menuOption(num, title, desc, y, sel){
  const w = 470, x = W/2 - w/2, h = 58;
  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
  roundRect(x, y - h/2, w, h, 12); ctx.fill();
  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
  ctx.lineWidth = 2;
  roundRect(x, y - h/2, w, h, 12); ctx.stroke();

  ctx.textAlign = 'left';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 18px system-ui, sans-serif';
  ctx.fillText(num + '.   ' + title, x + 20, y - 3);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '400 13px system-ui, sans-serif';
  ctx.fillText(desc, x + 20, y + 17);
  ctx.textAlign = 'center';
}

function menuOption(num, title, desc, y, sel){
  const w = MENU.optW, x = W/2 - w/2, h = MENU.optH;
  ctx.fillStyle = sel ? 'rgba(92,200,255,.16)' : 'rgba(255,255,255,.04)';
  roundRect(x, y - h/2, w, h, 12); ctx.fill();
  ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.12)';
  ctx.lineWidth = 2;
  roundRect(x, y - h/2, w, h, 12); ctx.stroke();

  ctx.textAlign = 'left';
  ctx.fillStyle = '#e8ecf5';
  ctx.font = '700 17px system-ui, sans-serif';
  ctx.fillText(num + '.   ' + title, x + 20, y - 4);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '400 12px system-ui, sans-serif';
  ctx.fillText(desc, x + 20, y + 15);
  ctx.textAlign = 'center';
}

+  const w = MENU.optW, x = W/2 - w/2, h = MENU.optH;

-  ctx.font = '700 18px system-ui, sans-serif';

-  ctx.fillText(num + '.   ' + title, x + 20, y - 3);

+  ctx.font = '700 17px system-ui, sans-serif';

+  ctx.fillText(num + '.   ' + title, x + 20, y - 4);

-  ctx.font = '400 13px system-ui, sans-serif';

+  ctx.font = '400 12px system-ui, sans-serif';


<total_tokens>14939916 tokens left</total_tokens>


1338	      if (joy.id === null && p.x < W * 0.6){      // levá část obrazovky = joystick
1339	        joyStart(e.pointerId, p);
1340	        return;
1341	      }
1342	    }
1343	  }
1344
1345	  if (state === 'menu'){
1346	    const ys = [H/2 - 26, H/2 + 42];
1347	    for (let i = 0; i < 2; i++)
1348	      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
1349	        return startGame(i === 0 ? 'levels' : 'endless');
1350	    // klik na barvu postavy
1351	    const n = SKINS.length, sw = 34, gap = 12, sy = H/2 + 118;
1352	    let sx = W/2 - (n * sw + (n - 1) * gap) / 2;
1353	    for (let i = 0; i < n; i++){
1354	      if (p.x >= sx && p.x <= sx + sw && p.y >= sy && p.y <= sy + sw){
1355	        skinSel = i;
1356	        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
1357	        return;
1358	      }
1359	      sx += sw + gap;
1360	    }
1361	  } else if (state === 'shop'){
1362	    for (let i = 0; i < SHOP.length; i++){
1363	      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);

      if (joy.id === null && p.x < W * 0.6){      // levá část obrazovky = joystick
        joyStart(e.pointerId, p);
        return;
      }
    }
  }

  if (state === 'menu'){
    const ys = [H/2 - 26, H/2 + 42];
    for (let i = 0; i < 2; i++)
      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
        return startGame(i === 0 ? 'levels' : 'endless');
    // klik na barvu postavy
    const n = SKINS.length, sw = 34, gap = 12, sy = H/2 + 118;
    let sx = W/2 - (n * sw + (n - 1) * gap) / 2;
    for (let i = 0; i < n; i++){
      if (p.x >= sx && p.x <= sx + sw && p.y >= sy && p.y <= sy + sw){
        skinSel = i;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
      sx += sw + gap;
    }
  } else if (state === 'shop'){
    for (let i = 0; i < SHOP.length; i++){
      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);


<total_tokens>14939708 tokens left</total_tokens>


  if (state === 'menu'){
    const ys = [H/2 - 26, H/2 + 42];
    for (let i = 0; i < 2; i++)
      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
        return startGame(i === 0 ? 'levels' : 'endless');
    // klik na barvu postavy
    const n = SKINS.length, sw = 34, gap = 12, sy = H/2 + 118;
    let sx = W/2 - (n * sw + (n - 1) * gap) / 2;
    for (let i = 0; i < n; i++){
      if (p.x >= sx && p.x <= sx + sw && p.y >= sy && p.y <= sy + sw){
        skinSel = i;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
      sx += sw + gap;
    }
  } else if (state === 'shop'){

  if (state === 'menu'){
    // volba režimu (ťuknutí = start)
    if (Math.abs(p.x - W/2) <= MENU.optW/2){
      if (Math.abs(p.y - MENU.opt1Y) <= MENU.optH/2 + 4){ menuSel = 0; return startGame('levels'); }
      if (Math.abs(p.y - MENU.opt2Y) <= MENU.optH/2 + 4){ menuSel = 1; return startGame('endless'); }
    }
    // volba obtížnosti
    for (let i = 0; i < 3; i++){
      const b = diffChipRect(i);
      if (p.x >= b.x - 4 && p.x <= b.x + b.w + 4 && p.y >= b.y - 6 && p.y <= b.y + b.h + 6){
        diffSel = i; endlessDiff = DIFF_KEYS[i]; menuSel = 1; S.coin();
        return;
      }
    }
    // volba barvy postavy
    for (let i = 0; i < SKINS.length; i++){
      const b = skinChipRect(i);
      if (p.x >= b.x - 3 && p.x <= b.x + b.w + 3 && p.y >= b.y - 3 && p.y <= b.y + b.h + 3){
        skinSel = i; menuSel = 0;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
    }
  } else if (state === 'shop'){


  if (state === 'menu'){
    const ys = [H/2 - 26, H/2 + 42];
    for (let i = 0; i < 2; i++)
      if (Math.abs(p.y - ys[i]) < 29 && Math.abs(p.x - W/2) < 235)
        return startGame(i === 0 ? 'levels' : 'endless');
    // klik na barvu postavy
    const n = SKINS.length, sw = 34, gap = 12, sy = H/2 + 118;
    let sx = W/2 - (n * sw + (n - 1) * gap) / 2;
    for (let i = 0; i < n; i++){
      if (p.x >= sx && p.x <= sx + sw && p.y >= sy && p.y <= sy + sw){
        skinSel = i;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
      sx += sw + gap;
    }
  } else if (state === 'shop'){

  if (state === 'menu'){
    // volba režimu (ťuknutí = start)
    if (Math.abs(p.x - W/2) <= MENU.optW/2){
      if (Math.abs(p.y - MENU.opt1Y) <= MENU.optH/2 + 4){ menuSel = 0; return startGame('levels'); }
      if (Math.abs(p.y - MENU.opt2Y) <= MENU.optH/2 + 4){ menuSel = 1; return startGame('endless'); }
    }
    // volba obtížnosti
    for (let i = 0; i < 3; i++){
      const b = diffChipRect(i);
      if (p.x >= b.x - 4 && p.x <= b.x + b.w + 4 && p.y >= b.y - 6 && p.y <= b.y + b.h + 6){
        diffSel = i; endlessDiff = DIFF_KEYS[i]; menuSel = 1; S.coin();
        return;
      }
    }
    // volba barvy postavy
    for (let i = 0; i < SKINS.length; i++){
      const b = skinChipRect(i);
      if (p.x >= b.x - 3 && p.x <= b.x + b.w + 3 && p.y >= b.y - 3 && p.y <= b.y + b.h + 3){
        skinSel = i; menuSel = 0;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
    }
  } else if (state === 'shop'){

-        return startGame(i === 0 ? 'levels' : 'endless');

-    const n = SKINS.length, sw = 34, gap = 12, sy = H/2 + 118;

+    if (Math.abs(p.x - W/2) <= MENU.optW/2){

+      if (Math.abs(p.y - MENU.opt1Y) <= MENU.optH/2 + 4){ menuSel = 0; return startGame('levels'); }

+      if (Math.abs(p.y - MENU.opt2Y) <= MENU.optH/2 + 4){ menuSel = 1; return startGame('endless'); }

+        diffSel = i; endlessDiff = DIFF_KEYS[i]; menuSel = 1; S.coin();

+    for (let i = 0; i < SKINS.length; i++){


<total_tokens>14938097 tokens left</total_tokens>


Bash

grep -n "\.unlock\|drawSkinPicker(\|rollTier(diff)\|const diff =\|diff \* 0\| diff)" hra.html; echo "--- brackets ---"; python3 - <<'EOF'
import re
js=re.search(r'<script>(.*)</script>',open('hra.html').read(),re.S).group(1)
for a,b in [('(',')'),('{','}'),('[',']')]:
    print(a,b,js.count(a),js.count(b),'OK' if js.count(a)==js.count(b) else 'MISMATCH')
EOF
echo "--- serve ---"; curl -sf -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8000/hra.html

Check for stale references and bracket balance


1217:  drawSkinPicker();
1249:function drawSkinPicker(){
--- brackets ---
( ) 995 995 OK
{ } 226 226 OK
[ ] 174 174 OK
--- serve ---
HTTP 200


<total_tokens>14937595 tokens left</total_tokens>


Edit

  easy:   { name: 'Lehká',   gapChance: 0.10, gapMax: 3, enemy1: 0.35, enemy2: 0.10, tierBias: -0.35, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.5 },
  medium: { name: 'Střední', gapChance: 0.20, gapMax: 4, enemy1: 0.55, enemy2: 0.42, tierBias:  0.00, wallBase: 1.3, wallRamp: 3.3, enemyMul: 1.0 },
  hard:   { name: 'Těžká',   gapChance: 0.30, gapMax: 5, enemy1: 0.82, enemy2: 0.78, tierBias:  0.42, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.6 },

  easy:   { name: 'Lehká',   gapChance: 0.09, gapMax: 3, enemy1: 0.30, enemy2: 0.06, tierBias: -0.55, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.45 },
  medium: { name: 'Střední', gapChance: 0.20, gapMax: 4, enemy1: 0.55, enemy2: 0.42, tierBias:  0.00, wallBase: 1.3, wallRamp: 3.3, enemyMul: 1.0 },
  hard:   { name: 'Těžká',   gapChance: 0.32, gapMax: 5, enemy1: 0.85, enemy2: 0.80, tierBias:  0.50, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.7 },


  easy:   { name: 'Lehká',   gapChance: 0.10, gapMax: 3, enemy1: 0.35, enemy2: 0.10, tierBias: -0.35, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.5 },
  medium: { name: 'Střední', gapChance: 0.20, gapMax: 4, enemy1: 0.55, enemy2: 0.42, tierBias:  0.00, wallBase: 1.3, wallRamp: 3.3, enemyMul: 1.0 },
  hard:   { name: 'Těžká',   gapChance: 0.30, gapMax: 5, enemy1: 0.82, enemy2: 0.78, tierBias:  0.42, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.6 },

  easy:   { name: 'Lehká',   gapChance: 0.09, gapMax: 3, enemy1: 0.30, enemy2: 0.06, tierBias: -0.55, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.45 },
  medium: { name: 'Střední', gapChance: 0.20, gapMax: 4, enemy1: 0.55, enemy2: 0.42, tierBias:  0.00, wallBase: 1.3, wallRamp: 3.3, enemyMul: 1.0 },
  hard:   { name: 'Těžká',   gapChance: 0.32, gapMax: 5, enemy1: 0.85, enemy2: 0.80, tierBias:  0.50, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.7 },

const DIFF_KEYS = ['easy', 'medium', 'hard'];

-  easy:   { name: 'Lehká',   gapChance: 0.10, gapMax: 3, enemy1: 0.35, enemy2: 0.10, tierBias: -0.35, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.5 },

+  easy:   { name: 'Lehká',   gapChance: 0.09, gapMax: 3, enemy1: 0.30, enemy2: 0.06, tierBias: -0.55, wallBase: 1.0, wallRamp: 2.2, enemyMul: 0.45 },

-  hard:   { name: 'Těžká',   gapChance: 0.30, gapMax: 5, enemy1: 0.82, enemy2: 0.78, tierBias:  0.42, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.6 },

+  hard:   { name: 'Těžká',   gapChance: 0.32, gapMax: 5, enemy1: 0.85, enemy2: 0.80, tierBias:  0.50, wallBase: 1.6, wallRamp: 4.2, enemyMul: 1.7 },


<total_tokens>14933336 tokens left</total_tokens>


Bash

which node nodejs deno bun qjs d8 rhino 2>/dev/null; ls ~/.nvm/versions/node/*/bin/node 2>/dev/null; npm ls 2>/dev/null | head -1; echo "checking python-based JS lint via esprima?"; python3 -c "import esprima" 2>&1 | head -1

Look for any JS runtime or linter


checking python-based JS lint via esprima?
Traceback (most recent call last):


<total_tokens>14932328 tokens left</total_tokens>


1195	}
1196
1197	function drawMenu(){
1198	  ctx.save();
1199	  ctx.fillStyle = 'rgba(8,10,16,.9)';
1200	  ctx.fillRect(0, 0, W, H);
1201	  ctx.textAlign = 'center';
1202
1203	  ctx.fillStyle = '#e8ecf5';
1204	  ctx.font = '800 40px system-ui, sans-serif';
1205	  ctx.fillText('HRANATÝ SVĚT', W/2, 128);
1206	  ctx.fillStyle = '#8b93a7';
1207	  ctx.font = '500 14px system-ui, sans-serif';
1208	  ctx.fillText('2D plošinovka — vyber si režim', W/2, 152);
1209	  ctx.fillStyle = COL.coin;
1210	  ctx.font = '600 12px system-ui, sans-serif';
1211	  ctx.fillText('Nejdál   ·   Lehká ' + bestDist.easy + ' m    ·    Střední ' + bestDist.medium + ' m    ·    Těžká ' + bestDist.hard + ' m', W/2, 176);
1212
1213	  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', MENU.opt1Y, menuSel === 0);
1214	  menuOption('2', 'Nekonečný běh', 'Obtížnost: ' + ENDLESS_DIFF[endlessDiff].name + '   ·   jeden život, hraj na vzdálenost', MENU.opt2Y, menuSel === 1);
1215
1216	  drawDiffPicker();
1217	  drawSkinPicker();
1218
1219	  ctx.textAlign = 'center';
1220	  ctx.fillStyle = '#8b93a7';
1221	  ctx.font = '500 12px system-ui, sans-serif';
1222	  ctx.fillText(
1223	    isTouch ? 'ťukni na režim = start   ·   ťukni na obtížnost a barvu'
1224	            : '↑↓ režim   ·   ←→ ' + (menuSel === 1 ? 'obtížnost' : 'barva') + '   ·   Enter = start',
1225	    W/2, H - 16);
1226	  ctx.restore();
1227	}
1228
1229	function drawDiffPicker(){
1230	  ctx.textAlign = 'center';
1231	  ctx.fillStyle = '#8b93a7';
1232	  ctx.font = '600 11px system-ui, sans-serif';
1233	  ctx.fillText('OBTÍŽNOST NEKONEČNÉHO BĚHU', W/2, MENU.diffY - 10);
1234
1235	  DIFF_KEYS.forEach((k, i) => {
1236	    const b = diffChipRect(i), sel = i === diffSel;
1237	    ctx.fillStyle = sel ? 'rgba(92,200,255,.18)' : 'rgba(255,255,255,.05)';
1238	    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
1239	    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.14)';
1240	    ctx.lineWidth = 2; roundRect(b.x, b.y, b.w, b.h, 8); ctx.stroke();
1241	    ctx.fillStyle = sel ? '#e8ecf5' : '#aab2c2';
1242	    ctx.font = '700 14px system-ui, sans-serif';
1243	    ctx.textBaseline = 'middle';
1244	    ctx.fillText(ENDLESS_DIFF[k].name, b.x + b.w/2, b.y + b.h/2 + 1);
1245	    ctx.textBaseline = 'alphabetic';
1246	  });
1247	}
1248
1249	function drawSkinPicker(){

}

function drawMenu(){
  ctx.save();
  ctx.fillStyle = 'rgba(8,10,16,.9)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';

  ctx.fillStyle = '#e8ecf5';
  ctx.font = '800 40px system-ui, sans-serif';
  ctx.fillText('HRANATÝ SVĚT', W/2, 128);
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 14px system-ui, sans-serif';
  ctx.fillText('2D plošinovka — vyber si režim', W/2, 152);
  ctx.fillStyle = COL.coin;
  ctx.font = '600 12px system-ui, sans-serif';
  ctx.fillText('Nejdál   ·   Lehká ' + bestDist.easy + ' m    ·    Střední ' + bestDist.medium + ' m    ·    Těžká ' + bestDist.hard + ' m', W/2, 176);

  menuOption('1', 'Příběhové levely', LEVELS.length + ' ručně navržených levelů s cílem a životy', MENU.opt1Y, menuSel === 0);
  menuOption('2', 'Nekonečný běh', 'Obtížnost: ' + ENDLESS_DIFF[endlessDiff].name + '   ·   jeden život, hraj na vzdálenost', MENU.opt2Y, menuSel === 1);

  drawDiffPicker();
  drawSkinPicker();

  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '500 12px system-ui, sans-serif';
  ctx.fillText(
    isTouch ? 'ťukni na režim = start   ·   ťukni na obtížnost a barvu'
            : '↑↓ režim   ·   ←→ ' + (menuSel === 1 ? 'obtížnost' : 'barva') + '   ·   Enter = start',
    W/2, H - 16);
  ctx.restore();
}

function drawDiffPicker(){
  ctx.textAlign = 'center';
  ctx.fillStyle = '#8b93a7';
  ctx.font = '600 11px system-ui, sans-serif';
  ctx.fillText('OBTÍŽNOST NEKONEČNÉHO BĚHU', W/2, MENU.diffY - 10);

  DIFF_KEYS.forEach((k, i) => {
    const b = diffChipRect(i), sel = i === diffSel;
    ctx.fillStyle = sel ? 'rgba(92,200,255,.18)' : 'rgba(255,255,255,.05)';
    roundRect(b.x, b.y, b.w, b.h, 8); ctx.fill();
    ctx.strokeStyle = sel ? COL.player : 'rgba(255,255,255,.14)';
    ctx.lineWidth = 2; roundRect(b.x, b.y, b.w, b.h, 8); ctx.stroke();
    ctx.fillStyle = sel ? '#e8ecf5' : '#aab2c2';
    ctx.font = '700 14px system-ui, sans-serif';
    ctx.textBaseline = 'middle';
    ctx.fillText(ENDLESS_DIFF[k].name, b.x + b.w/2, b.y + b.h/2 + 1);
    ctx.textBaseline = 'alphabetic';
  });
}

function drawSkinPicker(){


<total_tokens>14932052 tokens left</total_tokens>


1345	  if (state === 'menu'){
1346	    // volba režimu (ťuknutí = start)
1347	    if (Math.abs(p.x - W/2) <= MENU.optW/2){
1348	      if (Math.abs(p.y - MENU.opt1Y) <= MENU.optH/2 + 4){ menuSel = 0; return startGame('levels'); }
1349	      if (Math.abs(p.y - MENU.opt2Y) <= MENU.optH/2 + 4){ menuSel = 1; return startGame('endless'); }
1350	    }
1351	    // volba obtížnosti
1352	    for (let i = 0; i < 3; i++){
1353	      const b = diffChipRect(i);
1354	      if (p.x >= b.x - 4 && p.x <= b.x + b.w + 4 && p.y >= b.y - 6 && p.y <= b.y + b.h + 6){
1355	        diffSel = i; endlessDiff = DIFF_KEYS[i]; menuSel = 1; S.coin();
1356	        return;
1357	      }
1358	    }
1359	    // volba barvy postavy
1360	    for (let i = 0; i < SKINS.length; i++){
1361	      const b = skinChipRect(i);
1362	      if (p.x >= b.x - 3 && p.x <= b.x + b.w + 3 && p.y >= b.y - 3 && p.y <= b.y + b.h + 3){
1363	        skinSel = i; menuSel = 0;
1364	        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
1365	        return;
1366	      }
1367	    }
1368	  } else if (state === 'shop'){
1369	    for (let i = 0; i < SHOP.length; i++){
1370	      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
1371	      if (p.x >= SHOP_X && p.x <= SHOP_X + SHOP_W && p.y >= y && p.y <= y + SHOP_ROW_H)
1372	        return buyShop(i);
1373	    }
1374	    const by = shopBtnY();
1375	    if (p.x >= W/2 - 120 && p.x <= W/2 + 120 && p.y >= by && p.y <= by + 44)
1376	      return shopContinue();
1377	  } else if (state === 'over' || state === 'win'){
1378	    state = 'menu';
1379	  } else if (state === 'paused'){
1380	    state = 'play';
1381	  }
1382	});
1383
1384	canvas.addEventListener('pointermove', e => {

  if (state === 'menu'){
    // volba režimu (ťuknutí = start)
    if (Math.abs(p.x - W/2) <= MENU.optW/2){
      if (Math.abs(p.y - MENU.opt1Y) <= MENU.optH/2 + 4){ menuSel = 0; return startGame('levels'); }
      if (Math.abs(p.y - MENU.opt2Y) <= MENU.optH/2 + 4){ menuSel = 1; return startGame('endless'); }
    }
    // volba obtížnosti
    for (let i = 0; i < 3; i++){
      const b = diffChipRect(i);
      if (p.x >= b.x - 4 && p.x <= b.x + b.w + 4 && p.y >= b.y - 6 && p.y <= b.y + b.h + 6){
        diffSel = i; endlessDiff = DIFF_KEYS[i]; menuSel = 1; S.coin();
        return;
      }
    }
    // volba barvy postavy
    for (let i = 0; i < SKINS.length; i++){
      const b = skinChipRect(i);
      if (p.x >= b.x - 3 && p.x <= b.x + b.w + 3 && p.y >= b.y - 3 && p.y <= b.y + b.h + 3){
        skinSel = i; menuSel = 0;
        if (skinUnlocked(i) && i !== equippedSkin){ equippedSkin = i; store('skin', i); S.coin(); }
        return;
      }
    }
  } else if (state === 'shop'){
    for (let i = 0; i < SHOP.length; i++){
      const y = SHOP_Y0 + i * (SHOP_ROW_H + SHOP_GAP);
      if (p.x >= SHOP_X && p.x <= SHOP_X + SHOP_W && p.y >= y && p.y <= y + SHOP_ROW_H)
        return buyShop(i);
    }
    const by = shopBtnY();
    if (p.x >= W/2 - 120 && p.x <= W/2 + 120 && p.y >= by && p.y <= by + 44)
      return shopContinue();
  } else if (state === 'over' || state === 'win'){
    state = 'menu';
  } else if (state === 'paused'){
    state = 'play';
  }
});

canvas.addEventListener('pointermove', e => {


<total_tokens>14930124 tokens left</total_tokens>


537	  }
538	  // nepřátelé
539	  for (const e of world.enemies){
540	    if (e.dead) continue;
541	    if (!rectOverlap(player.x, player.y, player.w, player.h, e.x, e.y, e.w, e.h)) continue;
542	    // dopad shora = zničíš nepřítele (i rohem). Zabije tě jen zásah z boku / zespodu.
543	    const feet = player.y + player.h;
544	    const fromAbove = player.vy > 0 &&
545	      ((player.prevBottom ?? feet) <= e.y + 8 || feet - e.y < e.h * 0.5 + 6);
546	    const stomp = fromAbove;
547	    if (stomp){
548	      const td = ETIER[e.tier || 0];
549	      e.hp--;
550	      if (e.hp > 0){
551	        // velká příšerka – první zášlap ji jen zmenší, druhý zabije
552	        const nw = 32, nh = 30;
553	        e.y += e.h - nh; e.x += (e.w - nw) / 2;
554	        e.w = nw; e.h = nh; e.flash = 10;
555	        player.vy = -9;
556	        score += 60; coinsCollected += 2;
557	        shake = 4;
558	        spawnCoinPops(e.x + e.w / 2, e.y, 2);
559	        beep(150, 0.1, 'sawtooth', 0.05);
560	      } else {
561	        e.dead = true; e.t = 0;
562	        player.vy = -10 - (e.tier || 0) * 1.5;        // z větší příšery větší odraz
563	        score += td.score;
564	        coinsCollected += td.coins;                   // odměna podle velikosti

  }
  // nepřátelé
  for (const e of world.enemies){
    if (e.dead) continue;
    if (!rectOverlap(player.x, player.y, player.w, player.h, e.x, e.y, e.w, e.h)) continue;
    // dopad shora = zničíš nepřítele (i rohem). Zabije tě jen zásah z boku / zespodu.
    const feet = player.y + player.h;
    const fromAbove = player.vy > 0 &&
      ((player.prevBottom ?? feet) <= e.y + 8 || feet - e.y < e.h * 0.5 + 6);
    const stomp = fromAbove;
    if (stomp){
      const td = ETIER[e.tier || 0];
      e.hp--;
      if (e.hp > 0){
        // velká příšerka – první zášlap ji jen zmenší, druhý zabije
        const nw = 32, nh = 30;
        e.y += e.h - nh; e.x += (e.w - nw) / 2;
        e.w = nw; e.h = nh; e.flash = 10;
        player.vy = -9;
        score += 60; coinsCollected += 2;
        shake = 4;
        spawnCoinPops(e.x + e.w / 2, e.y, 2);
        beep(150, 0.1, 'sawtooth', 0.05);
      } else {
        e.dead = true; e.t = 0;
        player.vy = -10 - (e.tier || 0) * 1.5;        // z větší příšery větší odraz
        score += td.score;
        coinsCollected += td.coins;                   // odměna podle velikosti


<total_tokens>14928899 tokens left</total_tokens>


python3 - <<'EOF'
import re
js=re.search(r'<script>(.*)</script>',open('/home/tonda/hra.html').read(),re.S).group(1)
bad=False
for a,b in [('(',')'),('{','}'),('[',']')]:
    if js.count(a)!=js.count(b): bad=True; print("MISMATCH",a,js.count(a),js.count(b))
print("brackets", "BAD" if bad else "OK")
for name in ['ENDLESS_DIFF','DIFF_KEYS','endlessDiff','diffSel','bestDist','diffChipRect','skinChipRect','drawDiffPicker','MENU']:
    print(name, js.count(name))
EOF
curl -sf -o /dev/null -w "server HTTP %{http_code}\n" http://localhost:8000/hra.html
ls -la /home/tonda/hra.html

Final verification


brackets OK
ENDLESS_DIFF 10
DIFF_KEYS 5
endlessDiff 8
diffSel 8
bestDist 7
diffChipRect 3
skinChipRect 3
drawDiffPicker 2
MENU 27
server HTTP 200
-rw-rw-r-- 1 tonda tonda 54085 Aug 27 16:52 /home/tonda/hra.html


<total_tokens>14927399 tokens left</total_tokens>


Hotovo — **F5**. Server běží. Nemám tu prohlížeč, takže jsem kód pečlivě prošel; kdyby byla stránka prázdná, otevři konzoli (F12) a pošli mi chybu.

## 1. Velcí nepřátelé na dvě zašlápnutí
- Velká příšerka má `hp = 2`. První zášlap ji **zmenší** (blikne bíle, odmění 2 mince / 60 bodů, odrazí tě). Druhý zášlap ji zabije a dá plnou odměnu (6 mincí / 180 bodů).
- Malé a střední dál padnou na jednu ránu.

## 2. Tři obtížnosti nekonečného běhu
Vybíráš v menu (dotykem na chip, nebo šipky ←→ když je vybraný řádek „Nekonečný běh"):

| | Mezery | Nepřátelé | Zeď smrti |
|---|---|---|---|
| **Lehká** | málo, úzké | málo, skoro jen malí | pomalá |
| **Střední** (výchozí) | jako dosud | jako dosud | jako dosud |
| **Těžká** | hodně, ale všechny přeskočitelné (širší dostanou plošinku) | hodně, velcí a rychlí | rychlá |

Výchozí zůstává **Střední** = přesně současný stav.

## 3. Barvy podle obtížnosti + odstíny podle vzdálenosti
- Každá obtížnost má rodinu barev: **Lehká = zelené**, **Střední = modré**, **Těžká = červené**.
- 3 odstíny na obtížnost, odemykají se podle nejdelšího běhu v té obtížnosti: **100 m → I**, **300 m → II**, **600 m → III**.
- V menu je řádek „Nejdál" s rekordní vzdáleností pro každou obtížnost, po smrti se ukáže „odemčeno: …".

**Pozn.:** systém odemykání barev se změnil (dřív podle skóre, teď podle vzdálenosti per obtížnost), takže dřívější odemčené barvy se počítají znovu.


You're building a 2D platformer game at /home/tonda/hra.html, served locally so you can test on your phone. Latest changes: two-hit big enemies, three endless-mode difficulties with per-difficulty color unlocks by distance. Next: refresh the page and test those. (disable recaps in /config)