Compare commits

..

3 Commits

Author SHA1 Message Date
9fc4eb09b7 chore: replace git.pihkaal.xyz and github by git.pihkaal.me
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m24s
2026-02-13 17:39:17 +01:00
fd5a0230f1 fix(common): reimplement missing function forceAnimateBLabel 2026-02-13 17:18:05 +01:00
d7bca21bd7 fix(nds): use deltaTime in frame based-motion 2026-02-13 17:10:32 +01:00
10 changed files with 74 additions and 54 deletions

View File

@@ -144,5 +144,14 @@ useKeyDown(({ key, repeated }) => {
} }
}); });
const forceAnimateBLabel = () =>
animateLabelChange(
(v) => (bButtonOffsetY = v),
(l) => (displayedBLabel = l),
props.bLabel,
);
defineExpose({ forceAnimateBLabel });
defineOptions({ render: () => null }); defineOptions({ render: () => null });
</script> </script>

View File

@@ -6,10 +6,10 @@ const props = defineProps<{
const confetti = useConfetti(); const confetti = useConfetti();
const { onRender } = useScreen(); const { onRender } = useScreen();
onRender((ctx) => { onRender((ctx, deltaTime) => {
let offset: number; let offset: number;
if (props.screen === "top") { if (props.screen === "top") {
confetti.update(); confetti.update(deltaTime);
offset = 0; offset = 0;
} else { } else {
offset = LOGICAL_HEIGHT; offset = LOGICAL_HEIGHT;

View File

@@ -13,8 +13,8 @@ const BUTTONS = {
git: { git: {
position: [31, 32], position: [31, 32],
action: "open", action: "open",
url: "https://git.pihkaal.xyz", url: "https://git.pihkaal.me",
text: "git.pihkaal.xyz", text: "git.pihkaal.me",
}, },
email: { email: {
position: [31, 64], position: [31, 64],

View File

@@ -12,16 +12,17 @@ const { onRender, onClick } = useScreen();
const BAR_HEIGHT = 24; const BAR_HEIGHT = 24;
const MAX_RADIUS = 27; const MAX_RADIUS = 27;
const MIN_RADIUS = 3; const MIN_RADIUS = 3;
const BASE_SHRINK_SPEED = 0.09; const BASE_SHRINK_SPEED = 0.18;
const MAX_SHRINK_SPEED = 0.14; const MAX_SHRINK_SPEED = 0.28;
const BASE_SPAWN_INTERVAL = 90; const BASE_SPAWN_INTERVAL = 45;
const MIN_SPAWN_INTERVAL = 45; const MIN_SPAWN_INTERVAL = 23;
const DIFFICULTY_SCORE_CAP = 100; const DIFFICULTY_SCORE_CAP = 100;
const RING_STROKE_WIDTH = 5; const RING_STROKE_WIDTH = 5;
const RING_EXPAND_SPEED = 0.3; const RING_EXPAND_SPEED = 0.6;
const RING_FADE_SPEED = 0.05; const RING_FADE_SPEED = 0.1;
const MAX_LIVES = 3; const MAX_LIVES = 3;
const CROSSHAIR_MOVE_SPEED = 5; const CROSSHAIR_MOVE_SPEED = 10;
const FRAME_TIME = 1000 / 60;
const getDifficulty = () => { const getDifficulty = () => {
const progress = Math.min(score / DIFFICULTY_SCORE_CAP, 1); const progress = Math.min(score / DIFFICULTY_SCORE_CAP, 1);
@@ -120,7 +121,7 @@ const animateIntro = async () => {
const animateOutro = async () => { const animateOutro = async () => {
isAnimating.value = true; isAnimating.value = true;
targetX = 0; targetX = 0;
targetY = LOGICAL_HEIGHT * 2 - 20; targetY = LOGICAL_HEIGHT;
await gsap await gsap
.timeline({ .timeline({
@@ -200,12 +201,12 @@ const handleActivateA = async () => {
} }
}; };
const moveTowards = (current: number, target: number) => { const moveTowards = (current: number, target: number, dt: number) => {
if (current === target) return current; if (current === target) return current;
const direction = Math.sign(target - current); const direction = Math.sign(target - current);
return ( return (
current + current +
direction * Math.min(CROSSHAIR_MOVE_SPEED, Math.abs(target - current)) direction * Math.min(CROSSHAIR_MOVE_SPEED * dt, Math.abs(target - current))
); );
}; };
@@ -303,15 +304,17 @@ onClick((mx, my) => {
} }
}); });
onRender((ctx) => { onRender((ctx, deltaTime) => {
const dt = deltaTime / FRAME_TIME;
// update crosshair position in all modes except paused // update crosshair position in all modes except paused
if (state.value !== "paused") { if (state.value !== "paused") {
if (horizontalFirst) { if (horizontalFirst) {
if (x !== targetX) x = moveTowards(x, targetX); if (x !== targetX) x = moveTowards(x, targetX, dt);
else y = moveTowards(y, targetY); else y = moveTowards(y, targetY, dt);
} else { } else {
if (y !== targetY) y = moveTowards(y, targetY); if (y !== targetY) y = moveTowards(y, targetY, dt);
else x = moveTowards(x, targetX); else x = moveTowards(x, targetX, dt);
} }
} }
@@ -320,15 +323,15 @@ onRender((ctx) => {
const { shrinkSpeed, spawnInterval } = getDifficulty(); const { shrinkSpeed, spawnInterval } = getDifficulty();
// spawn circles // spawn circles
spawnTimer++; spawnTimer += deltaTime;
if (spawnTimer >= spawnInterval) { if (spawnTimer >= spawnInterval * FRAME_TIME) {
spawnCircle(); spawnCircle();
spawnTimer = 0; spawnTimer = 0;
} }
// update circles and rings // update circles and rings
circles = circles.filter((circle) => { circles = circles.filter((circle) => {
circle.radius -= shrinkSpeed; circle.radius -= shrinkSpeed * dt;
if (circle.radius < MIN_RADIUS) { if (circle.radius < MIN_RADIUS) {
lives--; lives--;
if (lives <= 0) { if (lives <= 0) {
@@ -341,8 +344,8 @@ onRender((ctx) => {
}); });
rings = rings.filter((ring) => { rings = rings.filter((ring) => {
ring.radius += RING_EXPAND_SPEED; ring.radius += RING_EXPAND_SPEED * dt;
ring.alpha -= RING_FADE_SPEED; ring.alpha -= RING_FADE_SPEED * dt;
return ring.alpha > 0; return ring.alpha > 0;
}); });
} }

View File

@@ -1,21 +1,22 @@
const PARTICLE_COUNT = 400; const PARTICLE_COUNT = 400;
const SPAWN_DURATION = 700; const SPAWN_DURATION = 350;
const VX_RANGE = 0.5; const VX_RANGE = 1.0;
const VY_MIN = 0.3; const VY_MIN = 0.6;
const VY_RANGE = 0.2; const VY_RANGE = 0.4;
const WIDTH_MIN = 3; const WIDTH_MIN = 3;
const WIDTH_RANGE = 4; const WIDTH_RANGE = 4;
const HEIGHT_MIN = 2; const HEIGHT_MIN = 2;
const HEIGHT_RANGE = 3; const HEIGHT_RANGE = 3;
const ROTATION_SPEED_RANGE = 0.15; const ROTATION_SPEED_RANGE = 0.3;
const DRIFT_STRENGTH = 0.03; const DRIFT_STRENGTH = 0.06;
const GRAVITY = 0.007; const GRAVITY = 0.014;
const VX_DAMPING = 0.995; const VX_DAMPING = 0.988;
const SWAY_AMPLITUDE = 0.3; const SWAY_AMPLITUDE = 0.6;
const SWAY_SPEED_MIN = 0.02; const SWAY_SPEED_MIN = 0.04;
const SWAY_SPEED_RANGE = 0.03; const SWAY_SPEED_RANGE = 0.06;
const OFFSCREEN_MARGIN = 10; const OFFSCREEN_MARGIN = 10;
const TOTAL_HEIGHT = 192 * 2; const TOTAL_HEIGHT = 192 * 2;
const FRAME_TIME = 1000 / 60;
const COLORS = [ const COLORS = [
"#fb0018", "#fb0018",
@@ -41,7 +42,7 @@ export type ConfettiParticle = {
}; };
type Spawner = { type Spawner = {
frame: number; elapsed: number;
accumulator: number; accumulator: number;
particleCount: number; particleCount: number;
duration: number; duration: number;
@@ -67,18 +68,25 @@ const spawnParticle = () => {
}; };
const spawn = (particleCount = PARTICLE_COUNT, duration = SPAWN_DURATION) => { const spawn = (particleCount = PARTICLE_COUNT, duration = SPAWN_DURATION) => {
spawners.push({ frame: 0, accumulator: 0, particleCount, duration }); spawners.push({
elapsed: 0,
accumulator: 0,
particleCount,
duration: duration * FRAME_TIME,
});
}; };
const update = () => { const update = (deltaTime: number) => {
const dt = deltaTime / FRAME_TIME;
for (let s = spawners.length - 1; s >= 0; s -= 1) { for (let s = spawners.length - 1; s >= 0; s -= 1) {
const spawner = spawners[s]!; const spawner = spawners[s]!;
if (spawner.frame < spawner.duration) { if (spawner.elapsed < spawner.duration) {
const progress = spawner.frame / spawner.duration; const progress = spawner.elapsed / spawner.duration;
const inv = 1 - progress; const inv = 1 - progress;
const rate = inv * inv; const rate = inv * inv;
spawner.accumulator += spawner.accumulator +=
rate * (spawner.particleCount / spawner.duration) * 3; rate * (spawner.particleCount / spawner.duration) * 3 * deltaTime;
const count = Math.floor(spawner.accumulator); const count = Math.floor(spawner.accumulator);
spawner.accumulator -= count; spawner.accumulator -= count;
@@ -87,7 +95,7 @@ const update = () => {
spawnParticle(); spawnParticle();
} }
spawner.frame += 1; spawner.elapsed += deltaTime;
} else { } else {
spawners.splice(s, 1); spawners.splice(s, 1);
} }
@@ -95,13 +103,13 @@ const update = () => {
for (let i = particles.length - 1; i >= 0; i -= 1) { for (let i = particles.length - 1; i >= 0; i -= 1) {
const p = particles[i]!; const p = particles[i]!;
p.vy += GRAVITY; p.vy += GRAVITY * dt;
p.vx += (Math.random() - 0.5) * DRIFT_STRENGTH; p.vx += (Math.random() - 0.5) * DRIFT_STRENGTH * dt;
p.vx *= VX_DAMPING; p.vx *= Math.pow(VX_DAMPING, dt);
p.swayPhase += p.swaySpeed; p.swayPhase += p.swaySpeed * dt;
p.x += p.vx + Math.sin(p.swayPhase) * SWAY_AMPLITUDE; p.x += (p.vx + Math.sin(p.swayPhase) * SWAY_AMPLITUDE) * dt;
p.y += p.vy; p.y += p.vy * dt;
p.rotation += p.rotationSpeed; p.rotation += p.rotationSpeed * dt;
if (p.y > TOTAL_HEIGHT + OFFSCREEN_MARGIN) { if (p.y > TOTAL_HEIGHT + OFFSCREEN_MARGIN) {
particles.splice(i, 1); particles.splice(i, 1);

View File

@@ -66,7 +66,7 @@ export const useAchievementsStore = defineStore("achievements", () => {
if (storage.value.unlocked.length === ACHIEVEMENTS.length) { if (storage.value.unlocked.length === ACHIEVEMENTS.length) {
confetti.spawn(); confetti.spawn();
} else { } else {
confetti.spawn(50, 350); confetti.spawn(50, 175);
} }
return true; return true;

View File

@@ -1,7 +1,7 @@
order: 50 order: 50
scope: hobby scope: hobby
title: LBF Bot title: LBF Bot
link: https://git.pihkaal.xyz/lbf-bot link: https://git.pihkaal.me/lbf-bot
description: For a gaming group description: For a gaming group
summary: Custom Discord bot summary: Custom Discord bot

View File

@@ -1,7 +1,7 @@
order: 60 order: 60
scope: hobby scope: hobby
title: Raylib Spdrns title: Raylib Spdrns
link: https://git.pihkaal.xyz/raylib-speedruns link: https://git.pihkaal.me/raylib-speedruns
description: Awesome video game library description: Awesome video game library
summary: Raylib Speedruns summary: Raylib Speedruns

View File

@@ -1,7 +1,7 @@
order: 20 order: 20
scope: hobby scope: hobby
title: tlock title: tlock
link: https://git.pihkaal.xyz/tlock link: https://git.pihkaal.me/tlock
description: For Hyprland ricing description: For Hyprland ricing
summary: Terminal based clock summary: Terminal based clock

View File

@@ -161,7 +161,7 @@
"actions": { "actions": {
"open": "Open", "open": "Open",
"copy": "Copy", "copy": "Copy",
"git": "Github profile", "git": "Git page",
"email": "Email", "email": "Email",
"linkedin": "LinkedIn link", "linkedin": "LinkedIn link",
"cv": "CV" "cv": "CV"