<div id="star-tooltip" class="star-tooltip"></div>
<div id="star-popup" class="star-popup"></div>
<script type="module">
import { app } from "/app.js";
class StarField {
constructor({ count = 200, container = document.body } = {}) {
this.container = container;
this.starCount = count;
this.stars = [];
this.positionMap = {};
this.originalColor = getComputedStyle(document.documentElement).getPropertyValue("--star-color").trim();
this._createStars();
window.stars = this.positionMap;
this.patchConsole()
this.starSignal = (() => {
const positionMap = this.positionMap;
const areaMap = {
Center: 'Center',
Corner: 'Corner',
Edge: 'Edge',
North: 'North',
South: 'South',
East: 'East',
West: 'West',
All: Object.keys(positionMap),
};
const applyEffect = (stars, effectFn, duration = 2000) => {
const active = new Set();
stars.forEach((star, i) => {
const { cleanup } = effectFn(star, i);
active.add(cleanup);
});
setTimeout(() => {
active.forEach(fn => fn && fn());
}, duration);
};
const effects = {
pulseColor: (color, size = 5) => (star) => {
const orig = {
color: star.style.backgroundColor,
width: star.style.width,
height: star.style.height,
shadow: star.style.boxShadow
};
star.style.backgroundColor = color;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.boxShadow = `0 0 ${size * 2}px ${color}`;
return {
cleanup: () => {
star.style.backgroundColor = orig.color;
star.style.width = orig.width;
star.style.height = orig.height;
star.style.boxShadow = orig.shadow;
}
};
},
flicker: (color) => (star) => {
let visible = true;
const orig = {
color: star.style.backgroundColor,
shadow: star.style.boxShadow
};
const flick = setInterval(() => {
star.style.backgroundColor = visible ? color : '';
star.style.boxShadow = visible ? `0 0 4px ${color}` : '';
visible = !visible;
}, 200);
return {
cleanup: () => {
clearInterval(flick);
star.style.backgroundColor = orig.color;
star.style.boxShadow = orig.shadow;
}
};
},
shimmer: (colors) => (star, i) => {
const orig = star.style.backgroundColor;
let idx = 0;
const interval = setInterval(() => {
star.style.backgroundColor = colors[idx % colors.length];
idx++;
}, 100 + (i % 5) * 10);
return {
cleanup: () => {
clearInterval(interval);
star.style.backgroundColor = orig;
}
};
}
};
const trigger = (signalName) => {
switch (signalName) {
// --- Notifications ---
case 'notif.newDM':
applyEffect(positionMap[areaMap.Corner], effects.pulseColor('white'));
break;
case 'notif.groupMsg':
applyEffect(positionMap[areaMap.Edge], effects.flicker('blue'), 1000);
break;
case 'notif.mention':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('magenta'));
break;
case 'notif.react':
applyEffect(positionMap[areaMap.South], effects.pulseColor('gold'), 1200);
break;
case 'notif.typing':
applyEffect(positionMap[areaMap.West], effects.pulseColor('teal'), 1500);
break;
case 'notif.msgSent':
applyEffect(positionMap[areaMap.East], effects.pulseColor('lightgreen'), 800);
break;
case 'notif.msgRead':
applyEffect(positionMap[areaMap.North], effects.pulseColor('lightblue'), 1000);
break;
// --- User Status ---
case 'status.online':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('green'), 3000);
break;
case 'status.offline':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('gray'), 3000);
break;
case 'status.idle':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('orange'), 3000);
break;
case 'status.typing':
applyEffect(positionMap[areaMap.Center], effects.flicker('lightblue'), 2000);
break;
case 'status.join':
applyEffect(positionMap[areaMap.Edge], effects.pulseColor('cyan'), 1500);
break;
case 'status.leave':
applyEffect(positionMap[areaMap.Edge], effects.pulseColor('black'), 1500);
break;
// --- System ---
case 'sys.broadcast':
areaMap.All.forEach(area => {
applyEffect(positionMap[area], effects.pulseColor('gold'), 1000);
});
break;
case 'sys.warning':
areaMap.All.forEach(area => {
applyEffect(positionMap[area], effects.flicker('red'), 2000);
});
break;
case 'sys.down':
areaMap.All.forEach(area => {
applyEffect(positionMap[area], effects.flicker('darkred'), 3000);
});
break;
case 'sys.update':
applyEffect(positionMap[areaMap.North], effects.shimmer(['cyan', 'violet', 'lime']), 2500);
break;
case 'sys.ping':
areaMap.All.forEach(area => {
applyEffect(positionMap[area], effects.pulseColor('white'), 500);
});
break;
// --- Activity & Reactions ---
case 'activity.surge':
applyEffect(positionMap[areaMap.Center], effects.shimmer(['white', 'blue', 'purple']), 1500);
break;
case 'reaction.burst':
applyEffect(positionMap[areaMap.South], effects.pulseColor('hotpink'), 1000);
break;
case 'reaction.lol':
applyEffect(positionMap[areaMap.South], effects.flicker('yellow'), 800);
break;
case 'reaction.likes':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('deeppink'), 1000);
break;
// --- Focus & Movement ---
case 'focus.zone':
applyEffect(positionMap[areaMap.Center], effects.pulseColor('white'), 1500);
break;
case 'focus.enter':
applyEffect(positionMap[areaMap.West], effects.pulseColor('cyan'), 1000);
break;
case 'focus.exit':
applyEffect(positionMap[areaMap.East], effects.pulseColor('gray'), 1000);
break;
case 'focus.idle':
applyEffect(positionMap[areaMap.North], effects.flicker('gray'), 2000);
break;
default:
console.warn('Unknown star signal:', signalName);
}
this.trigger = this.starSignal.trigger;
};
return { trigger };
})();
}
showLogEvent(...args) {
//this.showNotify(...args)
}
mapLogLevel(level) {
switch (level) {
case 'info':
return { category: 'Info', color: 'skyblue' };
case 'warn':
return { category: 'Warning', color: 'orange' };
case 'error':
return { category: 'Error', color: 'crimson' };
case 'log':
default:
return { category: 'Log', color: 'gold' };
}
}
patchConsole(){
const originalConsole = {
info: console.info,
warn: console.warn,
error: console.error,
log: console.log,
};
const me = this
// Override console methods
console.info = function(...args) {
me.showLogEvent('info', args);
me.createConsoleStar('info', args);
originalConsole.info.apply(console, args);
};
console.warn = function(...args) {
me.showLogEvent('warn', args);
me.createConsoleStar('warn', args);
originalConsole.warn.apply(console, args);
};
console.error = function(...args) {
me.showLogEvent('error', args);
me.createConsoleStar('error', args);
originalConsole.error.apply(console, args);
};
console.log = function(...args) {
me.showLogEvent('log', args);
me.createConsoleStar('log', args);
originalConsole.log.apply(console, args);
};
}
_getStarPosition(star) {
const left = parseFloat(star.style.left);
const top = parseFloat(star.style.top);
if (top < 40 && left >= 40 && left <= 60) return "North";
if (top > 60 && left >= 40 && left <= 60) return "South";
if (left < 40 && top >= 40 && top <= 60) return "West";
if (left > 60 && top >= 40 && top <= 60) return "East";
if (top >= 40 && top <= 60 && left >= 40 && left <= 60) return "Center";
return "Corner or Edge";
}
_createStars() {
for (let i = 0; i < this.starCount; i++) {
const star = document.createElement("div");
star.classList.add("star");
this._randomizeStar(star);
this._placeStar(star);
this.container.appendChild(star);
this.stars.push(star);
}
}
_randomizeStar(star) {
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 100}%`;
star.style.width = `${Math.random() * 2 + 1}px`;
star.style.height = `${Math.random() * 2 + 1}px`;
star.style.animationDelay = `${Math.random() * 2}s`;
star.style.position = "absolute";
star.style.transition = "top 1s ease, left 1s ease, opacity 1s ease";
star.shuffle = () => this._randomizeStar(star);
star.position = this._getStarPosition(star);
}
createConsoleStar(level, args) {
const { category, color } = this.mapLogLevel(level);
const message = args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
this.addSpecialStar({
title: category.toUpperCase(),
content: message,
category,
color,
onClick: () => {
this.showNotify(level, [`[${category}]`, message]);
},
});
}
_placeStar(star) {
const pos = star.position;
if (!this.positionMap[pos]) this.positionMap[pos] = [];
this.positionMap[pos].push(star);
}
shuffleAll(duration = 1000) {
this.stars.forEach(star => {
star.style.transition = `top ${duration}ms ease, left ${duration}ms ease, opacity ${duration}ms ease`;
star.style.filter = "drop-shadow(0 0 2px white)";
const left = Math.random() * 100;
const top = Math.random() * 100;
star.style.left = `${left}%`;
star.style.top = `${top}%`;
setTimeout(() => {
star.style.filter = "";
star.position = this._getStarPosition(star);
}, duration);
});
}
glowColor(tempColor, duration = 2500) {
const lighten = (hex, percent) => {
const num = parseInt(hex.replace("#", ""), 16);
const r = Math.min(255, (num >> 16) + Math.round(255 * percent / 100));
const g = Math.min(255, ((num >> 8) & 0xff) + Math.round(255 * percent / 100));
const b = Math.min(255, (num & 0xff) + Math.round(255 * percent / 100));
return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
};
const glow = lighten(tempColor, 10);
document.documentElement.style.setProperty("--star-color", glow);
setTimeout(() => {
document.documentElement.style.setProperty("--star-color", this.originalColor);
}, duration);
}
showNotify(text, { duration = 3000, color = "white", fontSize = "1.2em" } = {}) {
// Create container if needed
if (!this._notifyContainer) {
this._notifyContainer = document.createElement("div");
this._notifyContainer.className = "star-notify-container";
document.body.appendChild(this._notifyContainer);
}
const messages = document.querySelectorAll('.star-notify');
const count = Array.from(messages).filter(el => el.textContent.trim() === text).length;
if (count) return;
const note = document.createElement("div");
note.className = "star-notify";
note.textContent = text;
note.style.color = color;
note.style.fontSize = fontSize;
this._notifyContainer.appendChild(note);
// Trigger animation
setTimeout(() => {
note.style.opacity = 1;
note.style.transform = "translateY(0)";
}, 10);
// Remove after duration
setTimeout(() => {
note.style.opacity = 0;
note.style.transform = "translateY(-10px)";
setTimeout(() => note.remove(), 500);
}, duration);
}
renderWord(word, { font = "bold", resolution = 8, duration = 1500, rainbow = false } = {}) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = this.container.clientWidth;
canvas.height = this.container.clientHeight / 3;
const fontSize = Math.floor(canvas.height / 2.5);
ctx.font = `${fontSize}px sans-serif`;
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(word, canvas.width / 2, canvas.height / 2);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const targetPositions = [];
for (let y = 0; y < canvas.height; y += resolution) {
for (let x = 0; x < canvas.width; x += resolution) {
const i = (y * canvas.width + x) * 4;
if (imageData[i + 3] > 128) {
targetPositions.push([
(x / canvas.width) * 100,
(y / canvas.height) * 100,
]);
}
}
}
targetPositions.sort(() => Math.random() - 0.5);
const used = targetPositions.slice(0, this.stars.length);
this.stars.forEach((star, i) => {
star.style.transition = `top ${duration}ms ease, left ${duration}ms ease, opacity ${duration}ms ease, background-color 1s ease`;
if (i < used.length) {
const [left, top] = used[i];
star.style.left = `${left}%`;
star.style.top = `${top}%`;
star.style.opacity = 1;
if (rainbow) {
const hue = (i / used.length) * 360;
star.style.backgroundColor = `hsl(${hue}, 100%, 70%)`;
}
} else {
star.style.opacity = 0;
}
});
setTimeout(() => {
this.stars.forEach(star => {
star.position = this._getStarPosition(star);
if (rainbow) star.style.backgroundColor = "";
});
}, duration);
}
explodeAndReturn(duration = 1000) {
const originalPositions = this.stars.map(star => ({
left: star.style.left,
top: star.style.top
}));
this.stars.forEach(star => {
const angle = Math.random() * 2 * Math.PI;
const radius = Math.random() * 200;
const x = 50 + Math.cos(angle) * radius;
const y = 50 + Math.sin(angle) * radius;
star.style.transition = `top ${duration / 2}ms ease-out, left ${duration / 2}ms ease-out`;
star.style.left = `${x}%`;
star.style.top = `${y}%`;
});
setTimeout(() => {
this.stars.forEach((star, i) => {
star.style.transition = `top ${duration}ms ease-in, left ${duration}ms ease-in`;
star.style.left = originalPositions[i].left;
star.style.top = originalPositions[i].top;
});
}, duration / 2);
}
startColorCycle() {
let hue = 0;
if (this._colorInterval) clearInterval(this._colorInterval);
this._colorInterval = setInterval(() => {
hue = (hue + 2) % 360;
this.stars.forEach((star, i) => {
star.style.backgroundColor = `hsl(${(hue + i * 3) % 360}, 100%, 75%)`;
});
}, 100);
}
stopColorCycle() {
if (this._colorInterval) {
clearInterval(this._colorInterval);
this._colorInterval = null;
this.stars.forEach(star => star.style.backgroundColor = "");
}
}
addSpecialStar({ title, content, category = "Info", color = "gold", onClick }) {
const star = this.stars.find(s => !s._dataAttached);
if (!star) return;
star.classList.add("special");
star.style.backgroundColor = color;
star._dataAttached = true;
star._specialData = { title, content, category, color, onClick };
const tooltip = document.getElementById("star-tooltip");
const showTooltip = (e) => {
tooltip.innerText = `${title} (${category})`;
tooltip.style.display = "block";
tooltip.style.left = `${e.clientX + 10}px`;
tooltip.style.top = `${e.clientY + 10}px`;
};
const hideTooltip = () => tooltip.style.display = "none";
star.addEventListener("mouseenter", showTooltip);
star.addEventListener("mouseleave", hideTooltip);
star.addEventListener("mousemove", showTooltip);
const showPopup = (e) => {
e.stopPropagation();
this._showPopup(star, star._specialData);
if (onClick) onClick(star._specialData);
};
star.addEventListener("click", showPopup);
star.addEventListener("touchend", showPopup);
return star;
}
_showPopup(star, data) {
const popup = document.getElementById("star-popup");
popup.innerHTML = `<strong>${data.title}</strong><br><small>${data.category}</small><div style="margin-top: 5px;">${data.content}</div>`;
popup.style.display = "block";
const rect = star.getBoundingClientRect();
popup.style.left = `${rect.left + window.scrollX + 10}px`;
popup.style.top = `${rect.top + window.scrollY + 10}px`;
const closeHandler = () => {
popup.style.display = "none";
document.removeEventListener("click", closeHandler);
};
setTimeout(() => document.addEventListener("click", closeHandler), 100);
}
removeSpecialStar(star) {
if (!star._specialData) return;
star.classList.remove("special");
delete star._specialData;
star._dataAttached = false;
const clone = star.cloneNode(true);
star.replaceWith(clone);
const index = this.stars.indexOf(star);
if (index >= 0) this.stars[index] = clone;
}
getSpecialStars() {
return this.stars
.filter(star => star._specialData)
.map(star => ({
star,
data: star._specialData
}));
}
}
const starField = new StarField({starCount: 200});
app.starField = starField;
class DemoSequence {
constructor(steps = []) {
this.steps = steps;
this.overlay = document.createElement("div");
this.overlay.className = "demo-overlay";
document.body.appendChild(this.overlay);
}
start() {
this._runStep(0);
}
_runStep(index) {
if (index >= this.steps.length) {
this._clearOverlay();
return;
}
const { target, text, duration = 3000 } = this.steps[index];
this._clearHighlights();
this._showOverlay(text);
if (target) {
const el = typeof target === 'string' ? document.querySelector(target) : target;
if (el) {
el.classList.remove("demo-highlight");
void el.offsetWidth; // force reflow to reset animation
el.classList.add("demo-highlight");
el.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
setTimeout(() => {
this._hideOverlay();
this._runStep(index + 1);
}, duration);
}
_showOverlay(text) {
this.overlay.innerText = text;
this.overlay.style.animation = "demoFadeIn 0.8s ease forwards";
}
_hideOverlay() {
this.overlay.style.opacity = 0;
this._clearHighlights();
}
_clearOverlay() {
this.overlay.remove();
this._clearHighlights();
}
_clearHighlights() {
document.querySelectorAll(".demo-highlight").forEach(el => {
el.classList.remove("demo-highlight");
});
}
}
/*
const demo = new DemoSequence([
{
text: "💬 Welcome to the Snek Developer Community!",
duration: 3000
},
{
target: ".channels-list",
text: "🔗 Channels help you organize conversations.",
duration: 3000
},
{
target: ".user-icon",
text: "👥 Invite team members here.",
duration: 3000
},
{
target: ".chat-input",
text: "⌨️ Type your message and hit Enter!",
duration: 3000
}
]);*/
// Start when ready (e.g., after load or user action)
//demo.start();
</script>