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;