1// /assets/mascot.js 2(function () { 3 // Signal that script loaded 4 window.__mascotReady = true; 5 try { console.debug('[mascot] handler ready'); } catch (e) {} 6 7 const PHRASES = [ 8 // Existing tone 9 "hi there!", 10 "hi", 11 "hey", 12 "this oneâs good", 13 "a tidy little read", 14 "fresh from the forest", 15 "mind the corners", 16 "take a peek", 17 "only takes a minute", 18 "i like webpages", 19 "i can't leave the browser", 20 "i am stuck", 21 "it's okay we're happy in here", 22 "this one is polite", 23 "a calm little corner", 24 "compact situation inside", 25 "a manageable amount of text", 26 "gently opinionated content", 27 "small but strangely specific", 28 "it fits in your brain probably", 29 "this one knows what itâs doing", 30 "a small card about something", 31 "nothing too dramatic in here", 32 33 // Playful and self-aware 34 "someone made this with words", 35 "I found this", 36 "you can almost imagine cilcking", 37 "i like scrolling", 38 "touch grass", 39 "please admire", 40 "thanks for clicking me", 41 "scrolling is fun", 42 "crafted in the undergrowth", 43 "artifact detected", 44 "itâs not broken", 45 "try pressing it againâbut slower", 46 "user delight achieved", 47 "someone polished this up for you", 48 "iâve been guarding this one", 49 "i pre-scanned this on your behalf", 50 "handpicked by a small algorithm", 51 "curated by a semi-reliable creature", 52 "someone worried about this sentence", 53 "iâve been standing here the whole time", 54 "you clicked. that was the hard part", 55 "all this just to get you here", 56 "a reasonable use of attention", 57 "world simulation loadingâ¦", 58 "local minimum of meaning reached", 59 "water keeps happening on everything", 60 "nothing glows on purpose", 61 "rain learns nothing from falling", 62 "the air folds around everything", 63 "the leaves keep practicing. sort of annoying", 64 "the air has regional settings", 65 66 // Meta and Bogostian 67 "do you like to click?", 68 "powered by metaphors", 69 "breathe.", 70 "this one argues with itself", 71 "meant to be overthought", 72 "a visitor mistook me for something else", 73 "nighttime makes everyone a little philosophical", 74 "is it daytime yet, or again?", 75 "i heard a rumor", 76 "object-oriented feelings ahead", 77 "try treating this as an artifact", 78 "gratification may occur", 79 "maintenance is the real protagonist", 80 "nothing here is metaphor", 81 82 // Natural and creaturely 83 "chirp!", 84 "oh sorry i was sleeping", 85 "rustle rustle", 86 "i live in your cache", 87 "i am on your computer now", 88 "tiny forest bureaucracy", 89 "donât mind me", 90 "composting ideas", 91 "sprouted overnight", 92 "the forest is thinking about you", 93 "mildly enchanted", 94 "I have opinions", 95 "I too am made of markup", 96 "the computer remembers everything", 97 "almost too quiet", 98 "bufferingâ¦", 99 "still loadingâ¦", 100 "omg i didn't see you there", 101 "not right now", 102 "hey can you come back later?", 103 "one sec", 104 "remember that time you clicked me", 105 "this website is woodland-themed, i guess", 106 "i hope nobody eats me", 107 "i was just listening to the dirt", 108 "the moss filed a report about you", 109 "some leaves asked me to show you this", 110 "i live between the pixels", 111 "this grew in the understory", 112 "the forest union approved this card", 113 "small woodland paperwork in progress", 114 "the birds have no opinion on this", 115 "i am technically a wetland feature", 116 "local microfauna endorse this message", 117 "i specialize in doing nothing", 118 "things usually grow because nobody stopped them", 119 "imagine how tired i am after doing this every day", 120 "big projects like to hum in the background, to remind you", 121 "once there were more of us, but i try not to dwell on it", 122 "some stuff appears overnight and pretends it was always there", 123 "mushrooms show up late and still get credit", 124 "a small thing can change everything", 125 "i'll be gone before you know it", 126 "a tidy archive of things you thought didn't matter",
127 "someone told me to wait, but i did not", 128 "i am smaller than my opinions", 129 "the draft version is a real thing too", 130 "i am disproportionately invested in everything", 131 "nobody was listening but i said it anyway", 132 "falling apart is one of the approved methods of becoming soil", 133 "wisdom is just keeping your eyes open longer", 134 "squirrels are wrong about almost everything but thrive anyway", 135 "i am built for caution", 136 "not every season needs a thesis.", 137 "i learned most of those things sleeping", 138 "half of these tunnels are for things that never happen", 139 "the crumbs collect where nobody looks", 140 "i choose to miss a lot on purpose", 141 "do what you can get away with, i guess", 142 143 // Gentle absurdities 144 "i stepped in something wet", 145 "do you ever think about existence", 146 "contains trace amounts of sincerity", 147 "freshly extracted", 148 "powered by longing", 149 "do not attempt to rationalize", 150 "i don't think we do anything", 151 "mossbait", 152 "semiotics ahead", 153 "if you know, you know", 154 "made of the same stuff as clouds", 155 "it hurts when you scroll", 156 "please admire this affordance", 157 "i heard people like the owl best", 158 "spores released", 159 "contentment not guaranteed", 160 "i miss them too", 161 "i tripped over a concept back there", 162 "do you ever misread on purpose", 163 "contains trace amounts of bureaucracy", 164 "powered by mild anxiety and html", 165 "everything here is technically true-ish", 166 "do not show this to your therapist", 167 "this paragraph is union-compliant", 168 "slightly haunted but in a nice way", 169 "the scrollbar knows what you did", 170 "recommended daily allowance of nonsense", 171 "one point for you" 172 ]; 173 174 const ALREADY_CLICKED = [ 175 "still thinking about that last one", 176 "no need to overdo it", 177 "iâll remember that", 178 "that feels strange", 179 "that tickles", 180 "if you click long enoughâ¦", 181 "kind of fun to do it again, huh", 182 "we just did this", 183 "i remember that click", 184 "you seem committed to this", 185 "okay, one more time", 186 "iâm starting to feel déjà vu", 187 "this is becoming a little ritual", 188 "iâm not going anywhere, you know", 189 "i can't click anything. really makes you think." 190 ]; 191 192 const ALREADY_CLICKED_ENOUGH = [ 193 "i think you already clicked", 194 "no need to overdo it", 195 "um", 196 "â¦", 197 "did you need something?", 198 "i wish i could help", 199 "i promise nothing new happens", 200 "we have reached the end of my tricks", 201 "this might be a cry for help", 202 "iâm just a small graphic, friend", 203 "let's both take a tiny break", 204 "okay, iâm pretending that didn't happen", 205 "there is no secret achievement", 206 "the easter egg is that there isn't one", 207 "i also love clicking", 208 "it makes me a little sad", 209 "are you lonely?" 210 ]; 211 212 // --- Simple context-free grammar for main phrases --- 213 // You can extend/override via window.MASCOT_LEXICON / window.MASCOT_TEMPLATES 214 const LEXICON = Object.assign({ 215 // greetings / openers 216 greet: [ 217 "hi there", "hello", "oh, hi", "hey", "greetings", "hello again" 218 ], 219 220 // âââ additions to support the proposed templates âââ 221 greet_soft: ["oh, hello", "hi again", "hey there", "hello there"], 222 greet_short: ["hi", "hey", "yo"], 223 224 eval_simple: ["good", "solid", "nice", "worth it", "fine"], 225 adj_neat: ["tidy", "quick", "compact", "neat"], 226 artifact_read: ["read", "look", "scan", "skim"], 227 duration_short: ["sec", "second", "minute", "moment"], 228 imperative_soft: ["please", "maybe", "feel free to", "go ahead and"], 229 adj_quick: ["quick", "brief", "tiny", "short"], 230 231 place_undergrowth: ["undergrowth", "thicket", "understory"], 232 material_natural: ["dirt", "mulch", "moss", "bark", "paper"], 233 material_digital: ["code", "silicon", "markup", "protocol"], 234 verb_extracted: ["harvested", "compiled", "foraged", "pulled"], 235 place_human_small: ["desk", "bench", "server closet", "studio"], 236 237 ui_edge_plural: ["corners", "edges", "margins"], 238 detail_small_plural: ["details", "fine print", "bits", "little things"], 239 state_fine: ["fine", "good", "okay"], 240 241 web_thing_plural: ["webpages", "tabs", "links", "cards"], 242 verb_exit: ["leave", "escape", "quit"], 243 container_digital: ["browser", "tab", "iframe", "page"], 244 state_stuck: ["stuck", "cached", "pinned"], 245 affect_happy: ["happy", "fine", "content"], 246 container_here: ["here", "this tab", "this page"], 247 cache_like: ["cache", "local storage", "cookies"], 248 device: ["computer", "phone", "laptop"], 249 250 lowstakes_magic: ["lowâstakes magic", "small wonder", "minor delight"], 251 user_action: ["click", "scroll", "read"], 252 gerund_scrolling: ["scrolling", "reading", "clicking"], 253 254 // keep tone-aligned conceptual tokens used by earlier sets
255 infrastructure_like: ["infrastructure", "plumbing", "wiring"], 256 metaphor_plural: ["metaphors", "analogies", "figures"], 257 textual_ambience: ["textual ambience", "quiet vibe", "background signal"], 258 design_intent: ["designed that way", "intentional", "on purpose"], 259 adverb_slower: ["slower", "gently", "politely"], 260 academic_word: ["hermeneutics", "semiotics", "ontology"], 261 hedgerow_metaphor: ["hedgerow", "thicket", "understory"], 262 state_achieved: ["achieved", "obtained", "arrived"], 263 time_for_now: ["for now", "for a bit", "for the moment"], 264 meaning: ["meaning", "sense", "significance"], 265 cog_action: ["contemplate", "ponder", "notice"], 266 267 // adjectives (soft tone / play / conceptual / bureaucratic) 268 adj_soft: [ 269 "mossy", "leafy", "soft", "quiet", "gentle", "tiny", "lowâstakes", "minor", "calm", "loâfi", "unhurried" 270 ], 271 adj_playful: [ 272 "ludic", "sly", "cheerful", "wonky", "odd", "persuasive", "enchanted", "impish", "tinkery", "toyâlike" 273 ], 274 adj_concept: [ 275 "ontological", "procedural", "material", "phenomenological", "ambient", "maintenanceâminded", "gratificationâadjacent", "dematerialized" 276 ], 277 adj_bureau: [ 278 "quiet", "slow", "invisible", "everyday", "functional", "municipal" 279 ], 280 281 // verbs 282 verb_ing: [ 283 "composting", "buffering", "thinking", "listening", "loitering", "idling", "daydreaming", "carpentering", "patching", "tinkering" 284 ], 285 verb_trans: [ 286 "assemble", "compose", "persuade", "invite", "provoke", "misdirect", "undermine", "reframe" 287 ], 288 verb_cog: [ 289 "notice", "contemplate", "reckon with", "overthink", "misread", "reâread" 290 ], 291 verb_sense: [ 292 "smell", "touch", "taste", "hear", "see" 293 ], 294 295 // nouns 296 noun: ["<noun_creature>", "<noun_tech>", "<noun_idea>"], 297 noun_nature: [ 298 "hedgerow", "understory", "mulch", "thicket", "moss", "leaf litter", "bark", "glade", "liana", "rootball", "soil" 299 ], 300 noun_nature_plural: [ 301 "mushrooms", "leaves", "acorns", "cones", "sparrows", "crickets", "ferns", "lichens" 302 ], 303 noun_creature: [ 304 "fox", "owl", "badger", "hedgehog", "squirrel", "hare", "wren", "marten" 305 ], 306 noun_tech: [ 307 "interface", "server", "database", "RSS", "feed", "cache", "console", "protocol" 308 ], 309 noun_media: [ 310 "essay", "column", "feature", "thread", "note", "dispatch" 311 ], 312 noun_idea: [ 313 "affordance", "metaphor", "treatise", "artifact", "essaylet", "microâargument", "prototype", "ritual", "concept" 314 ], 315 316 // artifacts & textures 317 artifact: [ 318 "essay", "card", "note", "anecdote", "footnote", "aside", "toy", "gamelet", "diagram" 319 ], 320 texture: [ 321 "texture", "grain", "patina", "finish", "sheen", "burr" 322 ], 323 324 // places 325 place_nature: [ 326 "forest", "understory", "hedgerow", "glade", "grove", "creekside", "prairie edge" 327 ], 328 place_human: [ 329 "studio", "kitchen", "workbench", "lab", "seminar room", "server closet" 330 ], 331 332 // bureaucratic / media / material 333 bureaucracy: [ 334 "bureaucracy", "committee", "ledger", "registry", "form", "work order" 335 ], 336 medium: [ 337 "medium", "interface", "form", "machine" 338 ], 339 material: [ 340 "mulch", "paper", "silicon", "twine", "moss", "cardboard", "steel", "ink" 341 ], 342 343 // concepts (general + Bogost canon) 344 concept: [ 345 "embodied cognition", "attention", "gratification", "delight", "maintenance", "infrastructure", "play", "material thinking" 346 ], 347 concept_bogost: [ 348 "procedural rhetoric", "alien phenomenology", "carpentry", "objectâorientation", "design fiction", "persuasive games" 349 ], 350 351 // time & adverbs & imperatives 352 time: [ 353 "today", "right now", "for a minute", "briefly", "between tasks" 354 ], 355 adverb: [ 356 "softly", "gently", "politely", "briefly", "idly", "curiously" 357 ], 358 imperative: [ 359 "please", "kindly", "maybe", "feel free to", "go ahead and" 360 ] 361 }, window.MASCOT_LEXICON || {}); 362 363 const TEMPLATES = (window.MASCOT_TEMPLATES || [ 364 // 1) Greetings / quick openers 365 "<greet>!", 366 "<greet>", 367 "<greet_short> there!", 368 369 // 2) Quick evaluations / micro-recos 370 "this oneâs <eval_simple>", 371 "a <adj_neat> little <artifact_read>", 372 "only takes a <duration_short>", 373 "<imperative_soft> a <adj_quick> <artifact_read>", 374 "<imperative_soft> a <adj_quick> look", 375 376 // 3) Place of origin / provenance 377 "fresh from the <place_nature>", 378 "crafted in the <place_undergrowth>", 379 "made with <material_natural> and <material_digital>", 380 "freshly <verb_extracted>", 381 "fresh from the <place_human_small>", 382 383 // 4) Cautions / micro-instructions 384 "<imperative_soft> the <ui_edge_plural>", 385 "<imperative_soft> the <detail_small_plural>", 386 "<imperative_soft>, itâs <state_fine>", 387 388 // 5) Self-reference to the web/app container 389 "i like <web_thing_plural>", 390 "i canât <verb_exit> the <container_digital>", 391 "i am <state_stuck>", 392 "itâs okay, weâre <affect_happy> in <container_here>", 393 "i live in your <cache_like>", 394 "i am on your <device> now", 395
396 // 6) Light meta / playful self-awareness 397 "made with <material_natural> and <material_digital>", 398 "i found this under a <place_undergrowth>", 399 "you can almost smell the <material_natural>", 400 "a fine example of <lowstakes_magic>", 401 "please <imperative>", 402 "thanks for <user_action>", 403 "<gerund_scrolling> is <eval_simple>", 404 "artifact <eval_simple>", 405 "itâs not broken, itâs <design_intent>", 406 "try to <user_action> again â but <adverb_slower>", 407 "<academic_word> of the <hedgerow_metaphor>", 408 "user delight <state_achieved> (<time_for_now>)", 409 "world simulation <state_loading>", 410 "local minimum of <meaning> reached", 411 "tap to <cog_action>", 412 413 // 7) Bogostian meta / philosophy winks (using existing concept pools) 414 "what even is <concept>?", 415 "do you like to <user_action>?", 416 "feels like <infrastructure_like>", 417 "a small machine for <concept>", 418 "content for your <concept>", 419 "powered by <metaphor_plural>", 420 "<textual_ambience> detected", 421 "probably not about <concept>", 422 423 // 8) Creaturely / woodland interiority (kept simple; many are in PHRASES) 424 "donât mind me", 425 "<verb_ing> ideas", 426 "sprouted <time>", 427 "the forest is <verb_ing> about you", 428 "mildly enchanted", 429 "i have opinions", 430 431 // 9) Gentle absurdities / sly jokes 432 "verified by <material_natural>", 433 "now with 40% more <concept>", 434 "press any <noun_nature> to continue", 435 "contains trace amounts of <concept>", 436 "the medium is the <material_natural>", 437 "freshly <verb_extracted>", 438 "powered by <concept>", 439 "do not attempt to <user_action>", 440 ]); 441 442 function choice(arr){ return arr[Math.floor(Math.random()*arr.length)]; } 443 444 // Expand tokens like <token> within a template using LEXICON 445 function expandTemplate(tpl, depth=0){ 446 if (depth > 5) return tpl; // safety 447 return tpl.replace(/<([a-z_]+)>/gi, (_m, key) => { 448 const list = LEXICON[key]; 449 if (!list || !list.length) return key; 450 const val = choice(list); 451 // If the chosen value itself contains tokens, expand further 452 if (/[<][a-z_]+[>]/i.test(val)) { 453 return expandTemplate(val, depth+1); 454 } 455 return val; 456 }); 457 } 458 459 // Collapse extra spaces and tidy punctuation 460 function tidy(str){ 461 return str 462 .replace(/\s+/g,' ') 463 .replace(/\s+([,;:!?])/g, '$1') 464 .replace(/\(\s+/g, '(') 465 .replace(/\s+\)/g, ')') 466 .trim(); 467 } 468 // Fix a/an before vowel sounds 469 function fixArticles(str){ 470 return str 471 .replace(/\b[Aa]\s+([aeiouAEIOU])/g, 'an $1') 472 .replace(/\b[Aa]n\s+([bcdfghjklmnpqrstvwxyz])/gi, 'a $1'); 473 } 474 475 // 75% grammarâgenerated, 25% from fixed list to keep favorites 476 function generateMainPhrase(){ 477 // just to test 478 return choice(PHRASES); 479 480 if (Math.random() < 0.25) { 481 return choice(PHRASES); 482 } 483 const raw = expandTemplate(choice(TEMPLATES)); 484 return fixArticles(tidy(raw)); 485 } 486 487 let mascotClickCount = 0; 488 489 function getMascotPhrase() { 490 mascotClickCount++; 491 492 // 1â2 clicks: normal, fresh phrases 493 if (mascotClickCount <= 2) { 494 return generateMainPhrase(); 495 } 496 497 // 3â5 clicks: mildly insistent / already-clicked 498 if (mascotClickCount <= 5) { 499 return ALREADY_CLICKED[Math.floor(Math.random() * ALREADY_CLICKED.length)]; 500 } 501 502 // 6+ clicks: increasingly over-it, occasionally just "â¦" 503 if (Math.random() < 0.25) { 504 return "â¦"; 505 } 506 return ALREADY_CLICKED_ENOUGH[Math.floor(Math.random() * ALREADY_CLICKED_ENOUGH.length)]; 507 } 508 509 function showBubble(anchorImg, text) { 510 const card = anchorImg.closest('.card') || anchorImg.parentElement || document.body; 511 // Ensure container can anchor absolutely positioned bubble 512 const cs = getComputedStyle(card); 513 if (cs.position === 'static') card.style.position = 'relative'; 514 515 // Remove any existing bubble 516 const existing = card.querySelector('.speech-bubble'); 517 if (existing) existing.remove(); 518 519 // Build bubble 520 const bubble = document.createElement('div'); 521 bubble.className = 'speech-bubble'; 522 bubble.setAttribute('role', 'status'); 523 bubble.setAttribute('aria-live', 'polite'); 524 bubble.textContent = text; 525 526 Object.assign(bubble.style, { 527 position: 'absolute', 528 right: '54px', 529 bottom: '8px', 530 maxWidth: '220px', 531 padding: '8px 10px', 532 borderRadius: '10px', 533 background: 'var(--card, #fffef9)', 534 color: 'var(--ink, #2f2a28)', 535 border: '1px solid var(--ring, #e3dccf)', 536 boxShadow: '0 6px 16px rgba(35,30,28,.16)', 537 fontSize: '0.9rem', 538 zIndex: 999, 539 opacity: '0', 540 transform: 'translateY(4px)', 541 transition: 'opacity .15s ease, transform .15s ease', 542 pointerEvents: 'none' 543 }); 544 545 const tail = document.createElement('div');
546 Object.assign(tail.style, { 547 position: 'absolute', 548 right: '-8px', 549 bottom: '12px', 550 width: 0, 551 height: 0, 552 borderLeft: '8px solid var(--card, #fffef9)', 553 borderTop: '6px solid transparent', 554 borderBottom: '6px solid transparent' 555 }); 556 557 bubble.appendChild(tail); 558 card.appendChild(bubble); 559 560 // Animate in/out 561 requestAnimationFrame(() => { 562 bubble.style.opacity = '1'; 563 bubble.style.transform = 'translateY(0)'; 564 }); 565 setTimeout(() => { 566 bubble.style.opacity = '0'; 567 bubble.style.transform = 'translateY(4px)'; 568 setTimeout(() => bubble.remove(), 160); 569 }, 2800); 570 } 571 572 // Global inline fallback so templates can call onclick="return __mascotSpeak(this,event)" 573 window.__mascotSpeak = function (img, ev) { 574 if (ev) { ev.preventDefault(); ev.stopPropagation(); } 575 const force = img && img.dataset && img.dataset.force === 'true'; 576 const filename = img.src.split('/').pop(); 577 if (!force && filename === '0.png') { 578 try { console.debug('[mascot] 0.png detected, skipping bubble'); } catch (e) {} 579 return false; 580 } 581 const phrase = getMascotPhrase(); 582 try { console.debug('[mascot] click â bubble (inline):', phrase); } catch (e) {} 583 showBubble(img, phrase); 584 return false; 585 }; 586 587 // Delegated handler in CAPTURE phase (intercepts before link navigation) 588 document.addEventListener('click', (ev) => { 589 if (!ev.target || !ev.target.closest) return; 590 const el = ev.target.closest('.mascot'); 591 if (!el) return; 592 593 // Prevent navigation / default behavior for mascot clicks 594 ev.preventDefault(); 595 ev.stopPropagation(); 596 597 const isImg = el.tagName === 'IMG'; 598 const force = el.dataset && el.dataset.force === 'true'; 599 600 // For IMG mascots, honor the 0.png skip behavior 601 const filename = el.src && el.src.split('/').pop(); 602 if (isImg && !force && filename === '0.png') { 603 try { console.debug('[mascot] 0.png detected, skipping bubble'); } catch (e) {} 604 return; 605 } 606 607 const phrase = getMascotPhrase(); 608 try { console.debug('[mascot] click â bubble (delegated):', phrase, 'on', el.tagName); } catch (e) {} 609 showBubble(el, phrase); 610 }, true); // capture 611 // âââ TEST HARNESS âââ 612 // Usage: add ?mascotTest=1 to the URL OR set window.MASCOT_TEST=true before this script. 613 // Logs a batch of grammar-generated phrases (and a few fixed) to the console on load. 614 function __generateGrammarOnly() { 615 const raw = expandTemplate(choice(TEMPLATES)); 616 return fixArticles(tidy(raw)); 617 } 618 619 function __logMascotBatch(count = 50) { 620 try { console.groupCollapsed(`[mascot:test] ${count} samples`); } catch (e) {} 621 for (let i = 0; i < count; i++) { 622 const g = __generateGrammarOnly(); 623 try { console.log('grammar:', g); } catch (e) {} 624 } 625 // also sample a few fixed phrases for contrast 626 for (let j = 0; j < 10; j++) { 627 try { console.log('fixed :', choice(PHRASES)); } catch (e) {} 628 } 629 try { console.groupEnd(); } catch (e) {} 630 } 631 632 if (typeof window !== 'undefined') { 633 const testFlag = (typeof window.MASCOT_TEST !== 'undefined' && window.MASCOT_TEST) || 634 (typeof location !== 'undefined' && /[?&]mascotTest=1\b/.test(location.search)); 635 if (testFlag) { 636 if (document.readyState === 'loading') { 637 document.addEventListener('DOMContentLoaded', () => __logMascotBatch(60)); 638 } else { 639 __logMascotBatch(60); 640 } 641 } 642 } 643})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.