Initial commit
This commit is contained in:
@@ -0,0 +1,545 @@
|
||||
// mechanical.js — Mechanical/industrial theme for procedural background system
|
||||
// 8 variants: gears, circuits, factory, robotFactory, conveyorBelts, pipes, steamPunk, techLab
|
||||
|
||||
(function(engine) {
|
||||
if (!engine) return;
|
||||
var D = engine.Draw, H = engine.helpers;
|
||||
|
||||
// Helper: draw a gear shape with teeth
|
||||
function drawGear(ctx, cx, cy, innerR, outerR, teeth, rotation, color) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
for (var i = 0; i < teeth * 2; i++) {
|
||||
var angle = rotation + (i / (teeth * 2)) * Math.PI * 2;
|
||||
var r = i % 2 === 0 ? outerR : innerR;
|
||||
var x = cx + Math.cos(angle) * r;
|
||||
var y = cy + Math.sin(angle) * r;
|
||||
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
// Center hole
|
||||
ctx.fillStyle = H.darken(color, 0.5);
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, innerR * 0.35, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Helper: draw a rivet
|
||||
function drawRivet(ctx, x, y, r, color) {
|
||||
var g = D.radialGradient(ctx, x - r * 0.3, y - r * 0.3, r, [[0, H.lighten(color, 0.4)], [0.6, color], [1, H.darken(color, 0.3)]]);
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, r, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Helper: draw a pipe segment
|
||||
function drawPipe(ctx, x1, y1, x2, y2, width, color) {
|
||||
var angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
var perpX = Math.sin(angle) * width / 2;
|
||||
var perpY = -Math.cos(angle) * width / 2;
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1 + perpX, y1 + perpY);
|
||||
ctx.lineTo(x2 + perpX, y2 + perpY);
|
||||
ctx.lineTo(x2 - perpX, y2 - perpY);
|
||||
ctx.lineTo(x1 - perpX, y1 - perpY);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
// Highlight stripe
|
||||
ctx.fillStyle = H.rgba(H.lighten(color, 0.3), 0.4);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1 + perpX * 0.6, y1 + perpY * 0.6);
|
||||
ctx.lineTo(x2 + perpX * 0.6, y2 + perpY * 0.6);
|
||||
ctx.lineTo(x2 + perpX * 0.2, y2 + perpY * 0.2);
|
||||
ctx.lineTo(x1 + perpX * 0.2, y1 + perpY * 0.2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Helper: draw a gauge
|
||||
function drawGauge(ctx, cx, cy, r, needleAngle, color, rimColor) {
|
||||
// Rim
|
||||
ctx.strokeStyle = rimColor;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
// Face
|
||||
ctx.fillStyle = H.darken(color, 0.6);
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r - 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
// Tick marks
|
||||
ctx.strokeStyle = H.rgba(rimColor, 0.5);
|
||||
ctx.lineWidth = 1;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var a = (i / 8) * Math.PI * 2 - Math.PI / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx + Math.cos(a) * (r * 0.7), cy + Math.sin(a) * (r * 0.7));
|
||||
ctx.lineTo(cx + Math.cos(a) * (r * 0.9), cy + Math.sin(a) * (r * 0.9));
|
||||
ctx.stroke();
|
||||
}
|
||||
// Needle
|
||||
ctx.strokeStyle = '#CC3333';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cy);
|
||||
ctx.lineTo(cx + Math.cos(needleAngle) * r * 0.75, cy + Math.sin(needleAngle) * r * 0.75);
|
||||
ctx.stroke();
|
||||
// Center dot
|
||||
D.circle(ctx, cx, cy, 3, rimColor);
|
||||
}
|
||||
|
||||
engine.registerTheme('mechanical', {
|
||||
|
||||
gears: {
|
||||
meta: { id: 'mechanical_gears', mood: 'focused', colors: ['#1A1A24', '#2A2A38', '#7A7A88', '#C08040'], tags: ['industrial', 'moving', 'metallic'], description: 'Interlocking gears turning against a dark steel backdrop', compatibleMusicMoods: ['ambient', 'electronic', 'tense'], recommendedFor: ['puzzle', 'physics', 'rpg'] },
|
||||
colors: ['#1A1A24', '#2A2A38', '#7A7A88', '#C08040'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1], c[0]], 90);
|
||||
// Subtle metal texture via scanlines
|
||||
Draw.scanlines(ctx, w, h, 3, H.rgba(c[2], 0.03));
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(100);
|
||||
// Draw interlocking gears at various sizes
|
||||
var gearData = [];
|
||||
for (var i = 0; i < 9; i++) {
|
||||
gearData.push({
|
||||
x: rng() * w, y: rng() * h,
|
||||
inner: 15 + rng() * 40, outer: 30 + rng() * 55,
|
||||
teeth: 8 + Math.floor(rng() * 10),
|
||||
speed: (rng() - 0.5) * 0.8, shade: rng()
|
||||
});
|
||||
}
|
||||
for (var i = 0; i < gearData.length; i++) {
|
||||
var g = gearData[i];
|
||||
var col = H.blendColor(c[2], c[3], g.shade * 0.5);
|
||||
drawGear(ctx, g.x, g.y, g.inner, g.outer, g.teeth, t * g.speed, col);
|
||||
}
|
||||
// Small accent gears in corners
|
||||
drawGear(ctx, w * 0.08, h * 0.08, 10, 18, 6, -t * 0.6, H.rgba(c[3], 0.6));
|
||||
drawGear(ctx, w * 0.92, h * 0.92, 12, 22, 7, t * 0.5, H.rgba(c[3], 0.6));
|
||||
// Rivets along edges
|
||||
for (var i = 0; i < 12; i++) {
|
||||
drawRivet(ctx, 15 + i * (w - 30) / 11, 12, 3, c[2]);
|
||||
drawRivet(ctx, 15 + i * (w - 30) / 11, h - 12, 3, c[2]);
|
||||
}
|
||||
},
|
||||
particles: { type: 'dust', count: 20, color: '#998877' }
|
||||
},
|
||||
|
||||
circuits: {
|
||||
meta: { id: 'mechanical_circuits', mood: 'focused', colors: ['#0A0F14', '#112222', '#00CC88', '#33FFAA'], tags: ['digital', 'tech', 'grid'], description: 'Circuit board traces with glowing data paths', compatibleMusicMoods: ['electronic', 'ambient', 'tense'], recommendedFor: ['puzzle', 'trivia', 'physics'] },
|
||||
colors: ['#0A0F14', '#112222', '#00CC88', '#33FFAA'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
||||
Draw.grid(ctx, w, h, 40, H.rgba(c[2], 0.06), 0.5);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(150);
|
||||
ctx.lineWidth = 2;
|
||||
// Draw circuit traces
|
||||
for (var i = 0; i < 20; i++) {
|
||||
var sx = rng() * w, sy = rng() * h;
|
||||
var segments = 3 + Math.floor(rng() * 5);
|
||||
var pulse = 0.3 + Math.sin(t * 1.5 + i * 0.7) * 0.25;
|
||||
ctx.strokeStyle = H.rgba(c[2], 0.15 + pulse * 0.3);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx, sy);
|
||||
for (var j = 0; j < segments; j++) {
|
||||
var dir = Math.floor(rng() * 2); // 0=horizontal, 1=vertical
|
||||
var len = 20 + rng() * 80;
|
||||
if (dir === 0) sx += (rng() > 0.5 ? 1 : -1) * len;
|
||||
else sy += (rng() > 0.5 ? 1 : -1) * len;
|
||||
sx = H.clamp(sx, 10, w - 10);
|
||||
sy = H.clamp(sy, 10, h - 10);
|
||||
ctx.lineTo(sx, sy);
|
||||
}
|
||||
ctx.stroke();
|
||||
// Node at end
|
||||
D.circle(ctx, sx, sy, 3, H.rgba(c[3], 0.4 + pulse * 0.4));
|
||||
}
|
||||
// Chip packages
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var cx = rng() * w * 0.8 + w * 0.1;
|
||||
var cy = rng() * h * 0.8 + h * 0.1;
|
||||
var cw = 20 + rng() * 20, ch = 15 + rng() * 15;
|
||||
Draw.rect(ctx, cx - cw / 2, cy - ch / 2, cw, ch, H.rgba(c[1], 0.8));
|
||||
ctx.strokeStyle = H.rgba(c[2], 0.3);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(cx - cw / 2, cy - ch / 2, cw, ch);
|
||||
// Pins
|
||||
for (var p = 0; p < 4; p++) {
|
||||
Draw.rect(ctx, cx - cw / 2 - 4, cy - ch / 2 + 3 + p * (ch / 4), 4, 2, H.rgba(c[2], 0.3));
|
||||
Draw.rect(ctx, cx + cw / 2, cy - ch / 2 + 3 + p * (ch / 4), 4, 2, H.rgba(c[2], 0.3));
|
||||
}
|
||||
}
|
||||
// Data pulses traveling along traces
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var px = (rng() * w + t * 60 * (rng() > 0.5 ? 1 : -1)) % w;
|
||||
var py = Math.round(rng() * h / 40) * 40;
|
||||
D.glow(ctx, px, py, 6, H.rgba(c[3], 0.5 + Math.sin(t * 3 + i) * 0.3));
|
||||
}
|
||||
},
|
||||
particles: { type: 'sparkles', count: 25, color: '#33FFAA' }
|
||||
},
|
||||
|
||||
factory: {
|
||||
meta: { id: 'mechanical_factory', mood: 'busy', colors: ['#1A1510', '#2A2520', '#8B7355', '#DDAA44'], tags: ['industrial', 'gritty', 'warm'], description: 'Factory floor with smokestacks and warning stripes', compatibleMusicMoods: ['tense', 'electronic', 'action'], recommendedFor: ['action', 'physics', 'puzzle'] },
|
||||
colors: ['#1A1510', '#2A2520', '#8B7355', '#DDAA44'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1], H.darken(c[0], 0.3)], 90);
|
||||
// Floor
|
||||
Draw.rect(ctx, 0, h * 0.78, w, h * 0.22, H.darken(c[2], 0.5));
|
||||
// Floor grid
|
||||
Draw.grid(ctx, w, h * 0.22, 30, H.rgba(c[2], 0.08), 0.5);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(200);
|
||||
// Smokestacks in back
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var sx = w * 0.2 + i * w * 0.3;
|
||||
var sw = 25 + rng() * 15;
|
||||
var sh = h * 0.35 + rng() * h * 0.1;
|
||||
Draw.rect(ctx, sx - sw / 2, h * 0.78 - sh, sw, sh, H.darken(c[2], 0.3));
|
||||
// Warning stripes
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(sx - sw / 2, h * 0.78 - sh, sw, 12);
|
||||
ctx.clip();
|
||||
for (var s = -2; s < sw / 8 + 2; s++) {
|
||||
Draw.rect(ctx, sx - sw / 2 + s * 16, h * 0.78 - sh, 8, 12, c[3]);
|
||||
}
|
||||
ctx.restore();
|
||||
// Smoke puffs
|
||||
for (var j = 0; j < 3; j++) {
|
||||
var smokeY = h * 0.78 - sh - 15 - j * 25;
|
||||
var drift = Math.sin(t * 0.5 + i + j) * 10;
|
||||
var alpha = 0.08 - j * 0.02;
|
||||
ctx.fillStyle = H.rgba('#888888', alpha);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(sx + drift, smokeY, 15 + j * 10, 8 + j * 5, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
// Catwalk
|
||||
Draw.rect(ctx, 0, h * 0.5, w, 4, c[2]);
|
||||
ctx.strokeStyle = H.rgba(c[2], 0.4);
|
||||
ctx.lineWidth = 1;
|
||||
for (var i = 0; i < w; i += 15) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(i, h * 0.5 + 4);
|
||||
ctx.lineTo(i + 7, h * 0.5 + 18);
|
||||
ctx.lineTo(i + 15, h * 0.5 + 4);
|
||||
ctx.stroke();
|
||||
}
|
||||
Draw.rect(ctx, 0, h * 0.5 + 18, w, 2, H.rgba(c[2], 0.3));
|
||||
// Warning light
|
||||
var blink = Math.sin(t * 4) > 0 ? 0.8 : 0.1;
|
||||
D.glow(ctx, w * 0.9, h * 0.15, 25, H.rgba('#FF4400', blink * 0.4));
|
||||
D.circle(ctx, w * 0.9, h * 0.15, 6, H.rgba('#FF4400', blink));
|
||||
// Rivets on floor seam
|
||||
for (var i = 0; i < 16; i++) {
|
||||
drawRivet(ctx, w * 0.05 + i * (w * 0.9 / 15), h * 0.78, 2.5, c[2]);
|
||||
}
|
||||
},
|
||||
particles: { type: 'dust', count: 30, color: '#AA9977' }
|
||||
},
|
||||
|
||||
robotFactory: {
|
||||
meta: { id: 'mechanical_robotFactory', mood: 'playful', colors: ['#151520', '#222238', '#5588CC', '#FFAA33'], tags: ['robots', 'tech', 'fun'], description: 'Robot assembly line with mechanical arms and sparks', compatibleMusicMoods: ['electronic', 'energetic', 'playful'], recommendedFor: ['action', 'puzzle', 'creative'] },
|
||||
colors: ['#151520', '#222238', '#5588CC', '#FFAA33'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
||||
// Floor
|
||||
Draw.rect(ctx, 0, h * 0.82, w, h * 0.18, H.darken(c[1], 0.3));
|
||||
Draw.grid(ctx, w, h, 50, H.rgba(c[2], 0.04), 0.5);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(250);
|
||||
// Assembly arms from ceiling
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var ax = w * 0.15 + i * w * 0.23;
|
||||
var armAngle = Math.sin(t * 0.7 + i * 1.5) * 0.4;
|
||||
var armLen = h * 0.25;
|
||||
var elbowX = ax + Math.sin(armAngle) * armLen;
|
||||
var elbowY = armLen * Math.cos(armAngle);
|
||||
// Arm segment
|
||||
ctx.strokeStyle = c[2];
|
||||
ctx.lineWidth = 8;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax, 0);
|
||||
ctx.lineTo(elbowX, elbowY);
|
||||
ctx.stroke();
|
||||
// Joint
|
||||
D.circle(ctx, elbowX, elbowY, 6, H.lighten(c[2], 0.2));
|
||||
D.circle(ctx, ax, 0, 8, c[2]);
|
||||
// Claw tip
|
||||
var clawAngle = armAngle + Math.sin(t * 1.5 + i) * 0.3;
|
||||
var tipX = elbowX + Math.sin(clawAngle) * 20;
|
||||
var tipY = elbowY + Math.cos(clawAngle) * 20;
|
||||
ctx.lineWidth = 4;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(elbowX, elbowY);
|
||||
ctx.lineTo(tipX, tipY);
|
||||
ctx.stroke();
|
||||
// Sparks at claw
|
||||
if (Math.sin(t * 5 + i * 2.3) > 0.6) {
|
||||
D.glow(ctx, tipX, tipY, 10, H.rgba(c[3], 0.6));
|
||||
}
|
||||
}
|
||||
// Robot bodies on conveyor (silhouettes)
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var rx = (w * 0.1 + i * w * 0.35 + t * 15) % (w + 60) - 30;
|
||||
var ry = h * 0.82;
|
||||
// Body
|
||||
Draw.rect(ctx, rx - 12, ry - 35, 24, 25, c[2]);
|
||||
// Head
|
||||
Draw.rect(ctx, rx - 8, ry - 45, 16, 12, H.lighten(c[2], 0.15));
|
||||
// Eyes
|
||||
var eyeGlow = 0.4 + Math.sin(t * 3 + i) * 0.3;
|
||||
D.circle(ctx, rx - 4, ry - 40, 2, H.rgba(c[3], eyeGlow));
|
||||
D.circle(ctx, rx + 4, ry - 40, 2, H.rgba(c[3], eyeGlow));
|
||||
// Legs
|
||||
Draw.rect(ctx, rx - 10, ry - 10, 6, 10, H.darken(c[2], 0.2));
|
||||
Draw.rect(ctx, rx + 4, ry - 10, 6, 10, H.darken(c[2], 0.2));
|
||||
}
|
||||
// Conveyor belt
|
||||
Draw.rect(ctx, 0, h * 0.82, w, 5, H.darken(c[2], 0.4));
|
||||
var beltOffset = (t * 30) % 20;
|
||||
for (var i = -1; i < w / 20 + 1; i++) {
|
||||
Draw.rect(ctx, i * 20 + beltOffset, h * 0.82, 2, 5, H.rgba(c[2], 0.3));
|
||||
}
|
||||
},
|
||||
particles: { type: 'sparkles', count: 20, color: '#FFAA33' }
|
||||
},
|
||||
|
||||
conveyorBelts: {
|
||||
meta: { id: 'mechanical_conveyorBelts', mood: 'busy', colors: ['#18181F', '#28283A', '#666680', '#DD8822'], tags: ['movement', 'industrial', 'repetitive'], description: 'Layered conveyor belts moving crates and parts', compatibleMusicMoods: ['electronic', 'ambient', 'action'], recommendedFor: ['puzzle', 'action', 'physics'] },
|
||||
colors: ['#18181F', '#28283A', '#666680', '#DD8822'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
||||
Draw.scanlines(ctx, w, h, 4, H.rgba(c[2], 0.02));
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(300);
|
||||
// Three conveyor belt layers
|
||||
var beltYs = [h * 0.3, h * 0.55, h * 0.8];
|
||||
var beltSpeeds = [25, -35, 20];
|
||||
for (var b = 0; b < 3; b++) {
|
||||
var by = beltYs[b];
|
||||
var beltH = 8;
|
||||
// Belt surface
|
||||
Draw.rect(ctx, 0, by, w, beltH, H.darken(c[2], 0.3));
|
||||
// Belt treads
|
||||
var treadOffset = (t * beltSpeeds[b]) % 16;
|
||||
for (var i = -1; i < w / 16 + 1; i++) {
|
||||
Draw.rect(ctx, i * 16 + treadOffset, by, 2, beltH, H.rgba(c[2], 0.2));
|
||||
}
|
||||
// Support legs
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var lx = w * 0.1 + i * w * 0.2;
|
||||
Draw.rect(ctx, lx - 3, by + beltH, 6, h - by - beltH, H.darken(c[2], 0.5));
|
||||
}
|
||||
// Rollers at ends
|
||||
D.circle(ctx, 15, by + beltH / 2, beltH / 2 + 2, c[2]);
|
||||
D.circle(ctx, w - 15, by + beltH / 2, beltH / 2 + 2, c[2]);
|
||||
// Items on belt
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var itemX = (rng() * w + t * beltSpeeds[b]) % (w + 40) - 20;
|
||||
var itemW = 12 + rng() * 18;
|
||||
var itemH = 10 + rng() * 14;
|
||||
var itemCol = rng() > 0.5 ? H.blendColor(c[2], c[3], rng() * 0.6) : H.darken(c[2], rng() * 0.3);
|
||||
Draw.rect(ctx, itemX - itemW / 2, by - itemH, itemW, itemH, itemCol);
|
||||
}
|
||||
}
|
||||
// Overhead arrows
|
||||
ctx.strokeStyle = H.rgba(c[3], 0.2);
|
||||
ctx.lineWidth = 1.5;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var ax = w * 0.1 + i * w * 0.16;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax, h * 0.12);
|
||||
ctx.lineTo(ax + 10, h * 0.15);
|
||||
ctx.lineTo(ax, h * 0.18);
|
||||
ctx.stroke();
|
||||
}
|
||||
},
|
||||
particles: { type: 'dust', count: 15, color: '#887766' }
|
||||
},
|
||||
|
||||
pipes: {
|
||||
meta: { id: 'mechanical_pipes', mood: 'complex', colors: ['#121218', '#1E2A1E', '#558855', '#88BB44'], tags: ['plumbing', 'industrial', 'network'], description: 'Interconnected pipe network with valves and joints', compatibleMusicMoods: ['ambient', 'tense', 'electronic'], recommendedFor: ['puzzle', 'physics', 'rpg'] },
|
||||
colors: ['#121218', '#1E2A1E', '#558855', '#88BB44'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(350);
|
||||
// Horizontal pipes
|
||||
var pipeYs = [h * 0.15, h * 0.35, h * 0.55, h * 0.75, h * 0.9];
|
||||
for (var i = 0; i < pipeYs.length; i++) {
|
||||
var pw = 8 + rng() * 8;
|
||||
var pipeCol = H.blendColor(c[2], c[3], rng() * 0.4);
|
||||
drawPipe(ctx, 0, pipeYs[i], w, pipeYs[i], pw, pipeCol);
|
||||
// Joints/flanges
|
||||
for (var j = 0; j < 4; j++) {
|
||||
var jx = rng() * w;
|
||||
Draw.rect(ctx, jx - pw * 0.7, pipeYs[i] - pw * 0.7, pw * 1.4, pw * 1.4, H.darken(pipeCol, 0.15));
|
||||
}
|
||||
}
|
||||
// Vertical connector pipes
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var vx = rng() * w * 0.8 + w * 0.1;
|
||||
var fromY = pipeYs[Math.floor(rng() * (pipeYs.length - 1))];
|
||||
var toIdx = Math.min(Math.floor(rng() * (pipeYs.length - 1)) + 1, pipeYs.length - 1);
|
||||
var toY = pipeYs[toIdx];
|
||||
if (fromY === toY) toY = pipeYs[Math.min(toIdx + 1, pipeYs.length - 1)];
|
||||
var vpw = 6 + rng() * 5;
|
||||
drawPipe(ctx, vx, fromY, vx, toY, vpw, H.blendColor(c[2], c[3], rng() * 0.3));
|
||||
}
|
||||
// Valves (wheel shapes)
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var vx = rng() * w * 0.7 + w * 0.15;
|
||||
var vy = pipeYs[Math.floor(rng() * pipeYs.length)];
|
||||
var vr = 8 + rng() * 6;
|
||||
var spin = t * 0.3 * (rng() > 0.5 ? 1 : -1);
|
||||
ctx.strokeStyle = H.rgba(c[3], 0.7);
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(vx, vy, vr, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
for (var s = 0; s < 4; s++) {
|
||||
var sa = spin + s * Math.PI / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vx, vy);
|
||||
ctx.lineTo(vx + Math.cos(sa) * vr, vy + Math.sin(sa) * vr);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
// Dripping animation
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var dx = rng() * w;
|
||||
var dripY = pipeYs[Math.floor(rng() * pipeYs.length)] + 8;
|
||||
var dropY = dripY + ((t * 40 + rng() * 100) % 50);
|
||||
var alpha = 1 - ((t * 40 + rng() * 100) % 50) / 50;
|
||||
D.circle(ctx, dx, dropY, 2, H.rgba(c[3], alpha * 0.5));
|
||||
}
|
||||
},
|
||||
particles: { type: 'dust', count: 12, color: '#667755' }
|
||||
},
|
||||
|
||||
steamPunk: {
|
||||
meta: { id: 'mechanical_steamPunk', mood: 'adventurous', colors: ['#1A1008', '#2A1A0A', '#C08040', '#E8C060'], tags: ['brass', 'vintage', 'ornate'], description: 'Brass gears, gauges, and steam vents in warm tones', compatibleMusicMoods: ['epic', 'ambient', 'action'], recommendedFor: ['rpg', 'story', 'action'] },
|
||||
colors: ['#1A1008', '#2A1A0A', '#C08040', '#E8C060'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1], H.darken(c[0], 0.2)], 135);
|
||||
// Warm vignette
|
||||
var g = D.radialGradient(ctx, w / 2, h / 2, Math.max(w, h) * 0.7, [[0, H.rgba(c[0], 0)], [1, H.rgba(c[0], 0.5)]]);
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(400);
|
||||
// Large decorative gears
|
||||
drawGear(ctx, w * 0.2, h * 0.3, 35, 55, 12, t * 0.15, H.rgba(c[2], 0.6));
|
||||
drawGear(ctx, w * 0.35, h * 0.45, 20, 35, 8, -t * 0.2, H.rgba(c[3], 0.5));
|
||||
drawGear(ctx, w * 0.8, h * 0.25, 40, 65, 14, t * 0.12, H.rgba(c[2], 0.55));
|
||||
drawGear(ctx, w * 0.65, h * 0.7, 25, 42, 10, -t * 0.18, H.rgba(c[3], 0.5));
|
||||
// Gauges
|
||||
var needleWobble1 = -Math.PI / 4 + Math.sin(t * 0.8) * 0.5;
|
||||
drawGauge(ctx, w * 0.5, h * 0.2, 22, needleWobble1, c[0], c[2]);
|
||||
var needleWobble2 = Math.sin(t * 0.5) * 0.8;
|
||||
drawGauge(ctx, w * 0.88, h * 0.6, 18, needleWobble2, c[0], c[3]);
|
||||
// Decorative border rivets
|
||||
for (var i = 0; i < 20; i++) {
|
||||
drawRivet(ctx, w * 0.03, h * 0.05 + i * (h * 0.9 / 19), 3, c[2]);
|
||||
drawRivet(ctx, w * 0.97, h * 0.05 + i * (h * 0.9 / 19), 3, c[2]);
|
||||
}
|
||||
for (var i = 0; i < 14; i++) {
|
||||
drawRivet(ctx, w * 0.05 + i * (w * 0.9 / 13), h * 0.03, 3, c[2]);
|
||||
drawRivet(ctx, w * 0.05 + i * (w * 0.9 / 13), h * 0.97, 3, c[2]);
|
||||
}
|
||||
// Steam vents
|
||||
for (var v = 0; v < 2; v++) {
|
||||
var vx = w * 0.3 + v * w * 0.4;
|
||||
var vy = h * 0.85;
|
||||
Draw.rect(ctx, vx - 6, vy, 12, h - vy, H.darken(c[2], 0.3));
|
||||
// Steam puffs
|
||||
for (var p = 0; p < 4; p++) {
|
||||
var pAlpha = 0.06 - p * 0.012;
|
||||
var drift = Math.sin(t + v + p * 0.5) * 8;
|
||||
ctx.fillStyle = H.rgba('#CCBBAA', pAlpha);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(vx + drift, vy - 10 - p * 18, 12 + p * 8, 6 + p * 4, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
},
|
||||
particles: { type: 'dust', count: 18, color: '#C0A060' }
|
||||
},
|
||||
|
||||
techLab: {
|
||||
meta: { id: 'mechanical_techLab', mood: 'curious', colors: ['#0E0E1A', '#1A1A30', '#4455AA', '#66DDFF'], tags: ['science', 'clean', 'futuristic'], description: 'Clean tech laboratory with holographic displays and instruments', compatibleMusicMoods: ['ambient', 'electronic', 'calm'], recommendedFor: ['puzzle', 'trivia', 'creative'] },
|
||||
colors: ['#0E0E1A', '#1A1A30', '#4455AA', '#66DDFF'],
|
||||
renderBase: function(ctx, w, h, c, Draw, t) {
|
||||
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
||||
// Clean floor
|
||||
Draw.rect(ctx, 0, h * 0.85, w, h * 0.15, H.darken(c[1], 0.2));
|
||||
Draw.grid(ctx, w, h, 60, H.rgba(c[2], 0.04), 0.5);
|
||||
},
|
||||
renderMid: function(ctx, w, h, c, Draw, t) {
|
||||
var rng = H.seededRandom(450);
|
||||
// Holographic display panels
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var px = w * 0.12 + i * w * 0.32;
|
||||
var py = h * 0.15;
|
||||
var pw = w * 0.22, ph = h * 0.35;
|
||||
// Panel frame
|
||||
ctx.strokeStyle = H.rgba(c[2], 0.4);
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeRect(px, py, pw, ph);
|
||||
// Panel glow fill
|
||||
ctx.fillStyle = H.rgba(c[2], 0.05 + Math.sin(t + i) * 0.02);
|
||||
ctx.fillRect(px, py, pw, ph);
|
||||
// Data lines inside panel
|
||||
ctx.strokeStyle = H.rgba(c[3], 0.2);
|
||||
ctx.lineWidth = 1;
|
||||
for (var l = 0; l < 5; l++) {
|
||||
var ly = py + 10 + l * (ph - 20) / 5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px + 8, ly);
|
||||
for (var x = 0; x < pw - 16; x += 4) {
|
||||
var val = Math.sin(x * 0.1 + t * 2 + l * 1.3 + i) * 6;
|
||||
ctx.lineTo(px + 8 + x, ly + val);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
// Blinking status light
|
||||
var statusBlink = Math.sin(t * 2.5 + i * 1.1) > 0 ? 0.8 : 0.2;
|
||||
D.circle(ctx, px + pw - 8, py + 8, 3, H.rgba(c[3], statusBlink));
|
||||
}
|
||||
// Lab equipment silhouettes
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var ex = rng() * w * 0.8 + w * 0.1;
|
||||
var eH = 15 + rng() * 30;
|
||||
Draw.rect(ctx, ex - 8, h * 0.85 - eH, 16, eH, H.rgba(c[2], 0.3));
|
||||
D.circle(ctx, ex, h * 0.85 - eH - 5, 4, H.rgba(c[3], 0.3));
|
||||
}
|
||||
// Floating data points
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var dx = rng() * w;
|
||||
var dy = rng() * h * 0.8;
|
||||
var flicker = 0.15 + Math.sin(t * 1.8 + rng() * 10) * 0.15;
|
||||
D.circle(ctx, dx, dy, 1.5, H.rgba(c[3], flicker));
|
||||
}
|
||||
},
|
||||
particles: { type: 'sparkles', count: 25, color: '#66DDFF' }
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(window.BackgroundEngine);
|
||||
Reference in New Issue
Block a user