diff --git a/src/snek/templates/sandbox.html b/src/snek/templates/sandbox.html
index 53e9d48..cdb7896 100644
--- a/src/snek/templates/sandbox.html
+++ b/src/snek/templates/sandbox.html
@@ -12,6 +12,250 @@ class StarField {
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) {
@@ -48,6 +292,21 @@ class StarField {
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;