home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

waow i really like doing config overhauls don't i + cursor improvements

+208 -62
+98 -24
public/script/config.js
··· 1 - const DEFAULT_CONFIG = { 2 - crt: false 3 - }; 4 - const config = (() => { 5 - let saved = localStorage.getItem("config"); 6 - if (saved) { 7 - const config = JSON.parse(saved); 8 - setCRT(config.crt || DEFAULT_CONFIG.crt); 9 - return config; 1 + const ARIMELODY_CONFIG_NAME = "arimelody.me-config"; 2 + 3 + class Config { 4 + _crt = false; 5 + _cursor = false; 6 + _cursorFunMode = false; 7 + 8 + /** @type Map<string, Array<Function>> */ 9 + #listeners = new Map(); 10 + 11 + constructor(values) { 12 + function thisOrElse(values, name, defaultValue) { 13 + if (values === null) return defaultValue; 14 + if (values[name] === undefined) return defaultValue; 15 + return values[name]; 16 + } 17 + 18 + this.#listeners.set('crt', new Array()); 19 + this.crt = thisOrElse(values, 'crt', false); 20 + this.#listeners.set('cursor', new Array()); 21 + this.cursor = thisOrElse(values, 'cursor', false); 22 + this.#listeners.set('cursorFunMode', new Array()); 23 + this.cursorFunMode = thisOrElse(values, 'cursorFunMode', false); 24 + this.save(); 25 + } 26 + 27 + /** 28 + * Appends a listener function to be called when the config value of `name` 29 + * is changed. 30 + */ 31 + addListener(name, callback) { 32 + const callbacks = this.#listeners.get(name); 33 + if (!callbacks) return; 34 + callbacks.push(callback); 10 35 } 11 36 12 - localStorage.setItem("config", JSON.stringify(DEFAULT_CONFIG)); 13 - return DEFAULT_CONFIG; 14 - })(); 37 + /** 38 + * Removes the listener function `callback` from the list of callbacks when 39 + * the config value of `name` is changed. 40 + */ 41 + removeListener(name, callback) { 42 + const callbacks = this.#listeners.get(name); 43 + if (!callbacks) return; 44 + callbacks.set(name, callbacks.filter(c => c !== callback)); 45 + } 15 46 16 - function saveConfig() { 17 - localStorage.setItem("config", JSON.stringify(config)); 47 + save() { 48 + localStorage.setItem(ARIMELODY_CONFIG_NAME, JSON.stringify({ 49 + crt: this.crt, 50 + cursor: this.cursor, 51 + cursorFunMode: this.cursorFunMode 52 + })); 53 + } 54 + 55 + get crt() { return this._crt } 56 + set crt(/** @type boolean */ enabled) { 57 + this._crt = enabled; 58 + 59 + if (enabled) { 60 + document.body.classList.add("crt"); 61 + } else { 62 + document.body.classList.remove("crt"); 63 + } 64 + document.getElementById('toggle-crt').className = enabled ? "" : "disabled"; 65 + 66 + this.#listeners.get('crt').forEach(callback => { 67 + callback(this._crt); 68 + }) 69 + 70 + this.save(); 71 + } 72 + 73 + get cursor() { return this._cursor } 74 + set cursor(/** @type boolean */ value) { 75 + this._cursor = value; 76 + this.#listeners.get('cursor').forEach(callback => { 77 + callback(this._cursor); 78 + }) 79 + this.save(); 80 + } 81 + 82 + get cursorFunMode() { return this._cursorFunMode } 83 + set cursorFunMode(/** @type boolean */ value) { 84 + this._cursorFunMode = value; 85 + this.#listeners.get('cursorFunMode').forEach(callback => { 86 + callback(this._cursorFunMode); 87 + }) 88 + this.save(); 89 + } 18 90 } 19 91 92 + const config = (() => { 93 + let values = null; 94 + 95 + const saved = localStorage.getItem(ARIMELODY_CONFIG_NAME); 96 + if (saved) 97 + values = JSON.parse(saved); 98 + 99 + return new Config(values); 100 + })(); 101 + 20 102 document.getElementById("toggle-crt").addEventListener("click", () => { 21 103 config.crt = !config.crt; 22 - setCRT(config.crt); 23 - saveConfig(); 24 104 }); 25 105 26 - function setCRT(/** @type boolean */ enabled) { 27 - if (enabled) { 28 - document.body.classList.add("crt"); 29 - } else { 30 - document.body.classList.remove("crt"); 31 - } 32 - document.getElementById('toggle-crt').className = enabled ? "" : "disabled"; 33 - } 106 + window.config = config; 107 + export default config;
+87 -35
public/script/cursor.js
··· 1 + import config from './config.js'; 2 + 1 3 const CURSOR_TICK_RATE = 1000/30; 2 4 const CURSOR_LERP_RATE = 1/100; 3 5 const CURSOR_CHAR_MAX_LIFE = 5000; 4 6 const CURSOR_MAX_CHARS = 50; 7 + 5 8 /** @type HTMLElement */ 6 9 let cursorContainer; 7 10 /** @type Cursor */ ··· 11 14 /** @type Array<FunChar> */ 12 15 let chars = new Array(); 13 16 17 + let running = false; 14 18 let lastCursorUpdateTime = 0; 15 19 let lastCharUpdateTime = 0; 16 20 ··· 42 46 this.rx = x; 43 47 this.ry = y; 44 48 45 - const element = document.createElement("i"); 46 - element.classList.add("cursor"); 47 - element.id = "cursor" + id; 49 + const element = document.createElement('i'); 50 + element.classList.add('cursor'); 51 + element.id = 'cursor' + id; 48 52 const colour = randomColour(); 49 53 element.style.borderColor = colour; 50 54 element.style.color = colour; 51 - element.innerText = "0x" + navigator.userAgent.hashCode(); 55 + element.innerText = '0x' + navigator.userAgent.hashCode(); 52 56 53 - const char = document.createElement("p"); 54 - char.className = "char"; 57 + const char = document.createElement('p'); 58 + char.className = 'char'; 55 59 element.appendChild(char); 56 60 57 61 this.#element = element; ··· 61 65 62 66 destroy() { 63 67 this.#element.remove(); 68 + this.#char.remove(); 64 69 } 65 70 66 71 /** ··· 78 83 update(deltaTime) { 79 84 this.rx += (this.x - this.rx) * CURSOR_LERP_RATE * deltaTime; 80 85 this.ry += (this.y - this.ry) * CURSOR_LERP_RATE * deltaTime; 81 - this.#element.style.left = this.rx + "px"; 82 - this.#element.style.top = this.ry + "px"; 86 + this.#element.style.left = this.rx + 'px'; 87 + this.#element.style.top = this.ry + 'px'; 83 88 } 84 89 85 90 /** ··· 88 93 print(text) { 89 94 if (text.length > 1) return; 90 95 this.#char.innerText = text; 96 + } 97 + 98 + /** 99 + * @param {boolean} active 100 + */ 101 + click(active) { 102 + if (active) 103 + this.#element.classList.add('click'); 104 + else 105 + this.#element.classList.remove('click'); 91 106 } 92 107 } 93 108 ··· 114 129 this.ra = this.r * 0.01; 115 130 this.life = 0; 116 131 117 - const char = document.createElement("i"); 118 - char.className = "funchar"; 132 + const char = document.createElement('i'); 133 + char.className = 'funchar'; 119 134 char.innerText = text; 120 - char.style.left = x + "px"; 121 - char.style.top = y + "px"; 135 + char.style.left = x + 'px'; 136 + char.style.top = y + 'px'; 122 137 char.style.transform = `rotate(${this.r}deg)`; 123 138 this.element = char; 124 139 cursorContainer.appendChild(this.element); ··· 130 145 update(deltaTime) { 131 146 this.life += deltaTime; 132 147 if (this.life > CURSOR_CHAR_MAX_LIFE || 133 - this.y > window.outerHeight || 148 + this.y > document.body.clientHeight || 134 149 this.x < 0 || 135 - this.x > window.outerWidth 150 + this.x > document.body.clientWidth 136 151 ) { 137 152 this.destroy(); 138 153 return; ··· 143 158 this.r += this.ra * deltaTime; 144 159 this.ya = Math.min(this.ya + 0.0005 * deltaTime, 10); 145 160 146 - this.element.style.left = this.x + "px"; 147 - this.element.style.top = this.y + "px"; 161 + this.element.style.left = (this.x - window.scrollX) + 'px'; 162 + this.element.style.top = (this.y - window.scrollY) + 'px'; 148 163 this.element.style.transform = `rotate(${this.r}deg)`; 149 164 } 150 165 ··· 175 190 const green = Math.round((min + Math.random() * range)).toString(16); 176 191 const blue = Math.round((min + Math.random() * range)).toString(16); 177 192 178 - return "#" + red + green + blue; 193 + return '#' + red + green + blue; 179 194 } 180 195 181 196 /** ··· 186 201 myCursor.move(event.x, event.y); 187 202 } 188 203 204 + function handleMouseDown() { 205 + myCursor.click(true); 206 + } 207 + function handleMouseUp() { 208 + myCursor.click(false); 209 + } 210 + 189 211 /** 190 212 * @param {KeyboardEvent} event 191 213 */ 192 214 function handleKeyDown(event) { 193 215 if (event.key.length > 1) return; 194 216 if (event.metaKey || event.ctrlKey) return; 195 - if (window.cursorFunMode === true) { 217 + if (config.cursorFunMode === true) { 196 218 const yOffset = -20; 197 219 const accelMultiplier = 0.002; 198 220 if (chars.length < CURSOR_MAX_CHARS) 199 221 chars.push(new FunChar( 200 - myCursor.x, myCursor.y + yOffset, 222 + myCursor.x + window.scrollX, myCursor.y + window.scrollY + yOffset, 201 223 (myCursor.x - myCursor.rx) * accelMultiplier, (myCursor.y - myCursor.ry) * accelMultiplier, 202 224 event.key)); 203 225 } else { ··· 206 228 } 207 229 208 230 function handleKeyUp() { 209 - if (!window.cursorFunMode) { 210 - myCursor.print(""); 231 + if (!config.cursorFunMode) { 232 + myCursor.print(''); 211 233 } 212 234 } 213 235 ··· 215 237 * @param {number} time 216 238 */ 217 239 function updateCursors(time) { 240 + if (!running) return; 241 + 218 242 const deltaTime = time - lastCursorUpdateTime; 219 243 220 244 cursors.forEach(cursor => { ··· 229 253 * @param {number} time 230 254 */ 231 255 function updateChars(time) { 256 + if (!running) return; 257 + 232 258 const deltaTime = time - lastCharUpdateTime; 233 259 234 260 chars.forEach(char => { ··· 240 266 } 241 267 242 268 function cursorSetup() { 243 - window.cursorFunMode = false; 244 - cursorContainer = document.createElement("div"); 245 - cursorContainer.id = "cursors"; 269 + if (running) throw new Error('Only one instance of Cursor can run at a time.'); 270 + running = true; 271 + 272 + cursorContainer = document.createElement('div'); 273 + cursorContainer.id = 'cursors'; 246 274 document.body.appendChild(cursorContainer); 275 + 247 276 myCursor = new Cursor(0, window.innerWidth / 2, window.innerHeight / 2); 248 277 cursors.set(0, myCursor); 249 - document.addEventListener("mousemove", handleMouseMove); 250 - document.addEventListener("keydown", handleKeyDown); 251 - document.addEventListener("keyup", handleKeyUp); 278 + 279 + document.addEventListener('mousemove', handleMouseMove); 280 + document.addEventListener('mousedown', handleMouseDown); 281 + document.addEventListener('mouseup', handleMouseUp); 282 + document.addEventListener('keydown', handleKeyDown); 283 + document.addEventListener('keyup', handleKeyUp); 284 + 252 285 requestAnimationFrame(updateCursors); 253 286 requestAnimationFrame(updateChars); 287 + 254 288 console.debug(`Cursor tracking @ ${window.location.pathname}`); 255 289 } 256 290 257 291 function cursorDestroy() { 258 - document.removeEventListener("mousemove", handleMouseMove); 259 - document.removeEventListener("keydown", handleKeyDown); 260 - document.removeEventListener("keyup", handleKeyUp); 292 + if (!running) return; 293 + 294 + document.removeEventListener('mousemove', handleMouseMove); 295 + document.removeEventListener('mousedown', handleMouseDown); 296 + document.removeEventListener('mouseup', handleMouseUp); 297 + document.removeEventListener('keydown', handleKeyDown); 298 + document.removeEventListener('keyup', handleKeyUp); 299 + 300 + chars.forEach(char => { 301 + char.destroy(); 302 + }); 303 + chars = new Array(); 261 304 cursors.forEach(cursor => { 262 305 cursor.destroy(); 263 306 }); 264 307 cursors.clear(); 265 - chars.forEach(cursor => { 266 - cursor.destroy(); 267 - }); 268 - chars = new Array(); 269 308 myCursor = null; 309 + 310 + cursorContainer.remove(); 311 + 270 312 console.debug(`Cursor no longer tracking.`); 313 + running = false; 314 + } 315 + 316 + if (config.cursor === true) { 317 + cursorSetup(); 271 318 } 272 319 273 - cursorSetup(); 320 + config.addListener('cursor', enabled => { 321 + if (enabled === true) 322 + cursorSetup(); 323 + else 324 + cursorDestroy(); 325 + });
+23 -3
public/style/cursor.css
··· 13 13 font-size: 10px; 14 14 font-weight: bold; 15 15 white-space: nowrap; 16 + } 16 17 17 - user-select: none; 18 - pointer-events: none; 18 + #cursors i.cursor.click { 19 + color: var(--on-background) !important; 20 + border-color: var(--on-background) !important; 19 21 } 20 22 21 23 #cursors i.cursor .char { ··· 27 29 } 28 30 29 31 #cursors .funchar { 30 - position: fixed; 32 + position: absolute; 31 33 margin: 0; 32 34 33 35 display: block; ··· 37 39 font-size: 20px; 38 40 font-weight: bold; 39 41 color: var(--on-background); 42 + } 43 + 44 + #cursors { 45 + width: 100vw; 46 + height: 100vh; 47 + position: fixed; 48 + top: 0; 49 + left: 0; 50 + 51 + z-index: 1000; 52 + overflow: clip; 40 53 41 54 user-select: none; 42 55 pointer-events: none; 43 56 } 57 + 58 + @media (prefers-color-scheme: light) { 59 + #cursors i.cursor { 60 + filter: saturate(5) brightness(0.8); 61 + background: #fff8; 62 + } 63 + }