Spaces:
Running
Running
хорошо, добавь прокрутку в редактор, а то нельзя добраться до некоторых настроек, создай самого персонажа и сделай игровое поле с сеткой после редакторы персонажа с тестовой картой и интерфейсом
3f8b4a4
verified
| document.addEventListener('DOMContentLoaded', () => { | |
| // State | |
| const characterState = { | |
| name: '', | |
| age: 24, | |
| gender: 'Male', | |
| traits: [], | |
| points: 3, | |
| stats: { | |
| logic: 50, | |
| emotion: 50, | |
| id: 50, | |
| strength: 50, | |
| agility: 50, | |
| toughness: 50, | |
| perception: 50 | |
| }, | |
| beliefs: { | |
| collectivism: 50, | |
| loyalty: 20, | |
| rationality: 70, | |
| life: 40 | |
| }, | |
| background: { | |
| childhood: 'Vat-Grown Soldier', | |
| adulthood: 'Corporate Executive', | |
| skills: { | |
| melee: 4, | |
| ranged: 2, | |
| social: 3, | |
| intellectual: 5, | |
| crafting: 2, | |
| medicine: 0 | |
| } | |
| } | |
| }; | |
| // Tab Switching Logic | |
| const tabBtns = document.querySelectorAll('.tab-btn'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| tabBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const target = btn.dataset.target; | |
| // Update buttons | |
| tabBtns.forEach(b => { | |
| b.classList.remove('text-neural-blue', 'border-b-2', 'border-neural-blue'); | |
| b.classList.add('text-slate-500', 'border-transparent'); | |
| }); | |
| btn.classList.remove('text-slate-500', 'border-transparent'); | |
| btn.classList.add('text-neural-blue', 'border-b-2', 'border-neural-blue'); | |
| // Update content | |
| tabContents.forEach(content => { | |
| if (content.id === target) { | |
| content.classList.remove('hidden'); | |
| // Trigger specific animations or logic per tab if needed | |
| if (target === 'psychology') { | |
| drawNeuralPreview(); | |
| } | |
| } else { | |
| content.classList.add('hidden'); | |
| } | |
| }); | |
| }); | |
| }); | |
| // Trait Selection Logic | |
| const traitItems = document.querySelectorAll('.trait-item'); | |
| const pointsDisplay = document.getElementById('trait-points'); | |
| traitItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| const costText = item.querySelector('span.text-xs').innerText; | |
| const cost = parseInt(costText); | |
| const traitName = item.querySelector('h4').innerText; | |
| if (item.classList.contains('selected')) { | |
| // Deselect | |
| item.classList.remove('selected'); | |
| characterState.traits = characterState.traits.filter(t => t !== traitName); | |
| characterState.points += cost; | |
| } else { | |
| // Select | |
| if (characterState.points - cost >= 0) { | |
| item.classList.add('selected'); | |
| characterState.traits.push(traitName); | |
| characterState.points -= cost; | |
| } else { | |
| // Shake effect or error feedback could go here | |
| console.log("Not enough points"); | |
| } | |
| } | |
| pointsDisplay.innerText = characterState.points; | |
| updateNeuralPreview(); | |
| }); | |
| }); | |
| // Character Name Input | |
| const charNameInput = document.getElementById('char-name'); | |
| const charNameDisplay = document.getElementById('char-name-display'); | |
| charNameInput.addEventListener('input', (e) => { | |
| characterState.name = e.target.value; | |
| charNameDisplay.innerText = e.target.value || 'Unknown'; | |
| }); | |
| // Age Slider | |
| const ageSlider = document.getElementById('char-age'); | |
| const ageValue = document.getElementById('age-value'); | |
| const ageDisplay = document.getElementById('char-age-display'); | |
| ageSlider.addEventListener('input', (e) => { | |
| characterState.age = parseInt(e.target.value); | |
| ageValue.innerText = e.target.value; | |
| ageDisplay.innerText = e.target.value; | |
| }); | |
| // Cognitive & Physical Stats Sliders | |
| const statControls = document.querySelectorAll('.stat-control'); | |
| statControls.forEach(control => { | |
| const range = control.querySelector('input[type=range]'); | |
| const display = control.querySelector('.value-display'); | |
| const statName = control.dataset.stat; | |
| range.addEventListener('input', (e) => { | |
| const val = e.target.value; | |
| display.innerText = val + '%'; | |
| characterState.stats[statName] = parseInt(val); | |
| updateNeuralPreview(); | |
| }); | |
| }); | |
| // Childhood Selection | |
| const childhoodItems = document.querySelectorAll('.childhood-item'); | |
| childhoodItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| childhoodItems.forEach(i => { | |
| i.classList.remove('selected'); | |
| i.classList.remove('border-neural-blue', 'bg-slate-900/50'); | |
| i.classList.add('border-slate-700', 'bg-slate-800'); | |
| i.querySelector('.check-icon').setAttribute('data-feather', 'circle'); | |
| i.querySelector('.check-icon').classList.remove('text-neural-blue'); | |
| i.querySelector('.check-icon').classList.add('text-slate-600'); | |
| }); | |
| item.classList.add('selected'); | |
| item.classList.remove('border-slate-700', 'bg-slate-800'); | |
| item.classList.add('border-neural-blue', 'bg-slate-900/50'); | |
| item.querySelector('.check-icon').setAttribute('data-feather', 'check-circle'); | |
| item.querySelector('.check-icon').classList.add('text-neural-blue'); | |
| item.querySelector('.check-icon').classList.remove('text-slate-600'); | |
| characterState.background.childhood = item.querySelector('h4').innerText; | |
| updateSkills(); | |
| feather.replace(); | |
| }); | |
| }); | |
| // Adulthood Selection | |
| const adulthoodItems = document.querySelectorAll('.adulthood-item'); | |
| adulthoodItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| adulthoodItems.forEach(i => { | |
| i.classList.remove('selected'); | |
| i.classList.remove('border-neural-blue', 'bg-slate-900/50'); | |
| i.classList.add('border-slate-700', 'bg-slate-800'); | |
| i.querySelector('.check-icon').setAttribute('data-feather', 'circle'); | |
| i.querySelector('.check-icon').classList.remove('text-neural-blue'); | |
| i.querySelector('.check-icon').classList.add('text-slate-600'); | |
| }); | |
| item.classList.add('selected'); | |
| item.classList.remove('border-slate-700', 'bg-slate-800'); | |
| item.classList.add('border-neural-blue', 'bg-slate-900/50'); | |
| item.querySelector('.check-icon').setAttribute('data-feather', 'check-circle'); | |
| item.querySelector('.check-icon').classList.add('text-neural-blue'); | |
| item.querySelector('.check-icon').classList.remove('text-slate-600'); | |
| characterState.background.adulthood = item.querySelector('h4').innerText; | |
| updateSkills(); | |
| feather.replace(); | |
| }); | |
| }); | |
| // Update Skills Display based on background | |
| function updateSkills() { | |
| const skillsGrid = document.getElementById('skills-grid'); | |
| if (!skillsGrid) return; | |
| // Base skills from childhood | |
| let skills = { melee: 0, ranged: 0, social: 0, intellectual: 0, crafting: 0, medicine: 0 }; | |
| switch(characterState.background.childhood) { | |
| case 'Vat-Grown Soldier': | |
| skills.melee += 2; skills.ranged += 1; skills.social -= 2; | |
| break; | |
| case 'Street Urchin': | |
| skills.melee += 1; | |
| break; | |
| case 'Academic Prodigy': | |
| skills.intellectual += 3; skills.social += 1; | |
| break; | |
| case 'Farm Hand': | |
| skills.intellectual -= 1; | |
| break; | |
| } | |
| // Skills from adulthood | |
| switch(characterState.background.adulthood) { | |
| case 'Mercenary': | |
| skills.melee += 1; skills.ranged += 2; | |
| break; | |
| case 'Cyber-Surgeon': | |
| skills.medicine += 3; skills.crafting += 2; | |
| break; | |
| case 'Corporate Executive': | |
| skills.social += 3; skills.intellectual += 2; | |
| break; | |
| case 'Wasteland Scavenger': | |
| skills.crafting += 3; skills.melee += 1; | |
| break; | |
| } | |
| // Update display | |
| skillsGrid.innerHTML = ` | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">⚔️</div> | |
| <div class="text-xs text-slate-400">Melee</div> | |
| <div class="text-neural-blue font-bold">${Math.max(0, skills.melee)}</div> | |
| </div> | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">🔫</div> | |
| <div class="text-xs text-slate-400">Ranged</div> | |
| <div class="text-neural-blue font-bold">${Math.max(0, skills.ranged)}</div> | |
| </div> | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">💬</div> | |
| <div class="text-xs text-slate-400">Social</div> | |
| <div class="text-neural-blue font-bold">${Math.max(0, skills.social)}</div> | |
| </div> | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">🧠</div> | |
| <div class="text-xs text-slate-400">Intellectual</div> | |
| <div class="text-neural-blue font-bold">${Math.max(0, skills.intellectual)}</div> | |
| </div> | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">🔧</div> | |
| <div class="text-xs text-slate-400">Crafting</div> | |
| <div class="text-neural-blue font-bold">${Math.max(0, skills.crafting)}</div> | |
| </div> | |
| <div class="bg-slate-900 rounded p-3 text-center border border-slate-700"> | |
| <div class="text-2xl mb-1">🩺</div> | |
| <div class="text-xs text-slate-400">Medicine</div> | |
| <div class="text-slate-500 font-bold">${Math.max(0, skills.medicine)}</div> | |
| </div> | |
| `; | |
| characterState.background.skills = skills; | |
| } | |
| // Belief Sliders Logic (Visual only for now) | |
| const beliefSliders = document.querySelectorAll('.belief-slider input[type=range]'); | |
| beliefSliders.forEach(slider => { | |
| slider.addEventListener('input', (e) => { | |
| const val = e.target.value; | |
| // Update the visual thumb position | |
| const thumb = e.target.parentElement.querySelector('div.absolute.rounded-full.shadow'); | |
| thumb.style.left = val + '%'; | |
| }); | |
| }); | |
| // Canvas Neural Preview Animation | |
| const canvas = document.getElementById('neural-preview'); | |
| const ctx = canvas.getContext('2d'); | |
| let animationId; | |
| function resizeCanvas() { | |
| if(canvas) { | |
| canvas.width = canvas.parentElement.offsetWidth; | |
| canvas.height = canvas.parentElement.offsetHeight; | |
| drawNeuralPreview(); | |
| } | |
| } | |
| window.addEventListener('resize', resizeCanvas); | |
| class Node { | |
| constructor(x, y, type) { | |
| this.x = x; | |
| this.y = y; | |
| this.type = type; // 'center', 'satellite' | |
| this.radius = type === 'center' ? 15 : 4; | |
| this.baseX = x; | |
| this.baseY = y; | |
| this.offset = Math.random() * Math.PI * 2; | |
| } | |
| draw() { | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); | |
| if (this.type === 'center') { | |
| ctx.fillStyle = '#0ea5e9'; | |
| ctx.shadowBlur = 20; | |
| ctx.shadowColor = '#0ea5e9'; | |
| } else { | |
| ctx.fillStyle = '#94a3b8'; | |
| ctx.shadowBlur = 0; | |
| } | |
| ctx.fill(); | |
| ctx.closePath(); | |
| ctx.shadowBlur = 0; // Reset | |
| } | |
| update(time) { | |
| if (this.type === 'satellite') { | |
| // Float gently | |
| this.x = this.baseX + Math.sin(time + this.offset) * 5; | |
| this.y = this.baseY + Math.cos(time + this.offset) * 5; | |
| } | |
| } | |
| } | |
| let nodes = []; | |
| function initNodes() { | |
| nodes = []; | |
| const cx = canvas.width / 2; | |
| const cy = canvas.height / 2; | |
| nodes.push(new Node(cx, cy, 'center')); | |
| // Create satellite nodes based on stats complexity | |
| const complexity = (characterState.stats.logic + characterState.stats.emotion) / 20; | |
| const count = 5 + Math.floor(complexity); | |
| for (let i = 0; i < count; i++) { | |
| const angle = (Math.PI * 2 / count) * i; | |
| const dist = 50 + Math.random() * 40; | |
| nodes.push(new Node( | |
| cx + Math.cos(angle) * dist, | |
| cy + Math.sin(angle) * dist, | |
| 'satellite' | |
| )); | |
| } | |
| } | |
| function drawNeuralPreview() { | |
| if (!ctx) return; | |
| const time = Date.now() * 0.002; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw Connections | |
| ctx.strokeStyle = 'rgba(56, 189, 248, 0.2)'; | |
| ctx.lineWidth = 1; | |
| if (nodes.length > 0) { | |
| const center = nodes[0]; | |
| for (let i = 1; i < nodes.length; i++) { | |
| ctx.beginPath(); | |
| ctx.moveTo(center.x, center.y); | |
| ctx.lineTo(nodes[i].x, nodes[i].y); | |
| ctx.stroke(); | |
| // Draw inter-satellite connections occasionally | |
| if (i < nodes.length - 1) { | |
| ctx.beginPath(); | |
| ctx.moveTo(nodes[i].x, nodes[i].y); | |
| ctx.lineTo(nodes[i+1].x, nodes[i+1].y); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| // Draw Nodes | |
| nodes.forEach(node => { | |
| node.update(time); | |
| node.draw(); | |
| }); | |
| animationId = requestAnimationFrame(drawNeuralPreview); | |
| } | |
| function updateNeuralPreview() { | |
| // Re-init nodes based on stats changes | |
| initNodes(); | |
| } | |
| // Initialize | |
| setTimeout(() => { | |
| resizeCanvas(); | |
| initNodes(); | |
| drawNeuralPreview(); | |
| }, 100); | |
| // Save character data to localStorage when navigating to game | |
| const beginSimulationBtn = document.querySelector('aside a[href="game.html"]'); | |
| if (beginSimulationBtn) { | |
| beginSimulationBtn.addEventListener('click', () => { | |
| localStorage.setItem('neuroscape-character', JSON.stringify(characterState)); | |
| }); | |
| } | |
| }); | |