Feature: stage light bars

This commit is contained in:
Dejvino 2026-01-04 07:04:28 +00:00
parent edbed229b3
commit 7296715a5e
4 changed files with 190 additions and 0 deletions

View File

@ -123,6 +123,68 @@ export class ConfigUI extends SceneFeature {
// Gameboy Toggle
createToggle('Gameboy', 'gameboyEnabled');
// --- Light Bar Colors ---
const colorContainer = document.createElement('div');
Object.assign(colorContainer.style, {
display: 'flex',
flexDirection: 'column',
gap: '5px',
marginTop: '5px',
paddingTop: '5px',
borderTop: '1px solid #444'
});
const colorLabel = document.createElement('label');
colorLabel.innerText = 'Stage Light Bars';
colorContainer.appendChild(colorLabel);
const colorControls = document.createElement('div');
colorControls.style.display = 'flex';
colorControls.style.gap = '5px';
const colorPicker = document.createElement('input');
colorPicker.type = 'color';
colorPicker.value = '#ff00ff';
colorPicker.style.width = '40px';
colorPicker.style.border = 'none';
colorPicker.style.cursor = 'pointer';
const addColorBtn = document.createElement('button');
addColorBtn.innerText = '+';
addColorBtn.style.cursor = 'pointer';
addColorBtn.onclick = () => {
state.config.lightBarColors.push(colorPicker.value);
saveConfig();
updateColorList();
const lightBars = sceneFeatureManager.features.find(f => f.constructor.name === 'StageLightBars');
if (lightBars) lightBars.refreshColors();
};
const clearColorsBtn = document.createElement('button');
clearColorsBtn.innerText = 'Clear';
clearColorsBtn.style.cursor = 'pointer';
clearColorsBtn.onclick = () => {
state.config.lightBarColors = [];
saveConfig();
updateColorList();
const lightBars = sceneFeatureManager.features.find(f => f.constructor.name === 'StageLightBars');
if (lightBars) lightBars.refreshColors();
};
colorControls.appendChild(colorPicker);
colorControls.appendChild(addColorBtn);
colorControls.appendChild(clearColorsBtn);
colorContainer.appendChild(colorControls);
const colorList = document.createElement('div');
colorList.style.display = 'flex';
colorList.style.flexWrap = 'wrap';
colorList.style.gap = '4px';
colorList.style.marginTop = '4px';
this.colorList = colorList;
colorContainer.appendChild(colorList);
rightContainer.appendChild(colorContainer);
// Guest Count Input
const guestRow = document.createElement('div');
guestRow.style.display = 'flex';
@ -378,6 +440,7 @@ export class ConfigUI extends SceneFeature {
document.body.appendChild(leftContainer);
document.body.appendChild(rightContainer);
this.updateStatus();
this.updateColorList();
// Restore poster
MediaStorage.getPoster().then(file => {
@ -388,6 +451,22 @@ export class ConfigUI extends SceneFeature {
});
}
updateColorList() {
if (!this.colorList) return;
this.colorList.innerHTML = '';
state.config.lightBarColors.forEach(color => {
const swatch = document.createElement('div');
Object.assign(swatch.style, {
width: '15px',
height: '15px',
backgroundColor: color,
border: '1px solid #fff',
borderRadius: '2px'
});
this.colorList.appendChild(swatch);
});
}
updateStatus() {
if (!this.songLabel) return;

View File

@ -19,6 +19,7 @@ import { DJ } from './dj.js';
import { ProjectionScreen } from './projection-screen.js';
import { StageLasers } from './stage-lasers.js';
import { ConfigUI } from './config-ui.js';
import { StageLightBars } from './stage-light-bars.js';
// Scene Features ^^^
// --- Scene Modeling Function ---

View File

@ -0,0 +1,109 @@
import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
export class StageLightBars extends SceneFeature {
constructor() {
super();
this.bars = [];
sceneFeatureManager.register(this);
}
init() {
// Shared Geometry/Material setup
// We use individual materials to allow different colors per bar if needed,
// or we can update them dynamically.
// 1. Stage Front Edge Bar
// Stage is approx width 11, height 1.5, centered at z=-17.5, depth 5.
// Front edge is at z = -17.5 + 2.5 = -15.
this.createBar(new THREE.Vector3(0, 1.50, -14.8), new THREE.Vector3(11, 0.1, 0.1));
// 2. Stage Side Bars (Vertical)
// Left Front
this.createBar(new THREE.Vector3(-5.45, 0.7, -14.8), new THREE.Vector3(0.1, 1.5, 0.1));
// Right Front
this.createBar(new THREE.Vector3(5.45, 0.7, -14.8), new THREE.Vector3(0.1, 1.5, 0.1));
// 3. Overhead Beam Bars
// Beam is at y=9, z=-14, length 24.
this.createBar(new THREE.Vector3(0, 8.7, -14), new THREE.Vector3(24, 0.1, 0.1));
// 4. Vertical Truss Bars (Sides of the beam)
this.createBar(new THREE.Vector3(-11.9, 3, -14), new THREE.Vector3(0.2, 12, 0.2));
this.createBar(new THREE.Vector3(11.9, 3, -14), new THREE.Vector3(0.2, 12, 0.2));
this.applyColors();
}
createBar(position, size) {
const geometry = new THREE.BoxGeometry(size.x, size.y, size.z);
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 1.0,
roughness: 0.4,
metalness: 0.8
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
mesh.castShadow = true; // Maybe cast shadow?
state.scene.add(mesh);
this.bars.push({ mesh, material });
}
applyColors() {
const colors = state.config.lightBarColors;
if (!colors || colors.length === 0) {
// Default off or white if list is empty
this.bars.forEach(bar => {
bar.material.color.setHex(0x111111);
bar.material.emissive.setHex(0x000000);
});
return;
}
this.bars.forEach((bar, index) => {
const colorHex = colors[index % colors.length];
const color = new THREE.Color(colorHex);
bar.material.color.copy(color);
bar.material.emissive.copy(color);
});
}
update(deltaTime) {
// Check if colors changed (simple length check or reference check could work,
// but here we can just re-apply if needed or rely on ConfigUI calling a refresh.
// For simplicity, we'll assume ConfigUI updates state and we might poll or be notified.
// Let's just re-apply in update if we want to be reactive to the UI instantly,
// though it's slightly expensive. Better: ConfigUI triggers a refresh.
// For now, we'll just animate intensity.
if (!state.partyStarted) return;
const time = state.clock.getElapsedTime();
let beatIntensity = 0;
if (state.music) {
beatIntensity = state.music.beatIntensity;
}
// Pulsate
const baseIntensity = 0.1;
const pulse = Math.sin(time * 2.0) * 0.3 + 0.3; // Breathing
const beatFlash = beatIntensity * 2.0; // Sharp flash on beat
const totalIntensity = baseIntensity + pulse + beatFlash;
this.bars.forEach(bar => {
bar.material.emissiveIntensity = totalIntensity;
});
}
refreshColors() {
this.applyColors();
}
}
new StageLightBars();

View File

@ -10,6 +10,7 @@ export function initState() {
consoleRGBEnabled: true,
consoleEnabled: true,
gameboyEnabled: false,
lightBarColors: ['#ff00ff', '#00ffff', '#ffff00'], // Default neon colors
guestCount: 150,
djHat: 'None' // 'None', 'Santa', 'Top Hat'
};