164 lines
6.7 KiB
JavaScript
164 lines
6.7 KiB
JavaScript
"use strict";
|
|
function create_night(canvas_element) {
|
|
const context = canvas_element.getContext("2d");
|
|
const star_range = 0.7; /* Variation in star size */
|
|
const star_spread = 0.002; /* Star density per pixel */
|
|
const concentration_corner = "top-left"; /* Concentration corner position */
|
|
const concentration_strength = 5.0; /* How much should it be concentrated? */
|
|
const twinkle_intensity = 5.0; /* How noticeable should the twinkle be? */
|
|
const interaction_radius = 150; /* Radius around mouse/touch where stars react */
|
|
const interaction_brightness_factor = 5.0; /* How much brightness increases */
|
|
const interaction_size_factor = 1.5; /* How much the stars should grow in size (only during interaction) */
|
|
const magic = [
|
|
16.31189, 32454.4619, 9371.1474, 6848, 1544, 6848, 2156,
|
|
]; /* Just some magic numbers */
|
|
/* Seeded "random" number */
|
|
function seeded_random(seed) {
|
|
const value = Math.sin(seed) * 10000;
|
|
return value - Math.floor(value);
|
|
}
|
|
let stars = [];
|
|
let time = 0;
|
|
let mouse_x = -1,
|
|
mouse_y = -1;
|
|
/* Determine corner position for concentration */
|
|
function get_corner(width, height) {
|
|
switch (concentration_corner) {
|
|
case "top-right":
|
|
return { x: width, y: 0 };
|
|
case "bottom-left":
|
|
return { x: 0, y: height };
|
|
case "bottom-right":
|
|
return { x: width, y: height };
|
|
default:
|
|
return { x: 0, y: 0 }; /* top-left */
|
|
}
|
|
}
|
|
/* Function to generate star shape */
|
|
function draw_star(x, y, radius, alpha) {
|
|
const num_points = 5 + Math.floor(Math.random() * 3);
|
|
const angle = Math.PI / num_points;
|
|
context.beginPath();
|
|
for (let idx = 0; idx < num_points; idx++) {
|
|
const angle_offset = (idx * 2 * Math.PI) / num_points;
|
|
const outer_x = x + radius * Math.cos(angle_offset);
|
|
const outer_y = y + radius * Math.sin(angle_offset);
|
|
context.lineTo(outer_x, outer_y);
|
|
/* Inner points for star shape */
|
|
const inner_radius = radius / 2 + Math.random() * 0.2;
|
|
const inner_x = x + inner_radius * Math.cos(angle_offset + angle);
|
|
const inner_y = y + inner_radius * Math.sin(angle_offset + angle);
|
|
context.lineTo(inner_x, inner_y);
|
|
}
|
|
context.closePath();
|
|
context.fillStyle = `rgba(255, 255, 180, ${alpha})`;
|
|
context.fill();
|
|
}
|
|
/* Function to generate star background */
|
|
function generate_stars() {
|
|
const { width, height } = canvas_element;
|
|
const star_count = Math.floor(width * height * star_spread);
|
|
const corner = get_corner(width, height);
|
|
stars = [];
|
|
for (let idx = 0; idx < star_count; idx++) {
|
|
const base_random = seeded_random(idx * magic[0]);
|
|
/* Uniform random position */
|
|
const raw_x = seeded_random(base_random * magic[1]) * width;
|
|
const raw_y = seeded_random(base_random * magic[2]) * height;
|
|
/* Calculate distance from the corner to control concentration */
|
|
const dx = raw_x - corner.x;
|
|
const dy = raw_y - corner.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
/* Apply exponential falloff based on distance (closer = more likely to be drawn) */
|
|
const max_distance = Math.sqrt(width * width + height * height);
|
|
const weight = Math.exp(
|
|
-concentration_strength * (distance / max_distance),
|
|
);
|
|
/* Skip stars that don't meet the concentration threshold */
|
|
if (seeded_random(base_random * magic[3]) > weight) {
|
|
continue;
|
|
}
|
|
/* Randomized star properties */
|
|
const radius =
|
|
seeded_random(base_random * magic[4]) * star_range +
|
|
0.4 +
|
|
Math.random() * 0.2;
|
|
const twinkle_speed =
|
|
seeded_random(base_random * magic[5]) * 1.5 + 0.3;
|
|
const twinkle_phase =
|
|
seeded_random(base_random * magic[6]) * Math.PI * 2;
|
|
stars.push({
|
|
x: raw_x,
|
|
y: raw_y,
|
|
radius,
|
|
twinkle_speed,
|
|
twinkle_phase,
|
|
original_radius: radius,
|
|
});
|
|
}
|
|
}
|
|
/* Resize canvas and regenerate stars on window resize */
|
|
function resize_canvas() {
|
|
canvas_element.width = window.innerWidth;
|
|
canvas_element.height = window.innerHeight;
|
|
generate_stars();
|
|
}
|
|
window.addEventListener("resize", resize_canvas);
|
|
resize_canvas();
|
|
/* Handle mouse/touch movement effects */
|
|
function handle_interaction(event) {
|
|
if (event.touches) {
|
|
mouse_x = event.touches[0].clientX;
|
|
mouse_y = event.touches[0].clientY;
|
|
} else {
|
|
mouse_x = event.clientX;
|
|
mouse_y = event.clientY;
|
|
}
|
|
}
|
|
window.addEventListener("mousemove", handle_interaction);
|
|
window.addEventListener("touchmove", handle_interaction);
|
|
/* Animate stars (twinke) */
|
|
function animate() {
|
|
time += 0.01;
|
|
const { width, height } = canvas_element;
|
|
context.fillStyle = "#0e0016";
|
|
context.fillRect(0, 0, width, height);
|
|
for (const star of stars) {
|
|
const twinkle = Math.sin(
|
|
time * star.twinkle_speed + star.twinkle_phase,
|
|
);
|
|
let alpha = 0.6 + twinkle_intensity * (0.5 + 0.5 * twinkle);
|
|
/* Interaction effect based on mouse/touch proximity */
|
|
if (mouse_x !== -1 && mouse_y !== -1) {
|
|
const dx = mouse_x - star.x;
|
|
const dy = mouse_y - star.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
if (distance < interaction_radius) {
|
|
const effect = Math.max(
|
|
0,
|
|
1 - distance / interaction_radius,
|
|
);
|
|
alpha *=
|
|
1 +
|
|
effect *
|
|
interaction_brightness_factor; /* Make the star brighter */
|
|
star.radius =
|
|
star.original_radius *
|
|
(1 +
|
|
effect *
|
|
interaction_size_factor); /* Grow the star size */
|
|
}
|
|
}
|
|
draw_star(star.x, star.y, star.radius, alpha);
|
|
}
|
|
requestAnimationFrame(animate);
|
|
}
|
|
animate();
|
|
}
|
|
function base_main() {
|
|
const night = document.getElementById("night");
|
|
create_night(night);
|
|
}
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
base_main();
|
|
}); |