feat: minecraft-style start menu with proper spacing
This commit is contained in:
parent
f14b61d7d9
commit
547ad86989
330
game.js
330
game.js
|
|
@ -106,87 +106,143 @@ function customConfirm(msg, onYes) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Minecraft-style CSS =====
|
||||||
|
const mcCSS = `
|
||||||
|
.mc-overlay { position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999;
|
||||||
|
display:flex;align-items:center;justify-content:center;font-family:'Courier New',monospace;
|
||||||
|
image-rendering:pixelated; }
|
||||||
|
.mc-bg { position:absolute;inset:0;background:#000;opacity:0.75; }
|
||||||
|
.mc-dirt { position:absolute;bottom:0;left:0;right:0;height:40%;
|
||||||
|
background:repeating-linear-gradient(0deg,#8B6914 0px,#8B6914 2px,#7A5B12 2px,#7A5B12 4px);opacity:0.3; }
|
||||||
|
.mc-box { position:relative;z-index:1;background:#C6C6C6;border:4px solid #000;
|
||||||
|
padding:0;max-width:420px;width:92%;box-shadow:4px 4px 0 #000; }
|
||||||
|
.mc-titlebar { background:#1a1a2e;padding:12px 16px;text-align:center;
|
||||||
|
border-bottom:4px solid #000; }
|
||||||
|
.mc-title { font-size:24px;font-weight:bold;color:#FFAA00;text-shadow:2px 2px 0 #000;
|
||||||
|
letter-spacing:2px; }
|
||||||
|
.mc-subtitle { font-size:13px;color:#AAA;margin-top:4px; }
|
||||||
|
.mc-body { padding:16px;background:#C6C6C6; }
|
||||||
|
.mc-btn { display:block;width:100%;padding:10px 16px;margin-bottom:8px;
|
||||||
|
font-family:'Courier New',monospace;font-size:16px;font-weight:bold;
|
||||||
|
cursor:pointer;text-align:center;border:3px solid #000;
|
||||||
|
image-rendering:pixelated;box-shadow:3px 3px 0 #000;
|
||||||
|
transition:transform 0.05s,box-shadow 0.05s; }
|
||||||
|
.mc-btn:active { transform:translate(2px,2px);box-shadow:1px 1px 0 #000; }
|
||||||
|
.mc-btn-green { background:#5A8F3C;color:#E0E0E0;border-color:#3A5F1C;text-shadow:1px 1px 0 #222; }
|
||||||
|
.mc-btn-green:hover { background:#6DAF4E; }
|
||||||
|
.mc-btn-blue { background:#3C5F9E;color:#E0E0E0;border-color:#2A4070;text-shadow:1px 1px 0 #222; }
|
||||||
|
.mc-btn-blue:hover { background:#4B73BE; }
|
||||||
|
.mc-btn-purple { background:#7B3FA0;color:#E0E0E0;border-color:#5A2C78;text-shadow:1px 1px 0 #222; }
|
||||||
|
.mc-btn-purple:hover { background:#9555BE; }
|
||||||
|
.mc-btn-orange { background:#B86E00;color:#E0E0E0;border-color:#8A5200;text-shadow:1px 1px 0 #222; }
|
||||||
|
.mc-btn-orange:hover { background:#DA8500; }
|
||||||
|
.mc-btn-red { background:#9E2B2B;color:#E0E0E0;border-color:#6E1B1B;text-shadow:1px 1px 0 #222; }
|
||||||
|
.mc-btn-red:hover { background:#BE3B3B; }
|
||||||
|
.mc-btn-gray { background:#555;color:#AAA;border-color:#333;text-shadow:1px 1px 0 #111; }
|
||||||
|
.mc-btn-gray:hover { background:#666;color:#DDD; }
|
||||||
|
.mc-btn-outline { background:transparent;color:#555;border-color:#555;
|
||||||
|
text-shadow:none;box-shadow:2px 2px 0 #333; }
|
||||||
|
.mc-btn-outline:hover { background:#DDD;color:#222; }
|
||||||
|
.mc-input { width:100%;padding:8px 12px;font-family:'Courier New',monospace;
|
||||||
|
font-size:16px;background:#FFF;border:3px solid #000;color:#222;
|
||||||
|
box-shadow:inset 2px 2px 0 #999;margin:4px 0; }
|
||||||
|
.mc-input:focus { outline:none;border-color:#FFAA00; }
|
||||||
|
.mc-label { font-size:13px;color:#444;font-weight:bold;margin:8px 0 4px; }
|
||||||
|
.mc-divider { border:none;border-top:2px solid #999;margin:12px 0; }
|
||||||
|
.mc-friend-row { display:flex;justify-content:space-between;align-items:center;
|
||||||
|
padding:6px 8px;margin-bottom:4px;background:#E8E8E8;border:2px solid #999;
|
||||||
|
box-shadow:2px 2px 0 #999; }
|
||||||
|
.mc-friend-btn { padding:4px 10px;font-family:'Courier New',monospace;
|
||||||
|
font-size:13px;font-weight:bold;cursor:pointer;border:2px solid #000;
|
||||||
|
box-shadow:1px 1px 0 #000; }
|
||||||
|
.mc-friend-btn:active { transform:translate(1px,1px);box-shadow:none; }
|
||||||
|
.mc-friend-btn-green { background:#5A8F3C;color:#E0E0E0; }
|
||||||
|
.mc-friend-btn-red { background:#9E2B2B;color:#E0E0E0; }
|
||||||
|
.mc-checkbox-row { display:flex;align-items:center;gap:8px;font-size:13px;color:#444;cursor:pointer; }
|
||||||
|
.mc-checkbox { width:18px;height:18px;accent-color:#5A8F3C; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const styleEl = document.createElement('style');
|
||||||
|
styleEl.textContent = mcCSS;
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'mc-overlay';
|
||||||
overlay.id = 'startMenu';
|
overlay.id = 'startMenu';
|
||||||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);display:flex;align-items:center;justify-content:center;z-index:99999;font-family:Arial,sans-serif;';
|
overlay.innerHTML = '<div class="mc-bg"></div><div class="mc-dirt"></div>';
|
||||||
|
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.style.cssText = 'background:#1a1a2e;border:2px solid #e94560;border-radius:16px;padding:32px;max-width:400px;width:90%;text-align:center;color:#eee;';
|
box.className = 'mc-box';
|
||||||
|
|
||||||
// Title
|
// Title bar
|
||||||
const title = document.createElement('div');
|
const titleBar = document.createElement('div');
|
||||||
title.style.cssText = 'font-size:28px;font-weight:bold;margin-bottom:8px;color:#e94560;';
|
titleBar.className = 'mc-titlebar';
|
||||||
title.textContent = '🏗️ GrechkaCraft';
|
titleBar.innerHTML = '<div class="mc-title">⛏️ GrechkaCraft</div>' +
|
||||||
box.appendChild(title);
|
'<div class="mc-subtitle">' + (isTgWebApp ? ('Привет, ' + (tgUser?.first_name || 'Игрок') + '!') : 'Voxel-мир в браузере') + '</div>';
|
||||||
|
box.appendChild(titleBar);
|
||||||
|
|
||||||
const subtitle = document.createElement('div');
|
const body = document.createElement('div');
|
||||||
subtitle.style.cssText = 'font-size:14px;color:#888;margin-bottom:24px;';
|
body.className = 'mc-body';
|
||||||
subtitle.textContent = isTgWebApp ? ('Привет, ' + (tgUser?.first_name || 'Игрок') + '!') : 'Voxel-мир в браузере';
|
|
||||||
box.appendChild(subtitle);
|
|
||||||
|
|
||||||
// New World button
|
// === Buttons ===
|
||||||
|
// New World
|
||||||
const btnNew = document.createElement('button');
|
const btnNew = document.createElement('button');
|
||||||
btnNew.style.cssText = 'width:100%;padding:14px;margin-bottom:12px;background:#27ae60;color:#fff;border:none;border-radius:10px;font-size:18px;cursor:pointer;font-weight:bold;';
|
btnNew.className = 'mc-btn mc-btn-green';
|
||||||
btnNew.textContent = '🌍 Новый мир';
|
btnNew.textContent = '🌍 Новый мир';
|
||||||
btnNew.onmouseover = () => btnNew.style.background = '#2ecc71';
|
|
||||||
btnNew.onmouseout = () => btnNew.style.background = '#27ae60';
|
|
||||||
btnNew.onclick = () => {
|
btnNew.onclick = () => {
|
||||||
worldId = generateShortWorldCode();
|
worldId = generateShortWorldCode();
|
||||||
overlay.remove();
|
overlay.remove();
|
||||||
|
styleEl.remove();
|
||||||
resolve(worldId);
|
resolve(worldId);
|
||||||
};
|
};
|
||||||
box.appendChild(btnNew);
|
body.appendChild(btnNew);
|
||||||
|
|
||||||
// Join World button
|
// Join by code
|
||||||
const btnJoin = document.createElement('button');
|
const btnJoin = document.createElement('button');
|
||||||
btnJoin.style.cssText = 'width:100%;padding:14px;margin-bottom:12px;background:#2980b9;color:#fff;border:none;border-radius:10px;font-size:18px;cursor:pointer;font-weight:bold;';
|
btnJoin.className = 'mc-btn mc-btn-blue';
|
||||||
btnJoin.textContent = '🔗 Вступить по коду';
|
btnJoin.textContent = '🔗 Вступить по коду';
|
||||||
btnJoin.onmouseover = () => btnJoin.style.background = '#3498db';
|
|
||||||
btnJoin.onmouseout = () => btnJoin.style.background = '#2980b9';
|
|
||||||
btnJoin.onclick = () => {
|
btnJoin.onclick = () => {
|
||||||
|
body.innerHTML = '';
|
||||||
|
const lbl = document.createElement('div');
|
||||||
|
lbl.className = 'mc-label';
|
||||||
|
lbl.textContent = 'Код мира:';
|
||||||
|
body.appendChild(lbl);
|
||||||
const codeInput = document.createElement('input');
|
const codeInput = document.createElement('input');
|
||||||
|
codeInput.className = 'mc-input';
|
||||||
codeInput.type = 'text';
|
codeInput.type = 'text';
|
||||||
codeInput.placeholder = 'Введите код мира (например: bkh237)';
|
codeInput.placeholder = 'bkh237';
|
||||||
codeInput.style.cssText = 'width:90%;padding:12px;margin:12px 0;border:2px solid #3498db;border-radius:8px;background:#16213e;color:#fff;font-size:16px;text-align:center;text-transform:lowercase;';
|
|
||||||
codeInput.maxLength = 12;
|
codeInput.maxLength = 12;
|
||||||
box.innerHTML = '';
|
body.appendChild(codeInput);
|
||||||
const backTitle = document.createElement('div');
|
|
||||||
backTitle.style.cssText = 'font-size:20px;font-weight:bold;margin-bottom:16px;color:#3498db;';
|
|
||||||
backTitle.textContent = '🔗 Вступить в мир';
|
|
||||||
box.appendChild(backTitle);
|
|
||||||
box.appendChild(codeInput);
|
|
||||||
const btnGo = document.createElement('button');
|
const btnGo = document.createElement('button');
|
||||||
btnGo.style.cssText = 'width:100%;padding:14px;margin-top:12px;background:#2980b9;color:#fff;border:none;border-radius:10px;font-size:18px;cursor:pointer;font-weight:bold;';
|
btnGo.className = 'mc-btn mc-btn-blue';
|
||||||
btnGo.textContent = 'Войти';
|
btnGo.textContent = 'Войти →';
|
||||||
btnGo.onclick = () => {
|
btnGo.onclick = () => {
|
||||||
const code = codeInput.value.trim().toLowerCase();
|
const code = codeInput.value.trim().toLowerCase();
|
||||||
if (code.length >= 3) {
|
if (code.length >= 3) {
|
||||||
worldId = code;
|
worldId = code;
|
||||||
overlay.remove();
|
overlay.remove();
|
||||||
|
styleEl.remove();
|
||||||
resolve(worldId);
|
resolve(worldId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
box.appendChild(btnGo);
|
body.appendChild(btnGo);
|
||||||
const btnBack = document.createElement('button');
|
const btnBack = document.createElement('button');
|
||||||
btnBack.style.cssText = 'width:100%;padding:10px;margin-top:8px;background:transparent;color:#888;border:1px solid #444;border-radius:8px;cursor:pointer;font-size:14px;';
|
btnBack.className = 'mc-btn mc-btn-gray';
|
||||||
btnBack.textContent = '← Назад';
|
btnBack.textContent = '← Назад';
|
||||||
btnBack.onclick = () => { overlay.remove(); showStartMenu().then(resolve); };
|
btnBack.onclick = () => { overlay.remove(); styleEl.remove(); showStartMenu().then(resolve); };
|
||||||
box.appendChild(btnBack);
|
body.appendChild(btnBack);
|
||||||
codeInput.focus();
|
codeInput.focus();
|
||||||
codeInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') btnGo.click(); });
|
codeInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') btnGo.click(); });
|
||||||
};
|
};
|
||||||
box.appendChild(btnJoin);
|
body.appendChild(btnJoin);
|
||||||
|
|
||||||
// History / Continue button (only if TG user with saved game OR localStorage save)
|
// Continue saved game
|
||||||
const hasLocalSave = !!localStorage.getItem('minegrechka_save');
|
const hasLocalSave = !!localStorage.getItem('minegrechka_save');
|
||||||
if (isTgWebApp || hasLocalSave) {
|
if (isTgWebApp || hasLocalSave) {
|
||||||
const btnHistory = document.createElement('button');
|
const btnHistory = document.createElement('button');
|
||||||
btnHistory.style.cssText = 'width:100%;padding:14px;margin-bottom:12px;background:#8e44ad;color:#fff;border:none;border-radius:10px;font-size:18px;cursor:pointer;font-weight:bold;';
|
btnHistory.className = 'mc-btn mc-btn-purple';
|
||||||
btnHistory.textContent = '💾 Продолжить игру';
|
btnHistory.textContent = '💾 Продолжить игру';
|
||||||
btnHistory.onmouseover = () => btnHistory.style.background = '#9b59b6';
|
|
||||||
btnHistory.onmouseout = () => btnHistory.style.background = '#8e44ad';
|
|
||||||
btnHistory.onclick = async () => {
|
btnHistory.onclick = async () => {
|
||||||
// TG user: load save from server
|
|
||||||
if (isTgWebApp && tgUser) {
|
if (isTgWebApp && tgUser) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(SERVER_URL + '/api/tg/save?tg_id=' + tgUser.id);
|
const resp = await fetch(SERVER_URL + '/api/tg/save?tg_id=' + tgUser.id);
|
||||||
|
|
@ -195,75 +251,89 @@ function customConfirm(msg, onYes) {
|
||||||
worldId = data.world_id;
|
worldId = data.world_id;
|
||||||
tgSaveLoaded = true;
|
tgSaveLoaded = true;
|
||||||
overlay.remove();
|
overlay.remove();
|
||||||
|
styleEl.remove();
|
||||||
resolve(worldId);
|
resolve(worldId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch(e) { console.warn('[TG] Load save failed:', e); }
|
} catch(e) { console.warn('[TG] Load save failed:', e); }
|
||||||
}
|
}
|
||||||
// Fallback: load from latest localStorage save
|
|
||||||
if (hasLocalSave) {
|
if (hasLocalSave) {
|
||||||
// Generate world from save (worldSeed based)
|
|
||||||
worldId = generateShortWorldCode();
|
worldId = generateShortWorldCode();
|
||||||
overlay.remove();
|
overlay.remove();
|
||||||
|
styleEl.remove();
|
||||||
resolve(worldId);
|
resolve(worldId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
customAlert('Сохранений не найдено');
|
customAlert('Сохранений не найдено');
|
||||||
};
|
};
|
||||||
box.appendChild(btnHistory);
|
body.appendChild(btnHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TG Share button (only in TG context)
|
// === TG-only buttons ===
|
||||||
if (isTgWebApp && window.Telegram && Telegram.WebApp.switchInlineQuery) {
|
if (isTgWebApp && window.Telegram && Telegram.WebApp.switchInlineQuery) {
|
||||||
|
// Invite friends — with margin
|
||||||
const btnInvite = document.createElement('button');
|
const btnInvite = document.createElement('button');
|
||||||
btnInvite.style.cssText = 'width:100%;padding:10px;margin-top:8px;background:transparent;color:#3498db;border:1px solid #3498db;border-radius:8px;cursor:pointer;font-size:14px;';
|
btnInvite.className = 'mc-btn mc-btn-outline';
|
||||||
|
btnInvite.style.marginTop = '16px';
|
||||||
btnInvite.textContent = '📤 Пригласить друзей';
|
btnInvite.textContent = '📤 Пригласить друзей';
|
||||||
btnInvite.onclick = () => {
|
btnInvite.onclick = () => {
|
||||||
const code = worldId || generateShortWorldCode();
|
const code = worldId || generateShortWorldCode();
|
||||||
// Share: open TG share dialog with game link
|
|
||||||
const url = 'https://t.me/Grechkacraft_bot/app?startapp=' + encodeURIComponent(code);
|
const url = 'https://t.me/Grechkacraft_bot/app?startapp=' + encodeURIComponent(code);
|
||||||
if (Telegram.WebApp && Telegram.WebApp.openTelegramLink) {
|
if (Telegram.WebApp && Telegram.WebApp.openTelegramLink) {
|
||||||
Telegram.WebApp.openTelegramLink('https://t.me/share/url?url=' + encodeURIComponent(url) + '&text=' + encodeURIComponent('🏗️ Заходи в GrechkaCraft! Мир: ' + code));
|
Telegram.WebApp.openTelegramLink('https://t.me/share/url?url=' + encodeURIComponent(url) + '&text=' + encodeURIComponent('🏗️ Заходи в GrechkaCraft! Мир: ' + code));
|
||||||
} else {
|
} else {
|
||||||
// Fallback: copy invite link
|
|
||||||
navigator.clipboard.writeText(url).then(() => showToast('📋 Скопировано!')).catch(() => showToast(url));
|
navigator.clipboard.writeText(url).then(() => showToast('📋 Скопировано!')).catch(() => showToast(url));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
box.appendChild(btnInvite);
|
body.appendChild(btnInvite);
|
||||||
|
|
||||||
// Friends button (TG only)
|
// Friends — with margin
|
||||||
const btnFriends = document.createElement('button');
|
const btnFriends = document.createElement('button');
|
||||||
btnFriends.style.cssText = 'width:100%;padding:14px;margin-bottom:12px;background:#f39c12;color:#fff;border:none;border-radius:10px;font-size:18px;cursor:pointer;font-weight:bold;';
|
btnFriends.className = 'mc-btn mc-btn-orange';
|
||||||
|
btnFriends.style.marginTop = '8px';
|
||||||
btnFriends.textContent = '👥 Друзья';
|
btnFriends.textContent = '👥 Друзья';
|
||||||
btnFriends.onmouseover = () => btnFriends.style.background = '#e67e22';
|
|
||||||
btnFriends.onmouseout = () => btnFriends.style.background = '#f39c12';
|
|
||||||
btnFriends.onclick = async () => {
|
btnFriends.onclick = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(SERVER_URL + '/api/tg/friends?tg_id=' + tgUser.id);
|
const resp = await fetch(SERVER_URL + '/api/tg/friends?tg_id=' + tgUser.id);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
box.innerHTML = '';
|
body.innerHTML = '';
|
||||||
const ft = document.createElement('div');
|
|
||||||
ft.style.cssText = 'font-size:20px;font-weight:bold;margin-bottom:8px;color:#f39c12;';
|
// Back button at top
|
||||||
ft.textContent = '👥 Друзья';
|
const btnBackTop = document.createElement('button');
|
||||||
box.appendChild(ft);
|
btnBackTop.className = 'mc-btn mc-btn-gray';
|
||||||
// Show nickname
|
btnBackTop.textContent = '← Назад';
|
||||||
|
btnBackTop.style.marginBottom = '12px';
|
||||||
|
btnBackTop.onclick = () => { overlay.remove(); styleEl.remove(); showStartMenu().then(resolve); };
|
||||||
|
body.appendChild(btnBackTop);
|
||||||
|
|
||||||
|
// Nickname section
|
||||||
if (data.nickname) {
|
if (data.nickname) {
|
||||||
const nickDiv = document.createElement('div');
|
const nickDiv = document.createElement('div');
|
||||||
nickDiv.style.cssText = 'color:#2ecc71;font-size:14px;margin-bottom:12px;';
|
nickDiv.className = 'mc-friend-row';
|
||||||
nickDiv.textContent = '🔗 Твой ник: ' + data.nickname;
|
nickDiv.innerHTML = '🔗 Твой ник: <b>' + data.nickname + '</b>';
|
||||||
box.appendChild(nickDiv);
|
nickDiv.style.background = '#D5E8C5';
|
||||||
|
body.appendChild(nickDiv);
|
||||||
} else {
|
} else {
|
||||||
// No nickname yet — set one
|
const nickLabel = document.createElement('div');
|
||||||
const nickDiv = document.createElement('div');
|
nickLabel.className = 'mc-label';
|
||||||
nickDiv.style.cssText = 'margin-bottom:12px;';
|
nickLabel.style.color = '#9E2B2B';
|
||||||
nickDiv.innerHTML = '<div style="color:#e74c3c;font-size:14px;margin-bottom:8px;">Задай ник, чтобы друзья могли тебя найти:</div>';
|
nickLabel.textContent = '⚡ Задай ник, чтобы друзья могли тебя найти:';
|
||||||
|
body.appendChild(nickLabel);
|
||||||
|
const nickRow = document.createElement('div');
|
||||||
|
nickRow.style.display = 'flex';
|
||||||
|
nickRow.style.gap = '4px';
|
||||||
|
nickRow.style.marginBottom = '8px';
|
||||||
const nickInput = document.createElement('input');
|
const nickInput = document.createElement('input');
|
||||||
|
nickInput.className = 'mc-input';
|
||||||
|
nickInput.style.flex = '1';
|
||||||
nickInput.type = 'text';
|
nickInput.type = 'text';
|
||||||
nickInput.maxLength = 16;
|
nickInput.maxLength = 16;
|
||||||
nickInput.placeholder = 'Твой ник';
|
nickInput.placeholder = 'Твой ник';
|
||||||
nickInput.style.cssText = 'width:65%;padding:10px;border:2px solid #f39c12;border-radius:8px;background:#16213e;color:#fff;font-size:16px;';
|
|
||||||
const nickBtn = document.createElement('button');
|
const nickBtn = document.createElement('button');
|
||||||
nickBtn.style.cssText = 'width:28%;padding:10px;background:#f39c12;color:#fff;border:none;border-radius:8px;font-size:16px;cursor:pointer;margin-left:4px;';
|
nickBtn.className = 'mc-btn mc-btn-green';
|
||||||
|
nickBtn.style.width = 'auto';
|
||||||
|
nickBtn.style.marginBottom = '0';
|
||||||
|
nickBtn.style.padding = '8px 16px';
|
||||||
nickBtn.textContent = '✓';
|
nickBtn.textContent = '✓';
|
||||||
nickBtn.onclick = async () => {
|
nickBtn.onclick = async () => {
|
||||||
const n = nickInput.value.trim().toLowerCase();
|
const n = nickInput.value.trim().toLowerCase();
|
||||||
|
|
@ -275,50 +345,61 @@ function customConfirm(msg, onYes) {
|
||||||
const sd = await set.json();
|
const sd = await set.json();
|
||||||
if (sd.ok) { showToast('✅ Ник: ' + sd.nickname); btnFriends.onclick(); } else { showToast('❌ Ошибка'); }
|
if (sd.ok) { showToast('✅ Ник: ' + sd.nickname); btnFriends.onclick(); } else { showToast('❌ Ошибка'); }
|
||||||
};
|
};
|
||||||
nickDiv.appendChild(nickInput);
|
nickRow.appendChild(nickInput);
|
||||||
nickDiv.appendChild(nickBtn);
|
nickRow.appendChild(nickBtn);
|
||||||
box.appendChild(nickDiv);
|
body.appendChild(nickRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Friends list
|
// Friends list
|
||||||
if (data.friends && data.friends.length) {
|
if (data.friends && data.friends.length) {
|
||||||
const list = document.createElement('div');
|
const listLabel = document.createElement('div');
|
||||||
list.style.cssText = 'margin-bottom:12px;';
|
listLabel.className = 'mc-label';
|
||||||
|
listLabel.textContent = '👥 Друзья:';
|
||||||
|
body.appendChild(listLabel);
|
||||||
for (const f of data.friends) {
|
for (const f of data.friends) {
|
||||||
const fDiv = document.createElement('div');
|
const fDiv = document.createElement('div');
|
||||||
fDiv.style.cssText = 'padding:6px 0;border-bottom:1px solid #333;display:flex;justify-content:space-between;align-items:center;';
|
fDiv.className = 'mc-friend-row';
|
||||||
const statusDot = f.online ? '🟢' : '⚫';
|
const statusDot = f.online ? '🟢' : '⚫';
|
||||||
const info = statusDot + ' <b>' + f.nickname + '</b>' + (f.world_id ? ' — мир ' + f.world_id : '');
|
let info = statusDot + ' <b>' + f.nickname + '</b>';
|
||||||
fDiv.innerHTML = '<div>' + info + '</div>';
|
if (f.online && f.world_id) info += ' — мир ' + f.world_id;
|
||||||
|
const fInfo = document.createElement('div');
|
||||||
|
fInfo.innerHTML = info;
|
||||||
|
fDiv.appendChild(fInfo);
|
||||||
if (f.online && f.world_id) {
|
if (f.online && f.world_id) {
|
||||||
const joinBtn = document.createElement('button');
|
const joinBtn = document.createElement('button');
|
||||||
joinBtn.style.cssText = 'padding:4px 10px;background:#27ae60;color:#fff;border:none;border-radius:6px;font-size:12px;cursor:pointer;';
|
joinBtn.className = 'mc-friend-btn mc-friend-btn-green';
|
||||||
joinBtn.textContent = '🎮 Вступить';
|
joinBtn.textContent = '🎮 Вступить';
|
||||||
joinBtn.onclick = () => { worldId = f.world_id; overlay.remove(); resolve(worldId); };
|
joinBtn.onclick = () => { worldId = f.world_id; overlay.remove(); styleEl.remove(); resolve(worldId); };
|
||||||
fDiv.appendChild(joinBtn);
|
fDiv.appendChild(joinBtn);
|
||||||
}
|
}
|
||||||
list.appendChild(fDiv);
|
body.appendChild(fDiv);
|
||||||
}
|
}
|
||||||
box.appendChild(list);
|
|
||||||
} else if (data.friends && !data.friends.length) {
|
} else if (data.friends && !data.friends.length) {
|
||||||
const noFriends = document.createElement('div');
|
const noFriends = document.createElement('div');
|
||||||
noFriends.style.cssText = 'color:#888;font-size:14px;margin-bottom:12px;';
|
noFriends.className = 'mc-label';
|
||||||
|
noFriends.style.color = '#888';
|
||||||
noFriends.textContent = 'Пока нет друзей';
|
noFriends.textContent = 'Пока нет друзей';
|
||||||
box.appendChild(noFriends);
|
body.appendChild(noFriends);
|
||||||
}
|
}
|
||||||
// Pending friend requests
|
|
||||||
|
// Pending requests
|
||||||
if (data.pending && data.pending.length) {
|
if (data.pending && data.pending.length) {
|
||||||
const pDiv = document.createElement('div');
|
const pLabel = document.createElement('div');
|
||||||
pDiv.style.cssText = 'margin-bottom:12px;';
|
pLabel.className = 'mc-label';
|
||||||
pDiv.innerHTML = '<div style="color:#f39c12;font-weight:bold;margin-bottom:6px;">📨 Запросы в друзья:</div>';
|
pLabel.style.color = '#B86E00';
|
||||||
|
pLabel.textContent = '📨 Запросы в друзья:';
|
||||||
|
body.appendChild(pLabel);
|
||||||
for (const p of data.pending) {
|
for (const p of data.pending) {
|
||||||
const pItem = document.createElement('div');
|
const pItem = document.createElement('div');
|
||||||
pItem.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:4px 0;border-bottom:1px solid #222;';
|
pItem.className = 'mc-friend-row';
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = p.from;
|
span.textContent = p.from;
|
||||||
pItem.appendChild(span);
|
pItem.appendChild(span);
|
||||||
const btns = document.createElement('div');
|
const btns = document.createElement('div');
|
||||||
|
btns.style.display = 'flex';
|
||||||
|
btns.style.gap = '4px';
|
||||||
const acceptBtn = document.createElement('button');
|
const acceptBtn = document.createElement('button');
|
||||||
acceptBtn.style.cssText = 'padding:4px 8px;background:#27ae60;color:#fff;border:none;border-radius:4px;font-size:12px;cursor:pointer;margin-right:4px;';
|
acceptBtn.className = 'mc-friend-btn mc-friend-btn-green';
|
||||||
acceptBtn.textContent = '✓';
|
acceptBtn.textContent = '✓';
|
||||||
acceptBtn.onclick = async () => {
|
acceptBtn.onclick = async () => {
|
||||||
const r = await fetch(SERVER_URL + '/api/friends/accept', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,request_id:p.id})});
|
const r = await fetch(SERVER_URL + '/api/friends/accept', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,request_id:p.id})});
|
||||||
|
|
@ -326,7 +407,7 @@ function customConfirm(msg, onYes) {
|
||||||
if (d.ok) { showToast('✅ Друг добавлен!'); btnFriends.onclick(); } else { showToast('❌ Ошибка'); }
|
if (d.ok) { showToast('✅ Друг добавлен!'); btnFriends.onclick(); } else { showToast('❌ Ошибка'); }
|
||||||
};
|
};
|
||||||
const declineBtn = document.createElement('button');
|
const declineBtn = document.createElement('button');
|
||||||
declineBtn.style.cssText = 'padding:4px 8px;background:#e74c3c;color:#fff;border:none;border-radius:4px;font-size:12px;cursor:pointer;';
|
declineBtn.className = 'mc-friend-btn mc-friend-btn-red';
|
||||||
declineBtn.textContent = '✕';
|
declineBtn.textContent = '✕';
|
||||||
declineBtn.onclick = async () => {
|
declineBtn.onclick = async () => {
|
||||||
await fetch(SERVER_URL + '/api/friends/decline', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,request_id:p.id})});
|
await fetch(SERVER_URL + '/api/friends/decline', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,request_id:p.id})});
|
||||||
|
|
@ -336,21 +417,31 @@ function customConfirm(msg, onYes) {
|
||||||
btns.appendChild(acceptBtn);
|
btns.appendChild(acceptBtn);
|
||||||
btns.appendChild(declineBtn);
|
btns.appendChild(declineBtn);
|
||||||
pItem.appendChild(btns);
|
pItem.appendChild(btns);
|
||||||
pDiv.appendChild(pItem);
|
body.appendChild(pItem);
|
||||||
}
|
}
|
||||||
box.appendChild(pDiv);
|
|
||||||
}
|
}
|
||||||
// Add friend by nickname
|
|
||||||
const addDiv = document.createElement('div');
|
// Add friend
|
||||||
addDiv.style.cssText = 'margin-top:12px;';
|
body.appendChild(document.createElement('hr'));
|
||||||
addDiv.innerHTML = '<div style="color:#888;font-size:14px;margin-bottom:8px;">Добавить друга по нику:</div>';
|
body.className = 'mc-label';
|
||||||
|
const addLabel = document.createElement('div');
|
||||||
|
addLabel.className = 'mc-label';
|
||||||
|
addLabel.textContent = '➕ Добавить друга по нику:';
|
||||||
|
body.appendChild(addLabel);
|
||||||
|
const addRow = document.createElement('div');
|
||||||
|
addRow.style.display = 'flex';
|
||||||
|
addRow.style.gap = '4px';
|
||||||
const addInput = document.createElement('input');
|
const addInput = document.createElement('input');
|
||||||
|
addInput.className = 'mc-input';
|
||||||
|
addInput.style.flex = '1';
|
||||||
addInput.type = 'text';
|
addInput.type = 'text';
|
||||||
addInput.maxLength = 16;
|
addInput.maxLength = 16;
|
||||||
addInput.placeholder = 'Ник друга';
|
addInput.placeholder = 'Ник друга';
|
||||||
addInput.style.cssText = 'width:65%;padding:10px;border:2px solid #f39c12;border-radius:8px;background:#16213e;color:#fff;font-size:16px;';
|
|
||||||
const addBtn = document.createElement('button');
|
const addBtn = document.createElement('button');
|
||||||
addBtn.style.cssText = 'width:28%;padding:10px;background:#3498db;color:#fff;border:none;border-radius:8px;font-size:16px;cursor:pointer;margin-left:4px;';
|
addBtn.className = 'mc-btn mc-btn-blue';
|
||||||
|
addBtn.style.width = 'auto';
|
||||||
|
addBtn.style.marginBottom = '0';
|
||||||
|
addBtn.style.padding = '8px 16px';
|
||||||
addBtn.textContent = '+';
|
addBtn.textContent = '+';
|
||||||
addBtn.onclick = async () => {
|
addBtn.onclick = async () => {
|
||||||
const nick = addInput.value.trim().toLowerCase();
|
const nick = addInput.value.trim().toLowerCase();
|
||||||
|
|
@ -360,68 +451,67 @@ function customConfirm(msg, onYes) {
|
||||||
if (d.ok) { showToast(d.message === 'Already friends' ? '✅ Уже друзья!' : '📨 Запрос отправлен!'); }
|
if (d.ok) { showToast(d.message === 'Already friends' ? '✅ Уже друзья!' : '📨 Запрос отправлен!'); }
|
||||||
else { showToast('❌ ' + (d.error || 'Ошибка')); }
|
else { showToast('❌ ' + (d.error || 'Ошибка')); }
|
||||||
};
|
};
|
||||||
addDiv.appendChild(addInput);
|
addRow.appendChild(addInput);
|
||||||
addDiv.appendChild(addBtn);
|
addRow.appendChild(addBtn);
|
||||||
box.appendChild(addDiv);
|
body.appendChild(addRow);
|
||||||
// Privacy settings
|
|
||||||
|
// Privacy checkboxes
|
||||||
const settings = data.settings || { allow_requests: 1, notify_online: 1 };
|
const settings = data.settings || { allow_requests: 1, notify_online: 1 };
|
||||||
const settingsDiv = document.createElement('div');
|
const settingsDiv = document.createElement('div');
|
||||||
settingsDiv.style.cssText = 'margin-top:12px;display:flex;gap:12px;font-size:13px;color:#888;';
|
settingsDiv.style.marginTop = '12px';
|
||||||
const reqLabel = document.createElement('label');
|
const reqLabel = document.createElement('label');
|
||||||
reqLabel.style.cssText = 'cursor:pointer;';
|
reqLabel.className = 'mc-checkbox-row';
|
||||||
const reqCb = document.createElement('input');
|
const reqCb = document.createElement('input');
|
||||||
|
reqCb.className = 'mc-checkbox';
|
||||||
reqCb.type = 'checkbox';
|
reqCb.type = 'checkbox';
|
||||||
reqCb.checked = settings.allow_requests;
|
reqCb.checked = settings.allow_requests;
|
||||||
reqCb.style.cssText = 'margin-right:4px;';
|
|
||||||
reqCb.onchange = async () => {
|
reqCb.onchange = async () => {
|
||||||
await fetch(SERVER_URL + '/api/friends/settings', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,allow_requests:reqCb.checked?1:0,notify_online:settings.notify_online})});
|
await fetch(SERVER_URL + '/api/friends/settings', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({tg_id:tgUser.id,allow_requests:reqCb.checked?1:0,notify_online:settings.notify_online})});
|
||||||
};
|
};
|
||||||
reqLabel.appendChild(reqCb);
|
reqLabel.appendChild(reqCb);
|
||||||
reqLabel.appendChild(document.createTextNode(' Запросы в друзья'));
|
reqLabel.appendChild(document.createTextNode(' Запросы в друзья'));
|
||||||
settingsDiv.appendChild(reqLabel);
|
settingsDiv.appendChild(reqLabel);
|
||||||
box.appendChild(settingsDiv);
|
body.appendChild(settingsDiv);
|
||||||
// Back button
|
|
||||||
const btnBackF = document.createElement('button');
|
|
||||||
btnBackF.style.cssText = 'width:100%;padding:10px;margin-top:12px;background:transparent;color:#888;border:1px solid #444;border-radius:8px;cursor:pointer;font-size:14px;';
|
|
||||||
btnBackF.textContent = '← Назад';
|
|
||||||
btnBackF.onclick = () => { overlay.remove(); showStartMenu().then(resolve); };
|
|
||||||
box.appendChild(btnBackF);
|
|
||||||
} catch(e) { showToast('❌ Ошибка загрузки друзей'); console.error(e); }
|
} catch(e) { showToast('❌ Ошибка загрузки друзей'); console.error(e); }
|
||||||
};
|
};
|
||||||
box.appendChild(btnFriends);
|
body.appendChild(btnFriends);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player name input (if not set)
|
// Player name input
|
||||||
if (!playerName) {
|
if (!playerName) {
|
||||||
|
const nameDivider = document.createElement('hr');
|
||||||
|
nameDivider.className = 'mc-divider';
|
||||||
|
body.appendChild(nameDivider);
|
||||||
|
const nameLabel = document.createElement('div');
|
||||||
|
nameLabel.className = 'mc-label';
|
||||||
|
nameLabel.textContent = 'Твоё имя:';
|
||||||
|
body.appendChild(nameLabel);
|
||||||
const nameInput = document.createElement('input');
|
const nameInput = document.createElement('input');
|
||||||
|
nameInput.className = 'mc-input';
|
||||||
nameInput.type = 'text';
|
nameInput.type = 'text';
|
||||||
nameInput.placeholder = 'Ваше имя';
|
nameInput.placeholder = 'Ваше имя';
|
||||||
nameInput.style.cssText = 'width:90%;padding:10px;margin:16px 0 8px;border:2px solid #e94560;border-radius:8px;background:#16213e;color:#fff;font-size:16px;text-align:center;';
|
|
||||||
nameInput.value = tgUser ? (tgUser.username || tgUser.first_name || '') : '';
|
nameInput.value = tgUser ? (tgUser.username || tgUser.first_name || '') : '';
|
||||||
nameInput.maxLength = 20;
|
nameInput.maxLength = 20;
|
||||||
box.appendChild(nameInput);
|
|
||||||
|
|
||||||
// Update playerName on input
|
|
||||||
nameInput.addEventListener('input', () => {
|
nameInput.addEventListener('input', () => {
|
||||||
if (nameInput.value.trim()) {
|
if (nameInput.value.trim()) {
|
||||||
playerName = nameInput.value.trim();
|
playerName = nameInput.value.trim();
|
||||||
localStorage.setItem('minegrechka_playerName', playerName);
|
localStorage.setItem('minegrechka_playerName', playerName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
box.appendChild(document.createElement('br'));
|
body.appendChild(nameInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
box.appendChild(body);
|
||||||
overlay.appendChild(box);
|
overlay.appendChild(box);
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
// If TG user without world in URL, also try to auto-load their save
|
// Auto-load TG save
|
||||||
if (isTgWebApp && tgUser && !worldParam) {
|
if (isTgWebApp && tgUser && !worldParam) {
|
||||||
fetch(SERVER_URL + '/api/tg/save?tg_id=' + tgUser.id)
|
fetch(SERVER_URL + '/api/tg/save?tg_id=' + tgUser.id)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.ok && data.world_id) {
|
if (data.ok && data.world_id) {
|
||||||
// Update the "Continue" button to show the world code
|
// could update UI but menu is already shown
|
||||||
const historyBtn = box.querySelector('button:nth-child(5)'); // rough
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="game.js?v=50"></script>
|
<script src="game.js?v=51"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue