neuroscape / script.js
Kabanchiquito's picture
хорошо, добавь прокрутку в редактор, а то нельзя добраться до некоторых настроек, создай самого персонажа и сделай игровое поле с сеткой после редакторы персонажа с тестовой картой и интерфейсом
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));
});
}
});