diff --git a/templates/editor.html b/templates/editor.html
index f578cc0..e414b50 100644
--- a/templates/editor.html
+++ b/templates/editor.html
@@ -107,7 +107,7 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
.schematic .anim-overlay.active{opacity:1}
/* Animations */
-@keyframes spin{100%{transform-origin:center;transform:rotate(360deg)}}
+@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
@keyframes pulse-glow{0%,100%{opacity:0.4}50%{opacity:1}}
@keyframes scroll-belt{100%{stroke-dashoffset:-20}}
@keyframes fall-particles{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(30px)}}
@@ -622,10 +622,10 @@ const defaultEquipment = [
{id:'eq_agit1', label:'A1', shape:'circle', x:440, y:170, r:12, anim:'agitator', link:'motor_3'},
{id:'eq_agit2', label:'A2', shape:'circle', x:510, y:170, r:12, anim:'agitator', link:'motor_4'},
{id:'eq_spinner', label:'Spin', shape:'circle', x:475, y:155, r:14, anim:'fan', link:'motor_2'},
- {id:'eq_discharge',label:'Discharge', shape:'rect', x:580, y:160, w:80, h:60, anim:null, link:null},
- {id:'eq_mill', label:'Mill', shape:'rect', x:580, y:250, w:70, h:50, anim:null, link:'output_4'},
- {id:'eq_shaker', label:'Shaker Sep.',shape:'rect', x:670, y:250, w:90, h:50, anim:null, link:'output_5'},
- {id:'eq_brush', label:'Brush', shape:'rect', x:670, y:160, w:60, h:40, anim:null, link:'output_1'},
+ {id:'eq_discharge',label:'Discharge', shape:'rect', x:580, y:160, w:80, h:60, anim:'flow', link:'output_0'},
+ {id:'eq_mill', label:'Mill', shape:'rect', x:580, y:250, w:70, h:50, anim:'vibrate', link:'output_4'},
+ {id:'eq_shaker', label:'Shaker Sep.',shape:'rect', x:670, y:250, w:90, h:50, anim:'vibrate', link:'output_5'},
+ {id:'eq_brush', label:'Brush', shape:'rect', x:670, y:160, w:60, h:40, anim:'glow', link:'output_1'},
{id:'eq_in', label:'IN \u25B6', shape:'label', x:60, y:148, anim:null, link:null, color:'#4a5670'},
{id:'eq_out', label:'\u25B6 OUT', shape:'label', x:740, y:320, anim:null, link:null, color:'#4a5670'},
{id:'eq_t_heat', label:'T1', shape:'circle', x:230, y:180, r:10, anim:'pulse', link:'temp_0', color:'#ff4444'},
@@ -851,9 +851,54 @@ function renderSchematic() {
if (eq.anim === 'burner') {
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'}));
- } else if (eq.anim === 'fan' || eq.anim === 'agitator') {
- const ag = mkSvgEl('g', {'class':`anim-overlay ${eq.anim==='fan'?'fan-anim':'agitator-anim'}`, id:'sim-'+eq.id, 'transform-origin':`${eq.x} ${eq.y}`});
- ag.innerHTML = ``;
+ } else if (eq.anim === 'fan') {
+ // Fan: 4 curved blades spinning inside the circle
+ const ag = mkSvgEl('g', {'class':'anim-overlay', id:'sim-'+eq.id});
+ const inner = mkSvgEl('g', {'class':'fan-anim', style:`transform-origin:0 0`});
+ const fc = col||'var(--blue)';
+ const b = r * 0.8;
+ // 4 fan blades as arcs
+ for (let i = 0; i < 4; i++) {
+ const a = (i * 90) * Math.PI / 180;
+ const x1 = Math.cos(a) * b * 0.2, y1 = Math.sin(a) * b * 0.2;
+ const x2 = Math.cos(a) * b, y2 = Math.sin(a) * b;
+ const cx1 = Math.cos(a + 0.6) * b * 0.6, cy1 = Math.sin(a + 0.6) * b * 0.6;
+ inner.appendChild(mkSvgEl('path', {
+ d: `M ${x1} ${y1} Q ${cx1} ${cy1} ${x2} ${y2}`,
+ fill:'none', stroke:fc, 'stroke-width':'3', 'stroke-linecap':'round'
+ }));
+ }
+ // Center dot
+ inner.appendChild(mkSvgEl('circle', {cx:0, cy:0, r:r*0.15, fill:fc}));
+ ag.setAttribute('transform', `translate(${eq.x},${eq.y})`);
+ ag.appendChild(inner);
+ g.appendChild(ag);
+ } else if (eq.anim === 'agitator') {
+ // Agitator: 3 flat paddles spinning slower
+ const ag = mkSvgEl('g', {'class':'anim-overlay', id:'sim-'+eq.id});
+ const inner = mkSvgEl('g', {'class':'agitator-anim', style:`transform-origin:0 0`});
+ const ac = col||'var(--amber)';
+ const pr = r * 0.75;
+ // 3 rectangular paddles
+ for (let i = 0; i < 3; i++) {
+ const a = (i * 120) * Math.PI / 180;
+ const x1 = Math.cos(a) * pr, y1 = Math.sin(a) * pr;
+ // Paddle: thick line from center to edge
+ inner.appendChild(mkSvgEl('line', {
+ x1: 0, y1: 0, x2: x1, y2: y1,
+ stroke:ac, 'stroke-width':'4', 'stroke-linecap':'round'
+ }));
+ // Paddle head: small rect at end
+ inner.appendChild(mkSvgEl('rect', {
+ x: x1 - 3, y: y1 - 5, width: 6, height: 10,
+ fill:ac, rx:'2',
+ transform: `rotate(${i*120} ${x1} ${y1})`
+ }));
+ }
+ // Center hub
+ inner.appendChild(mkSvgEl('circle', {cx:0, cy:0, r:r*0.2, fill:ac, stroke:ac, 'stroke-width':'1'}));
+ ag.setAttribute('transform', `translate(${eq.x},${eq.y})`);
+ ag.appendChild(inner);
g.appendChild(ag);
} else if (eq.anim === 'pulse') {
g.appendChild(mkSvgEl('circle', {'class':'anim-overlay pulse-anim', id:'sim-'+eq.id,