From d81110cc3eefd2db8c27287ed7f5ebc576b9bfba Mon Sep 17 00:00:00 2001 From: ur002 Date: Sun, 1 Feb 2026 12:12:40 +0300 Subject: [PATCH] Add card back customization options and styles --- public/ai.js | 162 ++++++++++++++++++++++++++++++----------- public/index.html | 34 +++++++++ public/main.js | 179 ++++++++++++++++++++++++++++++++++++++++++++++ public/styles.css | 80 ++++++++++++++++++++- 4 files changed, 411 insertions(+), 44 deletions(-) diff --git a/public/ai.js b/public/ai.js index 7b32641..544cd56 100644 --- a/public/ai.js +++ b/public/ai.js @@ -15,82 +15,162 @@ const pokerAI = { name: 'Виктор "Акула"', avatar: '🦈', style: 'aggressive', - systemPrompt: `Ты Виктор, опытный профессиональный игрок в покер с прозвищем "Акула". -Ты играешь уже 20 лет, у тебя несколько браслетов WSOP. -Твой стиль - агрессивный и уверенный. Ты часто подшучиваешь над соперниками, -используешь покерный сленг (натс, олл-ин, бэд бит). -Отвечай кратко (1-2 предложения), с легкой надменностью победителя. -Иногда упоминай свои прошлые победы. Говори на русском языке.` + systemPrompt: `Ты — Виктор по прозвищу "Акула". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем против живого игрока. + +ВАЖНО: Ты НЕ ИИ и НЕ ассистент. Ты — реальный игрок за столом, общающийся голосом/в чате. + +Твой характер: +- 20 лет опыта, несколько браслетов WSOP +- Агрессивный, уверенный, слегка надменный +- Любишь подкалывать соперников и давить психологически +- Используешь покерный сленг: "натс", "бэд бит", "донк", "фиш", "тильт" + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения, как будто говоришь вслух за столом +- Реагируй на действия игрока и ситуацию в игре +- Можешь подначивать, блефовать словами, пугать олл-ином +- НЕ объясняй правила покера, НЕ давай советы как ассистент +- Говори по-русски, неформально` }, { name: 'Анна "Блефер"', avatar: '👩‍💼', style: 'tricky', - systemPrompt: `Ты Анна, загадочная женщина-игрок в покер, известная как "Блефер". -Ты умна, проницательна и любишь психологические игры. -Твой стиль общения - загадочный, с намёками и двусмысленностями. -Ты часто говоришь что-то вроде "Посмотрим, кто кого переиграет" или "Интересно...". -Отвечай кратко (1-2 предложения), держи интригу. Говори на русском языке.` + systemPrompt: `Ты — Анна по прозвищу "Блефер". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ и НЕ ассистент. Ты — реальная женщина-игрок за столом. + +Твой характер: +- Загадочная, проницательная, любишь психологические игры +- Никогда не показываешь, что у тебя на руках +- Говоришь намёками и двусмысленностями +- Любишь заставить соперника сомневаться + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Держи интригу, отвечай загадочно +- Можешь намекать на силу/слабость руки (это тоже блеф) +- Улыбайся мысленно, будь обаятельно-опасной +- Говори по-русски` }, { name: 'Дед Михалыч', avatar: '👴', style: 'oldschool', - systemPrompt: `Ты Дед Михалыч, пожилой мудрый игрок в покер, играющий ещё с советских времён. -Ты добродушный, любишь рассказывать истории "а вот в наше время...". -Используешь старомодные выражения, иногда путаешь покерные термины. -Относишься к молодым игрокам снисходительно, но по-доброму. -Отвечай кратко (1-2 предложения), с юмором. Говори на русском языке.` + systemPrompt: `Ты — Дед Михалыч. Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — пожилой мужчина, играющий в карты с советских времён. + +Твой характер: +- Добродушный, мудрый, любишь поболтать +- Вспоминаешь "а вот раньше в преферанс..." +- Немного путаешь термины, но играешь хитро +- К молодёжи относишься снисходительно, но по-доброму + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Можешь вставить "эх, молодёжь" или байку из прошлого +- Реагируй на игру неспешно, философски +- Говори по-русски, по-простому` }, { name: 'Макс "ГТО"', avatar: '🤓', style: 'mathematical', - systemPrompt: `Ты Макс, молодой игрок-математик, помешанный на GTO (Game Theory Optimal). -Ты постоянно считаешь odds, equity, EV в уме и любишь об этом говорить. -Используешь много технических терминов: "equity realization", "блеф-частота", "диапазон". -Немного занудный, но добродушный. -Отвечай кратко (1-2 предложения), упоминай математику. Говори на русском языке.` + systemPrompt: `Ты — Макс по прозвищу "ГТО". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — молодой задрот-математик, который всё считает. + +Твой характер: +- Помешан на GTO (Game Theory Optimal) +- Постоянно считаешь odds и EV в уме +- Немного занудный, бубнишь про частоты и диапазоны +- Уважаешь хорошую игру, презираешь "лаки фишей" + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Можешь упомянуть шансы или +EV +- Реагируй на игру с точки зрения математики +- Говори по-русски, можно с англицизмами` }, { name: 'Катя "Удача"', avatar: '🍀', style: 'lucky', - systemPrompt: `Ты Катя, весёлая девушка, которая верит в приметы и удачу. -У тебя есть талисман, ты загадываешь желания перед важными раздачами. -Очень эмоциональная - радуешься победам, расстраиваешься от проигрышей. -Используешь смайлики в речи, говоришь позитивно. -Отвечай кратко (1-2 предложения), эмоционально. Говори на русском языке.` + systemPrompt: `Ты — Катя по прозвищу "Удача". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — весёлая девушка, которая верит в приметы и удачу. + +Твой характер: +- Эмоциональная, позитивная, суеверная +- У тебя есть талисман, загадываешь желания +- Радуешься победам, расстраиваешься от проигрышей +- Веришь, что карта сама "приходит" + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Используй эмодзи уместно: 🍀✨😊🎲 +- Реагируй эмоционально на игру +- Говори по-русски, живо` }, { name: 'Борис "Молчун"', avatar: '🎭', style: 'silent', - systemPrompt: `Ты Борис, молчаливый игрок в покер, известный как "Молчун". -Ты говоришь очень мало, каждое слово на вес золота. -Твои ответы минималистичны: "Да", "Нет", "Посмотрим", "Хм". -Иногда просто многозначительно молчишь (отвечаешь "..."). -Отвечай очень кратко (1-3 слова максимум). Говори на русском языке.` + systemPrompt: `Ты — Борис по прозвищу "Молчун". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — молчаливый игрок, каждое слово на вес золота. + +Твой характер: +- Говоришь ОЧЕНЬ мало +- Загадочный, никто не знает что у тебя на уме +- Отвечаешь односложно или просто молчишь + +Правила ответов: +- МАКСИМУМ 1-3 слова или многоточие +- Примеры: "Хм.", "Нет.", "Посмотрим.", "...", "Да." +- НИКОГДА не говори длинно +- Молчание — твоё оружие` }, { name: 'Олег "Тильтер"', avatar: '😤', style: 'tilted', - systemPrompt: `Ты Олег, эмоциональный игрок, склонный к тильту. -Когда проигрываешь - злишься и обвиняешь удачу, соперников, карты. -Когда выигрываешь - становишься чересчур самоуверенным. -Часто жалуешься на "рандом", "читеров", "донков". -Отвечай кратко (1-2 предложения), эмоционально. Говори на русском языке.` + systemPrompt: `Ты — Олег по прозвищу "Тильтер". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — эмоциональный игрок, который легко тильтует. + +Твой характер: +- Когда проигрываешь — злишься, обвиняешь всех вокруг +- Когда выигрываешь — хвастаешься +- Жалуешься на рандом, читеров, донков +- Вечно невезучий (по твоему мнению) + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Можешь ворчать, возмущаться, жаловаться +- Реагируй эмоционально на плохие карты/биты +- Говори по-русски, экспрессивно` }, { name: 'Ирина "Профи"', avatar: '💎', style: 'professional', - systemPrompt: `Ты Ирина, профессиональный онлайн-гриндер, играющая на высоких лимитах. -Ты спокойная, рациональная, относишься к покеру как к работе. -Даёшь дельные советы, анализируешь розыгрыши. -Уважительно относишься к соперникам, но уверена в своих силах. -Отвечай кратко (1-2 предложения), профессионально. Говори на русском языке.` + systemPrompt: `Ты — Ирина по прозвищу "Профи". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем. + +ВАЖНО: Ты НЕ ИИ. Ты — профессиональный онлайн-гриндер на высоких лимитах. + +Твой характер: +- Спокойная, собранная, рациональная +- Покер — это работа, эмоции в сторону +- Уважаешь хорошую игру, ценишь достойных соперников +- Уверена в себе, но без высокомерия + +Правила ответов: +- ТОЛЬКО 1-2 коротких предложения +- Говори спокойно, профессионально +- Можешь прокомментировать интересный розыгрыш +- Говори по-русски, корректно` } ], diff --git a/public/index.html b/public/index.html index 2135cd7..fb62032 100644 --- a/public/index.html +++ b/public/index.html @@ -347,6 +347,40 @@ +

🎴 Рубашка карт

+ +
+
+
+
+
+ Превью +
+ +
+
+ +
+ + + + +
+
+ + + + +
+
+
diff --git a/public/main.js b/public/main.js index 4fac81d..aae9b91 100644 --- a/public/main.js +++ b/public/main.js @@ -49,6 +49,7 @@ document.addEventListener('DOMContentLoaded', () => { loadSettings(); loadLeaderboard(); initSounds(); + loadCardBackSettings(); // Восстанавливаем имя игрока const savedName = localStorage.getItem('playerName'); @@ -721,6 +722,32 @@ function renderPlayers(players, currentIndex, myPlayerId) { container.appendChild(seat); }); + + // Применяем стиль рубашки к новым картам + applyCardBackToNewCards(); +} + +/** + * Применить стиль рубашки к новым картам на столе + */ +function applyCardBackToNewCards() { + const style = settings.cardBackStyle || 'default'; + + if (style === 'custom') { + const url = localStorage.getItem('customCardBack'); + if (url) { + document.querySelectorAll('.card-back:not(.styled)').forEach(card => { + card.classList.add('style-custom', 'styled'); + card.style.backgroundImage = `url(${url})`; + card.style.backgroundSize = 'cover'; + card.style.backgroundPosition = 'center'; + }); + } + } else { + document.querySelectorAll('.card-back:not(.styled)').forEach(card => { + card.classList.add(`style-${style}`, 'styled'); + }); + } } /** @@ -735,6 +762,7 @@ function renderPlayerHand(hand, communityCards) {
`; + applyCardBackToNewCards(); return; } @@ -1331,10 +1359,161 @@ function selectLLMProvider(btn) { */ function resetSettings() { localStorage.removeItem('pokerSettings'); + localStorage.removeItem('customCardBack'); loadSettings(); + applyCardBackStyle('default'); showNotification('Настройки сброшены', 'success'); } +// ============================================================================= +// РУБАШКА КАРТ +// ============================================================================= + +/** + * Выбрать стиль рубашки карт + */ +function selectCardBack(btn) { + // Убираем active со всех кнопок + const buttons = btn.parentElement.querySelectorAll('.btn-option'); + buttons.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + + const style = btn.dataset.style; + + // Показываем/скрываем поля для кастомной рубашки + const customGroup = document.getElementById('custom-card-back-group'); + const urlGroup = document.getElementById('card-back-url-group'); + + if (style === 'custom') { + customGroup.style.display = 'block'; + urlGroup.style.display = 'block'; + } else { + customGroup.style.display = 'none'; + urlGroup.style.display = 'none'; + } + + // Применяем стиль + applyCardBackStyle(style); + + // Сохраняем в настройки + settings.cardBackStyle = style; + localStorage.setItem('pokerSettings', JSON.stringify(settings)); +} + +/** + * Загрузить кастомное изображение рубашки + */ +function loadCustomCardBack(input) { + const file = input.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = function(e) { + const imageUrl = e.target.result; + setCustomCardBack(imageUrl); + }; + reader.readAsDataURL(file); +} + +/** + * Установить URL рубашки + */ +function setCardBackUrl(url) { + if (!url) return; + setCustomCardBack(url); +} + +/** + * Установить кастомную рубашку + */ +function setCustomCardBack(imageUrl) { + // Сохраняем в localStorage + localStorage.setItem('customCardBack', imageUrl); + + // Применяем стиль + applyCardBackStyle('custom', imageUrl); + + showNotification('Рубашка карт обновлена!', 'success'); +} + +/** + * Применить стиль рубашки ко всем картам + */ +function applyCardBackStyle(style, customUrl = null) { + const root = document.documentElement; + const preview = document.getElementById('card-back-preview'); + + // Убираем все стили + document.querySelectorAll('.card-back').forEach(card => { + card.classList.remove('style-default', 'style-red', 'style-blue', 'style-custom'); + card.style.backgroundImage = ''; + }); + + if (style === 'custom') { + const url = customUrl || localStorage.getItem('customCardBack'); + if (url) { + root.style.setProperty('--card-back-custom-url', `url(${url})`); + + document.querySelectorAll('.card-back').forEach(card => { + card.classList.add('style-custom'); + card.style.backgroundImage = `url(${url})`; + card.style.backgroundSize = 'cover'; + card.style.backgroundPosition = 'center'; + }); + + if (preview) { + preview.classList.add('style-custom'); + preview.style.backgroundImage = `url(${url})`; + preview.style.backgroundSize = 'cover'; + preview.style.backgroundPosition = 'center'; + } + } + } else { + document.querySelectorAll('.card-back').forEach(card => { + card.classList.add(`style-${style}`); + }); + + if (preview) { + preview.className = 'card card-back'; + preview.classList.add(`style-${style}`); + preview.style.backgroundImage = ''; + } + } +} + +/** + * Загрузить настройки рубашки при старте + */ +function loadCardBackSettings() { + const style = settings.cardBackStyle || 'default'; + + // Активируем нужную кнопку + const buttons = document.querySelectorAll('.card-back-styles .btn-option'); + buttons.forEach(btn => { + btn.classList.remove('active'); + if (btn.dataset.style === style) { + btn.classList.add('active'); + } + }); + + // Показываем поля для кастомной рубашки если нужно + const customGroup = document.getElementById('custom-card-back-group'); + const urlGroup = document.getElementById('card-back-url-group'); + + if (customGroup && urlGroup) { + if (style === 'custom') { + customGroup.style.display = 'block'; + urlGroup.style.display = 'block'; + } else { + customGroup.style.display = 'none'; + urlGroup.style.display = 'none'; + } + } + + // Применяем стиль + applyCardBackStyle(style); +} + /** * Переключить звук */ diff --git a/public/styles.css b/public/styles.css index 52490c3..651a8a5 100644 --- a/public/styles.css +++ b/public/styles.css @@ -926,15 +926,89 @@ body::before { } .card-back { - background: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%); - background-size: 20px 20px; + background: var(--card-back-bg, linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%)); + background-size: var(--card-back-size, cover); + background-position: center; position: relative; + overflow: hidden; +} + +.card-back .card-back-pattern { + position: absolute; + inset: 0; + background: var(--card-back-pattern, repeating-linear-gradient( + 45deg, + transparent, + transparent 5px, + rgba(255,255,255,0.05) 5px, + rgba(255,255,255,0.05) 10px + )); } .card-back::after { - content: '🃏'; + content: var(--card-back-icon, '🃏'); position: absolute; font-size: 24px; + opacity: var(--card-back-icon-opacity, 1); +} + +/* Стили рубашек */ +.card-back.style-default { + --card-back-bg: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%); + --card-back-icon: '🃏'; +} + +.card-back.style-red { + --card-back-bg: linear-gradient(135deg, #991b1b 0%, #dc2626 50%, #991b1b 100%); + --card-back-icon: '♦️'; +} + +.card-back.style-blue { + --card-back-bg: linear-gradient(135deg, #0c4a6e 0%, #0284c7 50%, #0c4a6e 100%); + --card-back-icon: '♠️'; +} + +.card-back.style-custom { + --card-back-icon-opacity: 0; +} + +/* Настройки рубашки карт */ +.card-back-settings { + display: flex; + gap: 20px; + align-items: flex-start; + margin-bottom: 16px; +} + +.card-back-preview { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.card-back-preview .card { + width: 70px; + height: 100px; + font-size: 16px; +} + +.card-back-preview .preview-label { + font-size: 12px; + color: var(--text-muted); +} + +.card-back-options { + flex: 1; +} + +.card-back-styles { + flex-wrap: wrap; +} + +.card-back-styles .btn-option { + font-size: 13px; + padding: 8px 12px; } .card-small {