mirror of
http://10.0.2.1:3031/sauer/bfa-dryer-design.git
synced 2026-06-30 11:36:42 +10:00
Enhanced animations: state-aware ON/OFF, 6 new animation types
- Linked elements show static red ring when OFF, animate when ON - Shapes get red tint fill when linked control is OFF - New animations: blink, vibrate, glow, flow (plus existing fan, agitator, burner, conveyor, pulse) - Right-click context menu shows all 10 animation options - Saved layout (current.json) is NOT touched by this update Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
748f8d5238
commit
1d26f78592
@ -108,15 +108,28 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
|
|||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
@keyframes spin{100%{transform-origin:center;transform:rotate(360deg)}}
|
@keyframes spin{100%{transform-origin:center;transform:rotate(360deg)}}
|
||||||
@keyframes pulse-glow{0%,100%{opacity:0.3}50%{opacity:1}}
|
@keyframes pulse-glow{0%,100%{opacity:0.4}50%{opacity:1}}
|
||||||
@keyframes scroll-belt{100%{stroke-dashoffset:-20}}
|
@keyframes scroll-belt{100%{stroke-dashoffset:-20}}
|
||||||
@keyframes fall-particles{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(30px)}}
|
@keyframes fall-particles{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(30px)}}
|
||||||
|
@keyframes blink{0%,100%{opacity:1}50%{opacity:0.1}}
|
||||||
|
@keyframes vibrate{0%,100%{transform:translateX(0)}25%{transform:translateX(-2px)}75%{transform:translateX(2px)}}
|
||||||
|
@keyframes glow-pulse{0%,100%{filter:drop-shadow(0 0 2px currentColor)}50%{filter:drop-shadow(0 0 12px currentColor)}}
|
||||||
|
@keyframes flow-dash{100%{stroke-dashoffset:-30}}
|
||||||
.fan-anim{animation:spin 1s linear infinite}
|
.fan-anim{animation:spin 1s linear infinite}
|
||||||
.burner-glow{animation:pulse-glow 1.5s ease-in-out infinite}
|
.burner-glow{animation:pulse-glow 1.5s ease-in-out infinite}
|
||||||
.conveyor-anim{animation:scroll-belt 0.5s linear infinite}
|
.conveyor-anim{animation:scroll-belt 0.5s linear infinite}
|
||||||
.discharge-anim{animation:fall-particles 1s ease-out infinite}
|
.discharge-anim{animation:fall-particles 1s ease-out infinite}
|
||||||
.agitator-anim{animation:spin 2s linear infinite}
|
.agitator-anim{animation:spin 2s linear infinite}
|
||||||
.pulse-anim{animation:pulse-glow 1.5s ease-in-out infinite}
|
.pulse-anim{animation:pulse-glow 1.5s ease-in-out infinite}
|
||||||
|
.blink-anim{animation:blink 1s step-end infinite}
|
||||||
|
.vibrate-anim{animation:vibrate 0.1s linear infinite}
|
||||||
|
.glow-anim{animation:glow-pulse 2s ease-in-out infinite}
|
||||||
|
.flow-anim{animation:flow-dash 1s linear infinite}
|
||||||
|
|
||||||
|
/* Static OFF state for linked elements */
|
||||||
|
.equip-off{fill:var(--red) !important;fill-opacity:0.3 !important;stroke:var(--red) !important}
|
||||||
|
.equip-on{stroke-width:2 !important}
|
||||||
|
.static-off-ring{fill:none;stroke:var(--red);stroke-width:2;opacity:0.6}
|
||||||
|
|
||||||
/* Help panel */
|
/* Help panel */
|
||||||
.help-panel{position:fixed;right:0;top:48px;width:400px;height:calc(100vh - 48px);background:var(--card);border-left:2px solid var(--border);z-index:150;display:none;flex-direction:column;overflow-y:auto;padding:24px}
|
.help-panel{position:fixed;right:0;top:48px;width:400px;height:calc(100vh - 48px);background:var(--card);border-left:2px solid var(--border);z-index:150;display:none;flex-direction:column;overflow-y:auto;padding:24px}
|
||||||
@ -679,8 +692,8 @@ function ctxAction(action) {
|
|||||||
saveEquip(eq); renderSchematic();
|
saveEquip(eq); renderSchematic();
|
||||||
}
|
}
|
||||||
} else if (action === 'anim') {
|
} else if (action === 'anim') {
|
||||||
const anims = ['none', 'fan', 'agitator', 'burner', 'conveyor', 'pulse'];
|
const anims = ['none', 'fan', 'agitator', 'burner', 'conveyor', 'pulse', 'blink', 'vibrate', 'glow', 'flow'];
|
||||||
const choice = prompt('Animation — type one of: none, fan, agitator, burner, conveyor, pulse\nCurrent: ' + (eq.anim || 'none'), eq.anim || 'none');
|
const choice = prompt('Animation — type one of:\n none, fan, agitator, burner, conveyor,\n pulse, blink, vibrate, glow, flow\n\nCurrent: ' + (eq.anim || 'none'), eq.anim || 'none');
|
||||||
if (choice && anims.includes(choice)) {
|
if (choice && anims.includes(choice)) {
|
||||||
eq.anim = choice === 'none' ? null : choice;
|
eq.anim = choice === 'none' ? null : choice;
|
||||||
saveEquip(eq); renderSchematic();
|
saveEquip(eq); renderSchematic();
|
||||||
@ -792,14 +805,32 @@ function renderSchematic() {
|
|||||||
const t = mkSvgEl('text', {'class':'equip-label', x:eq.x+w/2, y:eq.y+h/2+5, ...(col?{fill:col}:{})});
|
const t = mkSvgEl('text', {'class':'equip-label', x:eq.x+w/2, y:eq.y+h/2+5, ...(col?{fill:col}:{})});
|
||||||
t.textContent = eq.label; g.appendChild(t);
|
t.textContent = eq.label; g.appendChild(t);
|
||||||
|
|
||||||
|
// Static OFF ring for linked elements
|
||||||
|
if (eq.link) {
|
||||||
|
g.appendChild(mkSvgEl('rect', {'class':'static-off-ring', id:'off-'+eq.id,
|
||||||
|
x:eq.x-1, y:eq.y-1, width:w+2, height:h+2, rx:'7', fill:'none'}));
|
||||||
|
}
|
||||||
|
// Animation overlay (only visible when ON)
|
||||||
|
const ac = col||'var(--green)';
|
||||||
if (eq.anim === 'conveyor') {
|
if (eq.anim === 'conveyor') {
|
||||||
g.appendChild(mkSvgEl('line', {'class':'anim-overlay conveyor-anim', id:'sim-'+eq.id,
|
g.appendChild(mkSvgEl('line', {'class':'anim-overlay conveyor-anim', id:'sim-'+eq.id,
|
||||||
x1:eq.x+10, y1:eq.y+h-5, x2:eq.x+w-10, y2:eq.y+h-5,
|
x1:eq.x+10, y1:eq.y+h-5, x2:eq.x+w-10, y2:eq.y+h-5,
|
||||||
stroke: col||'var(--green)', 'stroke-width':'3', 'stroke-dasharray':'10 10'}));
|
stroke:ac, 'stroke-width':'3', 'stroke-dasharray':'10 10'}));
|
||||||
} else if (eq.anim === 'pulse') {
|
} else if (eq.anim === 'pulse') {
|
||||||
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay pulse-anim', id:'sim-'+eq.id,
|
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay pulse-anim', id:'sim-'+eq.id,
|
||||||
x:eq.x, y:eq.y, width:w, height:h, rx:'6',
|
x:eq.x, y:eq.y, width:w, height:h, rx:'6', fill:'none', stroke:ac, 'stroke-width':'3'}));
|
||||||
fill:'none', stroke: col||'var(--green)', 'stroke-width':'3'}));
|
} else if (eq.anim === 'blink') {
|
||||||
|
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay blink-anim', id:'sim-'+eq.id,
|
||||||
|
x:eq.x, y:eq.y, width:w, height:h, rx:'6', fill:ac, 'fill-opacity':'0.3'}));
|
||||||
|
} else if (eq.anim === 'vibrate') {
|
||||||
|
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay vibrate-anim', id:'sim-'+eq.id,
|
||||||
|
x:eq.x+2, y:eq.y+2, width:w-4, height:h-4, rx:'4', fill:'none', stroke:ac, 'stroke-width':'2'}));
|
||||||
|
} else if (eq.anim === 'glow') {
|
||||||
|
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay glow-anim', id:'sim-'+eq.id,
|
||||||
|
x:eq.x-2, y:eq.y-2, width:w+4, height:h+4, rx:'8', fill:'none', stroke:ac, 'stroke-width':'3', color:ac}));
|
||||||
|
} else if (eq.anim === 'flow') {
|
||||||
|
g.appendChild(mkSvgEl('rect', {'class':'anim-overlay flow-anim', id:'sim-'+eq.id,
|
||||||
|
x:eq.x, y:eq.y, width:w, height:h, rx:'6', fill:'none', stroke:ac, 'stroke-width':'2', 'stroke-dasharray':'8 8'}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── CIRCLE ──
|
// ── CIRCLE ──
|
||||||
@ -811,6 +842,12 @@ function renderSchematic() {
|
|||||||
const t = mkSvgEl('text', {'class':'equip-label', x:eq.x, y:eq.y+4, 'font-size':r<15?'8':'10', ...(col?{fill:col}:{})});
|
const t = mkSvgEl('text', {'class':'equip-label', x:eq.x, y:eq.y+4, 'font-size':r<15?'8':'10', ...(col?{fill:col}:{})});
|
||||||
t.textContent = eq.label; g.appendChild(t);
|
t.textContent = eq.label; g.appendChild(t);
|
||||||
|
|
||||||
|
// Static OFF ring
|
||||||
|
if (eq.link) {
|
||||||
|
g.appendChild(mkSvgEl('circle', {'class':'static-off-ring', id:'off-'+eq.id,
|
||||||
|
cx:eq.x, cy:eq.y, r:r+3}));
|
||||||
|
}
|
||||||
|
const cc = col||'var(--green)';
|
||||||
if (eq.anim === 'burner') {
|
if (eq.anim === 'burner') {
|
||||||
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay burner-glow', id:'sim-'+eq.id,
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay burner-glow', id:'sim-'+eq.id,
|
||||||
cx:eq.x, cy:eq.y, r:r+4, fill:'none', stroke:col||'var(--amber)', 'stroke-width':'4'}));
|
cx:eq.x, cy:eq.y, r:r+4, fill:'none', stroke:col||'var(--amber)', 'stroke-width':'4'}));
|
||||||
@ -820,7 +857,19 @@ function renderSchematic() {
|
|||||||
g.appendChild(ag);
|
g.appendChild(ag);
|
||||||
} else if (eq.anim === 'pulse') {
|
} else if (eq.anim === 'pulse') {
|
||||||
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay pulse-anim', id:'sim-'+eq.id,
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay pulse-anim', id:'sim-'+eq.id,
|
||||||
cx:eq.x, cy:eq.y, r:r+3, fill:'none', stroke:col||'var(--green)', 'stroke-width':'2'}));
|
cx:eq.x, cy:eq.y, r:r+3, fill:'none', stroke:cc, 'stroke-width':'2'}));
|
||||||
|
} else if (eq.anim === 'blink') {
|
||||||
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay blink-anim', id:'sim-'+eq.id,
|
||||||
|
cx:eq.x, cy:eq.y, r:r, fill:cc, 'fill-opacity':'0.3'}));
|
||||||
|
} else if (eq.anim === 'vibrate') {
|
||||||
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay vibrate-anim', id:'sim-'+eq.id,
|
||||||
|
cx:eq.x, cy:eq.y, r:r-2, fill:'none', stroke:cc, 'stroke-width':'2'}));
|
||||||
|
} else if (eq.anim === 'glow') {
|
||||||
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay glow-anim', id:'sim-'+eq.id,
|
||||||
|
cx:eq.x, cy:eq.y, r:r+4, fill:'none', stroke:cc, 'stroke-width':'3', color:cc}));
|
||||||
|
} else if (eq.anim === 'flow') {
|
||||||
|
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay flow-anim', id:'sim-'+eq.id,
|
||||||
|
cx:eq.x, cy:eq.y, r:r, fill:'none', stroke:cc, 'stroke-width':'2', 'stroke-dasharray':'6 6'}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── LABEL ──
|
// ── LABEL ──
|
||||||
@ -949,11 +998,34 @@ function renameEquip(eq) {
|
|||||||
function updateSchematicAnimations() {
|
function updateSchematicAnimations() {
|
||||||
const equip = getEquipment();
|
const equip = getEquipment();
|
||||||
equip.forEach(eq => {
|
equip.forEach(eq => {
|
||||||
if (!eq.link || !eq.anim) return;
|
if (!eq.link) return;
|
||||||
const el = document.getElementById('sim-' + eq.id);
|
|
||||||
if (!el) return;
|
|
||||||
const s = simState[eq.link];
|
const s = simState[eq.link];
|
||||||
el.classList.toggle('active', s && s.on);
|
const isOn = s && s.on;
|
||||||
|
|
||||||
|
// Update the animation overlay
|
||||||
|
const animEl = document.getElementById('sim-' + eq.id);
|
||||||
|
if (animEl) animEl.classList.toggle('active', isOn);
|
||||||
|
|
||||||
|
// Update the main shape — show red when OFF, normal when ON
|
||||||
|
const g = document.querySelector(`[data-eq-id="${eq.id}"]`);
|
||||||
|
if (!g) return;
|
||||||
|
const shape = g.querySelector('.equip');
|
||||||
|
if (!shape) return;
|
||||||
|
|
||||||
|
// Static OFF indicator (red tint)
|
||||||
|
const offRing = document.getElementById('off-' + eq.id);
|
||||||
|
|
||||||
|
if (!simEditMode) {
|
||||||
|
if (isOn) {
|
||||||
|
shape.classList.remove('equip-off');
|
||||||
|
shape.classList.add('equip-on');
|
||||||
|
if (offRing) offRing.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
shape.classList.add('equip-off');
|
||||||
|
shape.classList.remove('equip-on');
|
||||||
|
if (offRing) offRing.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user