(() => { const { Bodies, Body, Vertices } = Matter; const scenes = (window.PhysilinksSceneDefs = window.PhysilinksSceneDefs || []); scenes.push({ id: "low-g-terraces", name: "Low-G terraces", config: { gravity: 0.65, spawnIntervalMs: 600, minChain: 3, palette: ["#fb7185", "#fbbf24", "#34d399", "#38bdf8"], ballRadius: 22, winCondition: { type: "score", target: 15000, onWin: { shoveBalls: true }, }, link: { stiffness: 0.6, lengthScale: 1, damping: 0.01, lineWidth: 4, rope: false, renderType: "spring", maxLengthMultiplier: 6, }, }, createBodies: (w, h) => { const floorHeight = Math.max(70, h * 0.12); const wallThickness = Math.max(32, w * 0.05); const wallHeight = h * 1.8; const cogRadius = Math.max(40, Math.min(w, h) * 0.085); const cogRadiusSmall = Math.max(30, Math.min(w, h) * 0.065); const bumperRadius = Math.max(20, Math.min(w, h) * 0.05); const stickyWidth = Math.max(90, w * 0.12); const stickyHeight = Math.max(14, h * 0.02); const stickyAmplitude = w * 0.06; const bumperAmplitude = h * 0.08; const makeGear = (cx, cy, outerRadius, teeth, color, rotSpeed) => { const coreRadius = outerRadius * 1.5; const toothLength = outerRadius * 0.5; const toothWidth = Math.max(outerRadius * 0.14, 10); const parts = [ Bodies.circle(cx, cy, coreRadius, { isStatic: true, render: { fillStyle: color, strokeStyle: color }, }), ]; const step = (Math.PI * 2) / teeth; for (let i = 0; i < teeth; i += 1) { const angle = step * i; const tx = cx + Math.cos(angle) * (coreRadius + toothLength / 2); const ty = cy + Math.sin(angle) * (coreRadius + toothLength / 2); parts.push( Bodies.rectangle(tx, ty, toothWidth, toothLength, { isStatic: true, angle, render: { fillStyle: color, strokeStyle: color }, }), ); } const gear = Body.create({ isStatic: true, parts, plugin: { rotSpeed }, }); return gear; }; const makeStar = (x, y, size, color, angle, rotSpeed) => { const base = Vertices.fromPath( "50 0 63 38 100 38 69 59 82 100 50 75 18 100 31 59 0 38 37 38", ); const scale = (size || 50) / 50; const pts = base.map((p) => ({ x: (p.x - 50) * scale, y: (p.y - 50) * scale, })); return Bodies.fromVertices( x, y, [pts], { isStatic: true, angle, render: { fillStyle: color, strokeStyle: color }, plugin: { rotSpeed }, }, true, ); }; return [ Bodies.rectangle( w / 2, h + floorHeight / 2, w + wallThickness * 2, floorHeight, { isStatic: true, restitution: 0.9, render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, }, ), Bodies.rectangle(-wallThickness / 2, h / 2, wallThickness, wallHeight, { isStatic: true, render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, }), Bodies.rectangle( w + wallThickness / 2, h / 2, wallThickness, wallHeight, { isStatic: true, render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" }, }, ), // Rotating cogs with teeth makeGear(w * 0.32, h * 0.38, cogRadius, 10, "#f97316", 1.2), makeGear(w * 0.64, h * 0.5, cogRadiusSmall, 12, "#a855f7", -1.6), makeGear(w * 0.5, h * 0.32, cogRadius * 0.85, 14, "#fb7185", 1.9), // Oscillating bumpers Bodies.circle(w * 0.2, h * 0.46, bumperRadius, { isStatic: true, restitution: 1.08, friction: 0.01, render: { fillStyle: "#14b8a6", strokeStyle: "#14b8a6" }, plugin: { oscillate: { axis: "y", amplitude: bumperAmplitude, speed: 1.4 }, }, }), Bodies.circle(w * 0.5, h * 0.62, bumperRadius * 0.9, { isStatic: true, restitution: 1.08, friction: 0.01, render: { fillStyle: "#fbbf24", strokeStyle: "#fbbf24" }, plugin: { oscillate: { axis: "x", amplitude: stickyAmplitude, speed: 1.1 }, }, }), Bodies.circle(w * 0.8, h * 0.38, bumperRadius * 1.05, { isStatic: true, restitution: 1.08, friction: 0.01, render: { fillStyle: "#38bdf8", strokeStyle: "#38bdf8" }, plugin: { oscillate: { axis: "y", amplitude: bumperAmplitude * 0.7, speed: 1.8, }, }, }), // Sticky moving pads Bodies.rectangle(w * 0.32, h * 0.72, stickyWidth, stickyHeight, { isStatic: true, angle: -0.08, friction: 1.3, render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" }, plugin: { oscillate: { axis: "x", amplitude: stickyAmplitude, speed: 0.9 }, }, }), Bodies.rectangle(w * 0.72, h * 0.7, stickyWidth * 0.9, stickyHeight, { isStatic: true, angle: 0.06, friction: 1.3, render: { fillStyle: "#f472b6", strokeStyle: "#f472b6" }, plugin: { oscillate: { axis: "x", amplitude: stickyAmplitude * 0.8, speed: 1.3, }, }, }), // Star obstacles makeStar(w * 0.18, h * 0.28, bumperRadius * 1.1, "#22c55e", 0.2, -0.6), makeStar( w * 0.86, h * 0.62, bumperRadius * 1.15, "#c084fc", -0.25, 0.9, ), ]; }, }); })();