Files
the-nexus/modules/portals/commit-banners.js

63 lines
2.5 KiB
JavaScript
Raw Normal View History

// modules/portals/commit-banners.js — Floating commit banner sprites
import * as THREE from 'three';
import { fetchRecentCommitsForBanners } from '../data/gitea.js';
const commitBanners = [];
function createCommitTexture(hash, message) {
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0, 0, 16, 0.75)'; ctx.fillRect(0, 0, 512, 64);
ctx.strokeStyle = '#4488ff'; ctx.lineWidth = 1; ctx.strokeRect(0.5, 0.5, 511, 63);
ctx.font = 'bold 11px "Courier New", monospace'; ctx.fillStyle = '#4488ff'; ctx.fillText(hash, 10, 20);
ctx.font = '12px "Courier New", monospace'; ctx.fillStyle = '#ccd6f6';
const displayMsg = message.length > 54 ? message.slice(0, 54) + '\u2026' : message;
ctx.fillText(displayMsg, 10, 46);
return new THREE.CanvasTexture(canvas);
}
export async function init(scene) {
const commits = await fetchRecentCommitsForBanners();
const spreadX = [-7, -3.5, 0, 3.5, 7];
const spreadY = [1.0, -1.5, 2.2, -0.8, 1.6];
const spreadZ = [-1.5, -2.5, -1.0, -2.0, -1.8];
commits.forEach((commit, i) => {
const texture = createCommitTexture(commit.hash, commit.message);
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, opacity: 0, depthWrite: false });
const sprite = new THREE.Sprite(material);
sprite.scale.set(12, 1.5, 1);
sprite.position.set(spreadX[i % spreadX.length], spreadY[i % spreadY.length], spreadZ[i % spreadZ.length]);
sprite.userData = {
baseY: spreadY[i % spreadY.length],
floatPhase: (i / commits.length) * Math.PI * 2,
floatSpeed: 0.25 + i * 0.07,
startDelay: i * 2.5,
lifetime: 12 + i * 1.5,
spawnTime: null,
zoomLabel: `Commit: ${commit.hash}`,
};
scene.add(sprite);
commitBanners.push(sprite);
});
}
export function update(elapsed) {
const FADE_DUR = 1.5;
commitBanners.forEach(banner => {
const ud = banner.userData;
if (ud.spawnTime === null) {
if (elapsed < ud.startDelay) return;
ud.spawnTime = elapsed;
}
const age = elapsed - ud.spawnTime;
let opacity;
if (age < FADE_DUR) opacity = age / FADE_DUR;
else if (age < ud.lifetime - FADE_DUR) opacity = 1;
else if (age < ud.lifetime) opacity = (ud.lifetime - age) / FADE_DUR;
else { ud.spawnTime = elapsed + 3; opacity = 0; }
banner.material.opacity = opacity * 0.85;
banner.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.4;
});
}