217 lines
10 KiB
JavaScript
217 lines
10 KiB
JavaScript
// Approved settings-panel core animations. Procedural, offline, and dependency-free.
|
|
(() => {
|
|
'use strict';
|
|
const TAU = Math.PI * 2;
|
|
function line(c, points, color, width = 1) {
|
|
c.beginPath(); points.forEach(([x, y], i) => i ? c.lineTo(x, y) : c.moveTo(x, y));
|
|
c.strokeStyle = color; c.lineWidth = width; c.stroke();
|
|
}
|
|
function ring(c, x, y, r, color, width = 1, start = 0, end = TAU) {
|
|
c.beginPath(); c.arc(x, y, r, start, end); c.strokeStyle = color; c.lineWidth = width; c.stroke();
|
|
}
|
|
function dot(c, x, y, r, color) {
|
|
c.fillStyle = color; c.beginPath(); c.arc(x, y, r, 0, TAU); c.fill();
|
|
}
|
|
function glow(c, x, y, r, color, alpha = 1) {
|
|
c.save(); c.globalAlpha = alpha;
|
|
const g = c.createRadialGradient(x, y, 0, x, y, r);
|
|
g.addColorStop(0, color); g.addColorStop(1, color + '00');
|
|
c.fillStyle = g; c.fillRect(x - r, y - r, r * 2, r * 2); c.restore();
|
|
}
|
|
function rect(c, x, y, w, h, fill, stroke) {
|
|
c.fillStyle = fill; c.fillRect(x, y, w, h);
|
|
if (stroke) { c.strokeStyle = stroke; c.lineWidth = 1; c.strokeRect(x, y, w, h); }
|
|
}
|
|
function polygon(c, r, sides, color, rotation = 0, width = 1) {
|
|
const p = Array.from({ length: sides + 1 }, (_, i) => [Math.cos(i * TAU / sides + rotation) * r, Math.sin(i * TAU / sides + rotation) * r]);
|
|
line(c, p, color, width);
|
|
}
|
|
function base(c) {
|
|
for (let x = -160; x <= 160; x += 20) for (let y = -120; y <= 120; y += 20) dot(c, x, y, 0.6, '#253545');
|
|
line(c, [[-158, -93], [-158, -110], [-141, -110]], '#344556');
|
|
line(c, [[158, 93], [158, 110], [141, 110]], '#344556');
|
|
}
|
|
|
|
const renderers = {
|
|
starfleet(c, t, load) {
|
|
const beat = Math.pow((Math.sin(t * 3.4) + 1) / 2, 5);
|
|
glow(c, 0, 0, 92, '#167eca', 0.17 + load * 0.2);
|
|
rect(c, -39, -105, 78, 210, '#0b1b2b', '#335770');
|
|
for (const side of [-1, 1]) {
|
|
line(c, [[side * 34, -105], [side * 48, -78], [side * 48, 78], [side * 34, 105]], '#6f8d9c', 3);
|
|
rect(c, -37, side < 0 ? -110 : 102, 74, 8, '#b89976');
|
|
line(c, [[side * 28, 0], [side * 87, 0], [side * 106, 20], [side * 146, 20]], '#234253', 8);
|
|
line(c, [[side * 28, 0], [side * 87, 0], [side * 106, 20], [side * 146, 20]], '#80b4c4', 1);
|
|
for (let i = 0; i < 18; i++) {
|
|
const travel = (i / 18 + t * 0.55) % 1;
|
|
const y = side * (100 - travel * 97);
|
|
const x = Math.sin(travel * 13 + t * 2) * 12;
|
|
line(c, [[x, y + side * 9], [x, y]], side < 0 ? '#75baff' : '#c8f5ff', 1 + load * 2);
|
|
}
|
|
}
|
|
for (let i = -6; i <= 6; i++) {
|
|
const wave = Math.pow(Math.max(0, Math.cos(t * 3.4 - Math.abs(i) * 0.62)), 6);
|
|
c.save(); c.globalAlpha = 0.2 + wave * (0.4 + load * 0.4);
|
|
line(c, [[-32, i * 15], [-22, i * 15 + 3], [22, i * 15 + 3], [32, i * 15]], '#89eaff', 3); c.restore();
|
|
}
|
|
glow(c, 0, 0, 28 + beat * 18 * load, '#81ddff', 0.8);
|
|
line(c, [[0, -20], [14, 0], [0, 20], [-14, 0], [0, -20]], '#e8fdff', 2);
|
|
line(c, [[0, -20], [0, 20], [14, 0], [-14, 0]], '#b0eeff');
|
|
},
|
|
military(c, t, load) {
|
|
polygon(c, 104, 6, '#3f4a50', Math.PI / 6, 12);
|
|
polygon(c, 110, 6, '#a7987e', Math.PI / 6);
|
|
for (let i = 0; i < 6; i++) {
|
|
const a = i * TAU / 6 + Math.PI / 6;
|
|
dot(c, Math.cos(a) * 104, Math.sin(a) * 104, 3, '#d6c6ad');
|
|
c.save(); c.rotate(a);
|
|
rect(c, 38, -10, 38, 20, '#171f25', '#655e4d');
|
|
for (let j = 0; j < 5; j++) rect(c, 42 + j * 6, -6, 4, 12, j < 1 + load * 4 && (t * 2 + i) % 6 > j * 0.65 ? '#e8b468' : '#433e30');
|
|
c.restore();
|
|
}
|
|
for (const [r, direction, color] of [[86, 1, '#d3b285'], [31, -1, '#8ea8a6']]) {
|
|
for (let i = 0; i < 12; i++) {
|
|
const a = i * TAU / 12 + direction * t * 0.65;
|
|
ring(c, 0, 0, r, color, 4, a, a + 0.3);
|
|
}
|
|
}
|
|
glow(c, 0, 0, 29, '#eab164', 0.25 + load * 0.4);
|
|
polygon(c, 18, 6, '#f8d09d', 0, 2);
|
|
line(c, [[-10, 0], [10, 0]], '#e7d8bf', 3);
|
|
for (const x of [-136, 126]) for (let j = 0; j < 7; j++) rect(c, x, -38 + j * 12, 10, 7, j < load * 7 ? '#c7a16c' : '#263039');
|
|
},
|
|
outlaw(c, t, load) {
|
|
c.save(); c.translate(Math.sin(t * 19) * load * 1.4, Math.sin(t * 23) * load * 1.5);
|
|
line(c, [[-126, 50], [-93, 50], [-93, -40], [26, -40], [26, -19]], '#a26c4f', 5);
|
|
line(c, [[-77, 48], [-54, 66], [47, 66], [66, 29]], '#746554', 4);
|
|
rect(c, -103, -23, 40, 70, '#302d29', '#81705c');
|
|
rect(c, -74, -38, 100, 85, '#34383b', '#8b8c7b');
|
|
rect(c, -53, -45, 29, 17, '#674938', '#c28b58');
|
|
rect(c, -19, -21, 40, 48, '#233c39', '#609a87');
|
|
for (let i = 0; i < 4; i++) rect(c, -14, -15 + i * 9, 28, 4, '#79b49e');
|
|
rect(c, 27, -22, 35, 51, '#494139', '#a08c6e');
|
|
for (let i = 0; i < 4; i++) rect(c, 30 + i * 8, -27, 4, 61, '#80745f');
|
|
const flicker = 0.8 + Math.sin(t * 17) * 0.12 + Math.sin(t * 29) * 0.08;
|
|
c.save(); c.translate(68, 3); c.scale((0.2 + load * 0.85) * flicker, 0.4 + load * 0.5);
|
|
glow(c, 25, 0, 55, '#e88250', 0.5);
|
|
for (let j = 0; j < 7; j++) {
|
|
const len = 42 + 35 * Math.sin(t * 13 + j) ** 2;
|
|
line(c, [[0, (j - 3) * 4], [len * 0.5, (j - 3) * 3], [len, Math.sin(t * 8 + j) * 5]], j % 2 ? '#ffc07c' : '#a3e5e7', 2);
|
|
}
|
|
c.restore();
|
|
dot(c, -67, 2, 27, '#0b1015'); ring(c, -67, 2, 28, '#b0967a', 3);
|
|
c.save(); c.translate(-67, 2); c.rotate(t * 4);
|
|
for (let i = 0; i < 5; i++) { c.rotate(TAU / 5); line(c, [[5, 0], [20, -8], [22, 3], [5, 0]], '#a49c87', 3); }
|
|
c.restore(); dot(c, -67, 2, 6, '#d8ae7a');
|
|
c.font = '9px Consolas, monospace'; c.fillStyle = '#dcc1a1'; c.fillText('NO. 07', -15, 43);
|
|
c.restore();
|
|
},
|
|
comedy(c, t, load) {
|
|
c.translate(0, Math.sin(t * 1.2) * 8);
|
|
glow(c, 0, 0, 100, '#6b49af', 0.17);
|
|
const colors = ['#d0acff', '#81ded2', '#f0c78e'];
|
|
for (let i = 0; i < 3; i++) {
|
|
c.save(); c.rotate(i * Math.PI / 3 + Math.sin(t * 0.5 + i) * 0.3);
|
|
const r = 74 + load * 15;
|
|
const squeeze = 0.22 + Math.abs(Math.sin(t * 0.65 + i)) * 0.55;
|
|
c.scale(1, squeeze); ring(c, 0, 0, r, colors[i], 2);
|
|
const a = t * (0.7 + i * 0.2) + i * 2;
|
|
dot(c, Math.cos(a) * r, Math.sin(a) * r, 5, colors[i]); c.restore();
|
|
}
|
|
glow(c, 0, 0, 45, '#a686f4', 0.4);
|
|
const vertices = Array.from({ length: 6 }, (_, i) => {
|
|
const a = i * TAU / 6 + t * 0.3;
|
|
const r = 24 + Math.sin(t * 1.7 + i * 2) * (4 + load * 10);
|
|
return [Math.cos(a) * r, Math.sin(a) * r];
|
|
});
|
|
line(c, [...vertices, vertices[0]], '#ece0ff', 2);
|
|
for (let i = 0; i < 6; i++) { line(c, [vertices[i], vertices[(i + 2) % 6]], '#b6a4d9'); dot(c, ...vertices[i], 2, '#ffffff'); }
|
|
for (let i = 0; i < 7; i++) {
|
|
const a = i * 2.4 + t * 0.18;
|
|
const x = Math.cos(a) * 121, y = Math.sin(a) * 86;
|
|
line(c, [[x - 3, y], [x + 3, y]], colors[i % 3]); line(c, [[x, y - 3], [x, y + 3]], colors[i % 3]);
|
|
}
|
|
},
|
|
industrial(c, t, load) {
|
|
const compression = (Math.sin(t * 2) + 1) / 2;
|
|
rect(c, -70, -93, 140, 186, '#27241e', '#877653');
|
|
for (const y of [-94, 85]) for (let i = 0; i < 14; i++) rect(c, -70 + i * 10, y, 10, 9, i % 2 ? '#bd943f' : '#30291a');
|
|
rect(c, -54, -76, 108, 152, '#0d1011', '#5e543d');
|
|
glow(c, 0, 0, 60 + compression * load * 18, '#ec8c26', 0.6 + load * 0.3);
|
|
c.save(); c.scale(1 - compression * 0.25, 1 + compression * 0.13);
|
|
glow(c, 0, 0, 31 + load * 8, '#ffda87'); c.restore();
|
|
for (const side of [-1, 1]) {
|
|
const x = side < 0 ? -133 : 78;
|
|
rect(c, x, -21, 55, 42, '#2e3332', '#6e7770');
|
|
const reach = 25 + compression * (12 + load * 16);
|
|
const inner = side * (77 - reach);
|
|
rect(c, side < 0 ? -78 : inner, -8, reach, 16, '#a6a092', '#d1c6ac');
|
|
rect(c, inner - (side < 0 ? 0 : 8), -34, 8, 68, '#746447', '#d9b875');
|
|
}
|
|
for (let i = 0; i < 16; i++) {
|
|
const travel = (i / 16 + t * 0.24) % 1;
|
|
dot(c, Math.sin(i * 8 + travel * 4) * 36, 60 - travel * 125, 0.8 + (i % 3) * 0.5, '#e4ad61');
|
|
}
|
|
for (const y of [-65, 59]) for (let i = 0; i < 8; i++) rect(c, -45 + i * 12, y, 6, 6, i < 2 + load * 6 ? '#e4a751' : '#55422b');
|
|
for (const x of [-63, 63]) for (const y of [-80, 80]) dot(c, x, y, 3, '#ad9b77');
|
|
},
|
|
station(c, t, load) {
|
|
ring(c, 0, 0, 109, '#34545c');
|
|
for (let i = 0; i < 4; i++) {
|
|
c.save(); c.rotate(i * Math.PI / 2);
|
|
line(c, [[-15, -119], [-15, -106], [15, -106], [15, -119]], '#8ba9a9', 3); c.restore();
|
|
}
|
|
c.save(); c.rotate(t * 0.24);
|
|
ring(c, 0, 0, 89, '#263f44', 19); ring(c, 0, 0, 100, '#93bcb8', 2); ring(c, 0, 0, 78, '#537c7e', 2);
|
|
for (let i = 0; i < 24; i++) {
|
|
const a = i * TAU / 24;
|
|
ring(c, 0, 0, 90, i % 4 === 0 ? '#c4d5bd' : '#568d86', 8, a + 0.025, a + 0.16);
|
|
}
|
|
for (let i = 0; i < 6; i++) {
|
|
c.save(); c.rotate(i * TAU / 6);
|
|
line(c, [[19, -4], [78, -4]], '#5b797c', 2); line(c, [[19, 4], [78, 4]], '#5b797c', 2);
|
|
for (let j = 0; j < 1 + Math.floor(load * 3); j++) {
|
|
const travel = (t * 0.55 + j / 3 + i / 6) % 1;
|
|
dot(c, 22 + travel * 55, 0, 2, '#b4ffe0');
|
|
}
|
|
c.restore();
|
|
}
|
|
c.restore();
|
|
glow(c, 0, 0, 30, '#73e4c2', 0.3 + load * 0.3);
|
|
dot(c, 0, 0, 21, '#142d32'); ring(c, 0, 0, 21, '#a3d9cd', 2);
|
|
line(c, [[-10, -5], [10, -5], [10, 5], [-10, 5], [-10, -5]], '#c5e7dc', 2);
|
|
for (let i = 0; i < 6; i++) ring(c, 0, 0, 115, '#3b625f', 2, i * TAU / 6 + 0.3, i * TAU / 6 + 0.6);
|
|
}
|
|
};
|
|
|
|
|
|
const modes = {
|
|
'intermix-cascade': 'starfleet',
|
|
'tactical-flywheel': 'military',
|
|
'patchwork-drive': 'outlaw',
|
|
'improbability-engine': 'comedy',
|
|
'compression-furnace': 'industrial',
|
|
'station-hub': 'station'
|
|
};
|
|
const CoreAnimations = {
|
|
has(mode) { return Object.prototype.hasOwnProperty.call(modes, mode); },
|
|
render(ctx, mode, width, height, time, load) {
|
|
if (!this.has(mode) || width <= 20 || height <= 42) return;
|
|
ctx.clearRect(0, 0, width, height);
|
|
ctx.save();
|
|
// Preserve the existing label and the full motion envelope at sidebar size.
|
|
const labelSpace = 32;
|
|
const bottomSpace = 10;
|
|
ctx.translate(width / 2, labelSpace + (height - labelSpace - bottomSpace) / 2);
|
|
const scale = Math.min((width - 20) / 330, (height - labelSpace - bottomSpace) / 250);
|
|
ctx.scale(scale, scale);
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
base(ctx);
|
|
renderers[modes[mode]](ctx, time, Math.max(0, Math.min(1, load)));
|
|
ctx.restore();
|
|
}
|
|
};
|
|
window.CoreAnimations = CoreAnimations;
|
|
})();
|