mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-03 14:32:36 +00:00
275 lines
8.4 KiB
JavaScript
275 lines
8.4 KiB
JavaScript
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
|
|
import 'phoenix_html';
|
|
|
|
import './live_reload.css';
|
|
|
|
const animateBg = function (bgCanvas) {
|
|
const { TweenMax, _ } = window;
|
|
/**
|
|
* Utility function for returning a random integer in a given range
|
|
* @param {Int} max
|
|
* @param {Int} min
|
|
*/
|
|
const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
const BASE_SIZE = 1;
|
|
const VELOCITY_INC = 1.01;
|
|
const VELOCITY_INIT_INC = 0.525;
|
|
const JUMP_VELOCITY_INC = 0.55;
|
|
const JUMP_SIZE_INC = 1.15;
|
|
const SIZE_INC = 1.01;
|
|
const RAD = Math.PI / 180;
|
|
const WARP_COLORS = [
|
|
[197, 239, 247],
|
|
[25, 181, 254],
|
|
[77, 5, 232],
|
|
[165, 55, 253],
|
|
[255, 255, 255],
|
|
];
|
|
/**
|
|
* Class for storing the particle metadata
|
|
* position, size, length, speed etc.
|
|
*/
|
|
class Star {
|
|
STATE = {
|
|
alpha: Math.random(),
|
|
angle: randomInRange(0, 360) * RAD,
|
|
};
|
|
reset = () => {
|
|
const angle = randomInRange(0, 360) * (Math.PI / 180);
|
|
const vX = Math.cos(angle);
|
|
const vY = Math.sin(angle);
|
|
const travelled =
|
|
Math.random() > 0.5
|
|
? Math.random() * Math.max(window.innerWidth, window.innerHeight) + Math.random() * (window.innerWidth * 0.24)
|
|
: Math.random() * (window.innerWidth * 0.25);
|
|
this.STATE = {
|
|
...this.STATE,
|
|
iX: undefined,
|
|
iY: undefined,
|
|
active: travelled ? true : false,
|
|
x: Math.floor(vX * travelled) + window.innerWidth / 2,
|
|
vX,
|
|
y: Math.floor(vY * travelled) + window.innerHeight / 2,
|
|
vY,
|
|
size: BASE_SIZE,
|
|
};
|
|
};
|
|
constructor() {
|
|
this.reset();
|
|
}
|
|
}
|
|
|
|
const generateStarPool = size => new Array(size).fill().map(() => new Star());
|
|
|
|
// Class for the actual app
|
|
// Not too much happens in here
|
|
// Initiate the drawing process and listen for user interactions 👍
|
|
class JumpToHyperspace {
|
|
STATE = {
|
|
stars: generateStarPool(300),
|
|
bgAlpha: 0,
|
|
sizeInc: SIZE_INC,
|
|
velocity: VELOCITY_INC,
|
|
};
|
|
canvas = null;
|
|
context = null;
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.context = canvas.getContext('2d');
|
|
this.bind();
|
|
this.setup();
|
|
this.render();
|
|
}
|
|
render = () => {
|
|
const {
|
|
STATE: { bgAlpha, velocity, sizeInc, initiating, jumping, stars },
|
|
context,
|
|
render,
|
|
} = this;
|
|
// Clear the canvas
|
|
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
|
if (bgAlpha > 0) {
|
|
context.fillStyle = `rgba(31, 58, 157, ${bgAlpha})`;
|
|
context.fillRect(0, 0, window.innerWidth, window.innerHeight);
|
|
}
|
|
// 1. Shall we add a new star
|
|
const nonActive = stars.filter(s => !s.STATE.active);
|
|
if (!initiating && nonActive.length > 0) {
|
|
// Introduce a star
|
|
nonActive[0].STATE.active = true;
|
|
}
|
|
// 2. Update the stars and draw them.
|
|
for (const star of stars.filter(s => s.STATE.active)) {
|
|
const { active, x, y, iX, iY, iVX, iVY, size, vX, vY } = star.STATE;
|
|
// Check if the star needs deactivating
|
|
if (
|
|
((iX || x) < 0 || (iX || x) > window.innerWidth || (iY || y) < 0 || (iY || y) > window.innerHeight) &&
|
|
active &&
|
|
!initiating
|
|
) {
|
|
star.reset(true);
|
|
} else if (active) {
|
|
const newIX = initiating ? iX : iX + iVX;
|
|
const newIY = initiating ? iY : iY + iVY;
|
|
const newX = x + vX;
|
|
const newY = y + vY;
|
|
// Just need to work out if it overtakes the original line that's all
|
|
const caught =
|
|
(vX < 0 && newIX < x) || (vX > 0 && newIX > x) || (vY < 0 && newIY < y) || (vY > 0 && newIY > y);
|
|
star.STATE = {
|
|
...star.STATE,
|
|
iX: caught ? undefined : newIX,
|
|
iY: caught ? undefined : newIY,
|
|
iVX: caught ? undefined : iVX * VELOCITY_INIT_INC,
|
|
iVY: caught ? undefined : iVY * VELOCITY_INIT_INC,
|
|
x: newX,
|
|
vX: star.STATE.vX * velocity,
|
|
y: newY,
|
|
vY: star.STATE.vY * velocity,
|
|
size: initiating ? size : size * (iX || iY ? SIZE_INC : sizeInc),
|
|
};
|
|
let color = `rgba(255, 255, 255, ${star.STATE.alpha})`;
|
|
if (jumping) {
|
|
const [r, g, b] = WARP_COLORS[randomInRange(0, WARP_COLORS.length)];
|
|
color = `rgba(${r}, ${g}, ${b}, ${star.STATE.alpha})`;
|
|
}
|
|
context.strokeStyle = color;
|
|
context.lineWidth = size;
|
|
context.beginPath();
|
|
context.moveTo(star.STATE.iX || x, star.STATE.iY || y);
|
|
context.lineTo(star.STATE.x, star.STATE.y);
|
|
context.stroke();
|
|
}
|
|
}
|
|
requestAnimationFrame(render);
|
|
};
|
|
initiate = () => {
|
|
if (this.STATE.jumping || this.STATE.initiating) return;
|
|
this.STATE = {
|
|
...this.STATE,
|
|
initiating: true,
|
|
initiateTimestamp: new Date().getTime(),
|
|
};
|
|
TweenMax.to(this.STATE, 0.25, { velocity: VELOCITY_INIT_INC, bgAlpha: 0.3 });
|
|
// When we initiate, stop the XY origin from moving so that we draw
|
|
// longer lines until the jump
|
|
for (const star of this.STATE.stars.filter(s => s.STATE.active)) {
|
|
star.STATE = {
|
|
...star.STATE,
|
|
iX: star.STATE.x,
|
|
iY: star.STATE.y,
|
|
iVX: star.STATE.vX,
|
|
iVY: star.STATE.vY,
|
|
};
|
|
}
|
|
};
|
|
jump = () => {
|
|
this.STATE = {
|
|
...this.STATE,
|
|
bgAlpha: 0,
|
|
jumping: true,
|
|
};
|
|
TweenMax.to(this.STATE, 0.25, { velocity: JUMP_VELOCITY_INC, bgAlpha: 0.75, sizeInc: JUMP_SIZE_INC });
|
|
setTimeout(() => {
|
|
this.STATE = {
|
|
...this.STATE,
|
|
jumping: false,
|
|
};
|
|
TweenMax.to(this.STATE, 0.25, { bgAlpha: 0, velocity: VELOCITY_INC, sizeInc: SIZE_INC });
|
|
}, 5000);
|
|
};
|
|
enter = () => {
|
|
if (this.STATE.jumping) return;
|
|
const { initiateTimestamp } = this.STATE;
|
|
this.STATE = {
|
|
...this.STATE,
|
|
initiating: false,
|
|
initiateTimestamp: undefined,
|
|
};
|
|
if (new Date().getTime() - initiateTimestamp > 600) {
|
|
this.jump();
|
|
} else {
|
|
TweenMax.to(this.STATE, 0.25, { velocity: VELOCITY_INC, bgAlpha: 0 });
|
|
}
|
|
};
|
|
bind = () => {
|
|
this.canvas.addEventListener('mousedown', this.initiate);
|
|
this.canvas.addEventListener('touchstart', this.initiate);
|
|
this.canvas.addEventListener('mouseup', this.enter);
|
|
this.canvas.addEventListener('touchend', this.enter);
|
|
};
|
|
setup = () => {
|
|
this.context.lineCap = 'round';
|
|
this.canvas.height = window.innerHeight;
|
|
this.canvas.width = window.innerWidth;
|
|
};
|
|
reset = () => {
|
|
this.STATE = {
|
|
...this.STATE,
|
|
stars: generateStarPool(300),
|
|
};
|
|
this.setup();
|
|
};
|
|
}
|
|
window.myJump = new JumpToHyperspace(bgCanvas);
|
|
window.addEventListener(
|
|
'resize',
|
|
_.debounce(() => {
|
|
window.myJump.reset();
|
|
}, 250),
|
|
);
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// animage background
|
|
const canvas = document.getElementById('bg-canvas');
|
|
if (canvas) {
|
|
animateBg(canvas);
|
|
}
|
|
|
|
// Select all buttons with the 'share-link' class
|
|
const buttons = document.querySelectorAll('button.copy-link');
|
|
|
|
buttons.forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
// Get the URL from the data attribute
|
|
const url = button.dataset.url;
|
|
|
|
button.classList.remove('copied');
|
|
|
|
// Copy the URL to the clipboard
|
|
navigator.clipboard
|
|
.writeText(url)
|
|
.then(() => {
|
|
// Add the 'copied' class to the button
|
|
button.classList.add('copied');
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to copy URL:', err);
|
|
});
|
|
});
|
|
});
|
|
|
|
const navbar = document.querySelector('navbar.navbar');
|
|
|
|
const scrollState = {
|
|
top: true,
|
|
topThreshold: 10,
|
|
onScroll: function () {
|
|
if (this.top && window.scrollY > this.topThreshold) {
|
|
this.top = false;
|
|
this.updateUI();
|
|
} else if (!this.top && window.scrollY <= this.topThreshold) {
|
|
this.top = true;
|
|
this.updateUI();
|
|
}
|
|
},
|
|
updateUI: function () {
|
|
navbar.classList.toggle('bg-opacity-30');
|
|
navbar.classList.toggle('backdrop-filter');
|
|
navbar.classList.toggle('backdrop-blur-lg');
|
|
},
|
|
};
|
|
window.addEventListener('scroll', () => scrollState.onScroll());
|
|
});
|