From d825b910d71a72664ae7a9c881ec2a14e7ec96ac Mon Sep 17 00:00:00 2001 From: "Arija A." Date: Thu, 23 Oct 2025 22:31:54 +0300 Subject: [PATCH] Add twinkle effect to background Signed-off-by: Arija A. --- src/static/css/base.css | 10 ++ src/static/js/base.js | 228 ++++++++++++++++++++++++++++++++++++++++ src/templates/base.j2 | 4 +- 3 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 src/static/js/base.js diff --git a/src/static/css/base.css b/src/static/css/base.css index 69b5e79..14ddda6 100644 --- a/src/static/css/base.css +++ b/src/static/css/base.css @@ -81,3 +81,13 @@ code { scroll-behavior: auto !important; } } + +#night { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0e0016; + z-index: -9999; +} diff --git a/src/static/js/base.js b/src/static/js/base.js new file mode 100644 index 0000000..dcaf962 --- /dev/null +++ b/src/static/js/base.js @@ -0,0 +1,228 @@ +/** + * @licstart The following is the entire license notice for the JavaScript + * code in this file. + * + * Copyright (C) 2025 purplebored.pl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * You should have received a copy of the Apache License, Version 2.0 + * along with this program. If not, see . + * + * @licend The above is the entire license notice for the JavaScript code + * in this page. + */ + +"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(); +}); diff --git a/src/templates/base.j2 b/src/templates/base.j2 index 8c01a41..bf93fec 100644 --- a/src/templates/base.j2 +++ b/src/templates/base.j2 @@ -19,8 +19,8 @@ /> - - + +