PageSourceSearch

https://bogost.com/assets/mascot.js

js bogost.com collected 2026-10-02 03:09:58 UTC 22,543 bytes, 643 lines download raw bytes

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.