1// Shimeji minimal JS: share, related, install ribbon 2(function () { 3 const byId = (id) => document.getElementById(id); 4 const q = (sel, el = document) => el.querySelector(sel); 5 const qall = (sel, el = document) => Array.from(el.querySelectorAll(sel)); 6 const debounce = (fn, ms = 120) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; }; 7 8 // Python-like slugify for URLs (match server): 9 function jsSlugify(str) { 10 if (!str) return ''; 11 let s = str.toLowerCase().replace(/&/g, ' and '); 12 s = s.replace(/[^a-z0-9]+/g, '-'); 13 s = s.replace(/-+/g, '-').replace(/^-|-$/g, ''); 14 return s; 15 } 16 17 // -------- A) Homepage Share button -------- 18 (function(){ 19 const btn = byId('share-home'); 20 if (!btn) return; 21 btn.addEventListener('click', async () => { 22 const url = location.origin + '/'; 23 const title = 'Shimeji - Anime Screen Buddies for Android'; 24 const text = 'Let anime characters roam your Android screen with Shimeji!'; 25 if (navigator.share) { 26 try { await navigator.share({ title, text, url }); return; } catch(_) {} 27 } 28 const t = encodeURIComponent(text + ' ' + url); 29 window.open('https://twitter.com/intent/tweet?text=' + t, '_blank', 'noopener'); 30 }); 31 })(); 32 33 // -------- D) Share (mascot page) -------- 34 (function () { 35 const shareBtn = byId('share-btn'); 36 if (!shareBtn) return; 37 38 const url = shareBtn.dataset.shareUrl || location.href; 39 const title = shareBtn.dataset.shareTitle || 'Shimeji'; 40 const text = shareBtn.dataset.shareText || ''; 41 42 const showToast = (msg) => { 43 let t = byId('toast'); 44 if (!t) { 45 t = document.createElement('div'); 46 t.id = 'toast'; 47 t.className = 'toast'; 48 document.body.appendChild(t); 49 } 50 t.textContent = msg; 51 t.classList.add('show'); 52 setTimeout(() => t.classList.remove('show'), 1800); 53 }; 54 55 if (!navigator.share && !navigator.clipboard) { 56 shareBtn.style.display = 'none'; 57 return; 58 } 59 60 shareBtn.addEventListener('click', async () => { 61 if (window.isSecureContext && navigator.share) { 62 try { await navigator.share({ title, text, url }); return; } 63 catch (_) {} 64 } 65 try { await navigator.clipboard.writeText(url); showToast('Link copied'); } 66 catch (_) { 67 const ta = document.createElement('textarea'); 68 ta.value = url; ta.style.position = 'fixed'; ta.style.opacity = '0'; 69 document.body.appendChild(ta); ta.select(); 70 try { document.execCommand('copy'); showToast('Link copied'); } 71 catch (_) { window.prompt('Copy this link:', url); } 72 document.body.removeChild(ta); 73 } 74 }); 75 })(); 76 77 // -------- E) Related (mascot page) -------- 78 if (window.__SHIMEJI__) { 79 try { 80 const { id, catSlug } = window.__SHIMEJI__; 81 fetch(`/${catSlug}/`).then(r => r.text()).then(html => { 82 const div = document.createElement('div'); div.innerHTML = html; 83 const cards = Array.from(div.querySelectorAll('.grid .card')); 84 const out = []; 85 for (const a of cards) { 86 const href = a.getAttribute('href') || ''; 87 const idFromHref = parseInt(href.split('-').pop()) || -1; 88 if (idFromHref === id) continue; 89 out.push(a.outerHTML); 90 if (out.length >= 8) break; 91 } 92 const mount = byId('related-grid'); if (mount) mount.innerHTML = out.join(''); 93 }).catch(()=>{}); 94 } catch(_) {} 95 } 96 97 // -------- F) Install ribbon -------- 98 (function(){ 99 const ribbon = byId('install-ribbon'); 100 const closeBtn = byId('ribbon-close'); 101 if (!ribbon || !closeBtn) return; 102 103 const key = 'shimeji_hide_ribbon_until'; 104 const now = Date.now(); 105 const hideUntil = parseInt(localStorage.getItem(key) || '0', 10); 106 const mobile = window.matchMedia('(max-width: 820px)').matches; 107 108 if (mobile && now > hideUntil) { 109 requestAnimationFrame(() => ribbon.classList.add('show')); 110 } 111 112 closeBtn.addEventListener('click', () => { 113 ribbon.classList.remove('show'); 114 ribbon.style.display = 'none'; 115 localStorage.setItem(key, String(now + 7*24*60*60*1000)); // 7 days 116 }); 117 })(); 118 119// -------- H) Smart "Get this character" CTA (deep link if app, Play fallback) -------- 120(function () { 121 const btn = document.querySelector('.js-get'); 122 if (!btn) return; 123 124 const cid = btn.getAttribute('data-cid') || ''; 125 const name = btn.getAttribute('data-name') || 'Shimeji'; 126 127 // Build Play URL with Install Referrer 128 const play = new URL('https://play.google.com/store/apps/details'); 129 play.searchParams.set('id', 'com.digitalcosmos.shimeji'); 130 play.searchParams.set('referrer', `cid=${encodeURIComponent(cid)}&src=website`); 131 132 // Android Chrome-style intent (opens app if installed; Play fallback embedded) 133 const intent = `intent://shimeji.app/dl?cid=${encodeURIComponent(cid)}` 134 + `#Intent;scheme=https;package=com.digitalcosmos.shimeji;` 135 + `S.browser_fallback_url=${encodeURIComponent(play.toString())};end`;
136 137 const ua = navigator.userAgent || ''; 138 const isAndroid = /Android/i.test(ua); 139 const isChromeLike = isAndroid && /Chrome|Chromium|EdgA|SamsungBrowser/i.test(ua) && !/OPR|Opera/i.test(ua); 140 141 const go = (href) => { try { location.href = href; } catch (_) { location.assign(href); } }; 142 143 btn.addEventListener('click', (e) => { 144 e.preventDefault(); 145 146 try { if (window.gtag) gtag('event','click_get_character',{ cid, name, path: location.pathname }); } catch(_) {} 147 148 if (isChromeLike) { 149 // Android Chrome/Samsung/Edge â use intent:// 150 go(intent); 151 } else if (isAndroid) { 152 // Other Android browsers (e.g., Firefox) â just use Play with referrer 153 go(play.toString()); 154 } else { 155 // Desktop/iOS/etc. â Play (Android-only app) 156 go(play.toString()); 157 } 158 }); 159})(); 160 161})();
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.