Files
wanderer/assets/lib/phoenix/index.js
2024-10-09 17:41:02 +04:00

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());
});