1/** 2 * æ±ç¨ã«ããã¤ã³ãªã¼ãã¼ã¬ã¤ 3 * spec: docs/features/battle-entry-cutin-popup/spec.md 4 * docs/features/dokidoki-max-pull-double-push-cutin/spec.mdï¼variant='bolt'ï¼ 5 * 6 * window.playCutin(config, onComplete) ãå ¬éã 7 * config: { userName, iconUrl, tagline, soundKey, isSelf, variant? } 8 * variant: 'band'ï¼æ¢å®ã»å¸¯åï¼| 'bolt'ï¼è½é·åã稲妻 canvas + ã·ã§ã¤ã¯ï¼ 9 * onComplete: ã«ããã¤ã³ CSS animation çµäºæï¼animationendï¼ã«å¼ã°ãããsetTimeout ãã©ã¼ã«ããã¯ä»ãã 10 * 11 * SE çºç«ã¯æ¬ã¢ã¸ã¥ã¼ã«å ã§ä¸å åï¼å¼ã³åºãå´ã¯ SE.play ãå¼ã°ãªãï¼ã 12 * 鮮度ã»äºéçºç« dedup ã¯å¼ã³åºãå´ï¼ä¾: auction-room.jsï¼ã®è²¬åã 13 */ 14(function () { 15 'use strict'; 16 17 // CSS animation total = 2.4sããã©ã¼ã«ãã㯠timeout ã¯ãã¼ã¸ã³ 200ms ãæãããã 18 // prefers-reduced-motion ç°å¢ã§ã¯ã¢ãã¡ã¼ã·ã§ã³ãç¡å¹åãã animationend ãçºç«ããªããã峿 resetã 19 var COMPLETE_FALLBACK_MS = 2600; 20 var REDUCED_MOTION_FALLBACK_MS = 100; 21 var BOLT_DECAY_MS = 300; 22 var SHAKE_DURATION_MS = 420; 23 var SHAKE_MAGNITUDE_PX = 14; 24 25 function isReducedMotion() { 26 return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); 27 } 28 29 var container = null; 30 var playing = false; 31 var playTimeoutId = null; 32 var currentOnComplete = null; 33 var completeCalled = false; 34 var boltRafId = null; 35 var shakeRafId = null; 36 37 function initContainer() { 38 if (container) return container; 39 container = document.getElementById('cutin-overlay'); 40 return container; 41 } 42 43 function clearBoltCanvas() { 44 var canvas = container && container.querySelector('.cutin-bolt'); 45 if (!canvas) return; 46 var ctx = canvas.getContext && canvas.getContext('2d'); 47 if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height); 48 } 49 50 function resetToIdle() { 51 if (playTimeoutId) { 52 clearTimeout(playTimeoutId); 53 playTimeoutId = null; 54 } 55 if (boltRafId) { cancelAnimationFrame(boltRafId); boltRafId = null; } 56 if (shakeRafId) { cancelAnimationFrame(shakeRafId); shakeRafId = null; } 57 clearBoltCanvas(); 58 if (container) { 59 container.classList.remove('play'); 60 container.classList.remove('variant-bolt'); 61 container.style.display = 'none'; 62 container.style.setProperty('--cutin-shake-x', '0px'); 63 container.style.setProperty('--cutin-shake-y', '0px'); 64 } 65 playing = false; 66 } 67 68 // ---- variant='bolt' ç¨: 稲妻æç»ï¼auction-room.js ã® AuctionDoublePushCutin ãåèã«ç¬ç«å®è£ ã 69 // auction å´ã®ãã¡ã¤ã«ã¯ä¸å夿´ããªã â spec §1.1ï¼ ---- 70 71 function makeBoltPath(x1, y1, x2, y2, displace, segments) { 72 var pts = [{ x: x1, y: y1 }]; 73 var dx = (x2 - x1) / segments, dy = (y2 - y1) / segments; 74 for (var i = 1; i < segments; i++) { 75 var t = i / segments; 76 var jitter = displace * (0.4 + t * 1.1); 77 pts.push({ 78 x: x1 + dx * i + (Math.random() - 0.5) * jitter, 79 y: y1 + dy * i + (Math.random() - 0.5) * jitter * 0.25 80 }); 81 } 82 pts.push({ x: x2, y: y2 }); 83 return pts; 84 } 85 86 function tracePathsBatch(ctx, bolts) { 87 ctx.beginPath(); 88 for (var i = 0; i < bolts.length; i++) { 89 var pts = bolts[i]; 90 ctx.moveTo(pts[0].x, pts[0].y); 91 for (var j = 1; j < pts.length; j++) ctx.lineTo(pts[j].x, pts[j].y); 92 } 93 ctx.stroke(); 94 } 95 96 function drawBolts(el) { 97 var canvas = el.querySelector('.cutin-bolt'); 98 if (!canvas) return; 99 var ctx = canvas.getContext('2d'); 100 if (!ctx) return; 101 102 var dpr = Math.min(window.devicePixelRatio || 1, 2); 103 var w = window.innerWidth; 104 var h = window.innerHeight; 105 canvas.width = Math.floor(w * dpr); 106 canvas.height = Math.floor(h * dpr); 107 canvas.style.width = w + 'px'; 108 canvas.style.height = h + 'px';
109 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 110 111 // åç¹ã¯ã¢ã¤ã³ã³ä¸å¿ï¼bolt ç¨ keyframes 㯠0% ãä¸å¤®ä½ç½®ããéå§ããããããã®æç¹ã§åº§æ¨ã確å®ãã¦ããï¼ 112 var iconEl = el.querySelector('.cutin-icon'); 113 var ox = w / 2, oy = h * 0.42; 114 if (iconEl) { 115 var rect = iconEl.getBoundingClientRect(); 116 if (rect.width && rect.height) { 117 ox = rect.left + rect.width / 2; 118 oy = rect.top + rect.height / 2; 119 } 120 } 121 122 var bolts = []; 123 var boltCount = 8; 124 var baseAngle = Math.random() * Math.PI * 2; 125 for (var i = 0; i < boltCount; i++) { 126 var angle = baseAngle + (i / boltCount) * Math.PI * 2 + (Math.random() - 0.5) * 0.4; 127 var length = 280 + Math.random() * 340; 128 var tx = ox + Math.cos(angle) * length; 129 var ty = oy + Math.sin(angle) * length; 130 bolts.push(makeBoltPath(ox, oy, tx, ty, 55, 10)); 131 if (Math.random() < 0.3) { 132 var last = bolts[bolts.length - 1]; 133 var gp = last[Math.floor(last.length / 2)]; 134 var subAngle = angle + (Math.random() - 0.5) * 1.2; 135 var subLen = 90 + Math.random() * 130; 136 bolts.push(makeBoltPath(gp.x, gp.y, gp.x + Math.cos(subAngle) * subLen, gp.y + Math.sin(subAngle) * subLen, 40, 6)); 137 } 138 } 139 140 var startTime = performance.now(); 141 function frame(now) { 142 var elapsed = now - startTime; 143 ctx.clearRect(0, 0, w, h); 144 var alpha = Math.max(0, 1 - elapsed / BOLT_DECAY_MS); 145 alpha = Math.pow(alpha, 0.7) * (0.55 + Math.random() * 0.45); 146 ctx.lineCap = 'round'; 147 ctx.lineJoin = 'round'; 148 ctx.strokeStyle = 'rgba(140,190,255,' + (alpha * 0.55) + ')'; 149 ctx.lineWidth = 38; 150 ctx.shadowColor = '#7db4ff'; 151 ctx.shadowBlur = 42; 152 tracePathsBatch(ctx, bolts); 153 ctx.strokeStyle = 'rgba(255,255,255,' + alpha + ')'; 154 ctx.lineWidth = 11; 155 ctx.shadowBlur = 18; 156 tracePathsBatch(ctx, bolts); 157 ctx.shadowBlur = 0; 158 if (elapsed < BOLT_DECAY_MS) { 159 boltRafId = requestAnimationFrame(frame); 160 } else { 161 ctx.clearRect(0, 0, w, h); 162 boltRafId = null; 163 } 164 } 165 boltRafId = requestAnimationFrame(frame); 166 } 167 168 function startShake(el) { 169 var start = performance.now(); 170 function step(now) { 171 var t = (now - start) / SHAKE_DURATION_MS; 172 if (t >= 1) { 173 el.style.setProperty('--cutin-shake-x', '0px'); 174 el.style.setProperty('--cutin-shake-y', '0px'); 175 shakeRafId = null; 176 return; 177 } 178 var damp = 1 - t; 179 el.style.setProperty('--cutin-shake-x', ((Math.random() - 0.5) * SHAKE_MAGNITUDE_PX * damp * 2).toFixed(1) + 'px'); 180 el.style.setProperty('--cutin-shake-y', ((Math.random() - 0.5) * SHAKE_MAGNITUDE_PX * damp * 2).toFixed(1) + 'px'); 181 shakeRafId = requestAnimationFrame(step); 182 } 183 shakeRafId = requestAnimationFrame(step); 184 } 185 186 function fireComplete() { 187 if (completeCalled) return; 188 completeCalled = true; 189 var cb = currentOnComplete; 190 currentOnComplete = null; 191 if (typeof cb === 'function') { 192 try { cb(); } catch (e) { /* callback 失æã¯æ¡ãæ½°ã */ } 193 } 194 } 195 196 function applyColors(el, fixedHue) { 197 var hue = (typeof fixedHue === 'number') ? fixedHue : Math.floor(Math.random() * 360); 198 var style = el.style; 199 style.setProperty('--cutin-hot', 'hsl(' + hue + ', 100%, 60%)'); 200 style.setProperty('--cutin-glow', 'hsla(' + hue + ', 100%, 55%, 0.78)'); 201 style.setProperty('--cutin-volt', 'hsl(' + ((hue + 15) % 360) + ', 100%, 80%)'); 202 } 203 204 function playCutin(config, onComplete) { 205 config = config || {}; 206 var el = initContainer(); 207 if (!el) { 208 if (typeof onComplete === 'function') onComplete(); 209 return; 210 } 211 212 if (playing) { 213 fireComplete(); 214 resetToIdle(); 215 } 216 currentOnComplete = onComplete || null; 217 completeCalled = false;
218 219 // â ï¸ variant ã®ä»ä¸ã¯ä¸ã® resetToIdle() ããå¿ ãå¾ã«ç½®ããå ã«ä»ããã¨ãç´åã®åçã 220 // å²ãè¾¼ãã ã¨ãã« resetToIdle() ãç´å¾ã«å¥ããã¦ãã¾ããå²ãè¾¼ã¿åçã®ã¨ãã ã 221 // bolt ãç¡é³ã§ band ã«ãªãï¼devils-advocate ææï¼ã 222 var isBolt = config.variant === 'bolt'; 223 el.classList.toggle('variant-bolt', isBolt); 224 225 var headlineEl = el.querySelector('.cutin-headline'); 226 if (headlineEl) { 227 if (Array.isArray(config.taglineLines) && config.taglineLines.length > 0) { 228 // æãè¿ãåä½ãå¼ã³åºãå´ã§æå®ããï¼åã»ã°ã¡ã³ã㯠inline-blockãã¢ãã¤ã«ã§ã¯ 1 è¡ãã¤ï¼ã 229 // textContent ã§å ¥ããã®ã§ HTML ã¯è§£éãããªãï¼XSS 対ç㯠tagline ã¨åçï¼ 230 headlineEl.textContent = ''; 231 config.taglineLines.forEach(function (line) { 232 var seg = document.createElement('span'); 233 seg.className = 'cutin-headline__seg'; 234 seg.textContent = String(line); 235 headlineEl.appendChild(seg); 236 }); 237 } else { 238 headlineEl.textContent = config.tagline ? String(config.tagline) : ''; 239 } 240 } 241 242 var nameEl = el.querySelector('.cutin-name'); 243 if (nameEl) { 244 nameEl.textContent = config.userName ? String(config.userName) : ''; 245 } 246 247 var imgEl = el.querySelector('.cutin-icon-img'); 248 if (imgEl) { 249 var src = config.iconUrl ? String(config.iconUrl) : ''; 250 imgEl.src = src; 251 imgEl.alt = config.userName ? String(config.userName) : ''; 252 imgEl.style.display = src ? '' : 'none'; 253 } 254 255 var youBadgeEl = el.querySelector('.cutin-you-badge'); 256 if (youBadgeEl) { 257 youBadgeEl.hidden = !config.isSelf; 258 } 259 260 applyColors(el, (typeof config.fixedHue === 'number') ? config.fixedHue : null); 261 262 if (config.soundKey && window.SE && typeof window.SE.play === 'function') { 263 try { 264 // ååãããã¤å¾ã® SE 診æãã°ï¼æ¬ PR ãã¼ã¸å¾ã«æ¡ä»¶ä»ãã«æ»ãï¼ 265 var cfg = (window.SE_CONFIG || {}).files || {}; 266 var hasUrl = !!cfg[config.soundKey]; 267 console.log('[cutin] SE.play soundKey=' + config.soundKey + ' hasUrl=' + hasUrl + (hasUrl ? ' url=' + cfg[config.soundKey].substring(0, 80) : '')); 268 window.SE.play(String(config.soundKey)); 269 } catch (e) { console.warn('[cutin] SE.play threw', e); } 270 } else if (config.soundKey) { 271 console.warn('[cutin] SE not available for soundKey=' + config.soundKey, {hasSE: !!window.SE, hasPlay: !!(window.SE && window.SE.play)}); 272 } 273 274 playing = true; 275 el.style.display = 'block'; 276 el.classList.remove('play'); 277 void el.offsetWidth; 278 el.classList.add('play'); 279 280 var reduced = isReducedMotion(); 281 // .cutin-bolt ãç¡ãå ´åï¼ä»ãã¼ã¸ã§å¤ããã¼ã¸ã§ã³ã® <x-cutin-overlay> ãèªã¾ããå ´åçï¼ã¯ 282 // 稲妻ãã·ã§ã¤ã¯ãè¡ããªããdrawBolts() åä½ã®æ©æ return ã ãã ã¨ãã·ã§ã¤ã¯ã ã帯ã 283 // æºããä¸éå端ãªç¶æ ã«ãªãï¼code-refactor-reviewer ææï¼ã 284 if (isBolt && !reduced && el.querySelector('.cutin-bolt')) { 285 try { drawBolts(el); startShake(el); } catch (e) { /* 表示å°ç¨æ©è½ã§æ½é¸ãæ¢ããªã */ } 286 } 287 288 var fallbackMs = reduced ? REDUCED_MOTION_FALLBACK_MS : COMPLETE_FALLBACK_MS; 289 playTimeoutId = setTimeout(function () { 290 fireComplete(); 291 resetToIdle(); 292 }, fallbackMs); 293 294 var nameWrap = el.querySelector('.cutin-name-wrap'); 295 if (nameWrap) { 296 var handleEnd = function () { 297 nameWrap.removeEventListener('animationend', handleEnd); 298 fireComplete(); 299 }; 300 nameWrap.addEventListener('animationend', handleEnd, { once: true }); 301 } 302 } 303 304 window.addEventListener('pageshow', function (e) { 305 if (e && e.persisted) { 306 fireComplete(); 307 resetToIdle(); 308 } 309 }); 310 311 window.playCutin = playCutin; 312}());
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.